Mercurial > judyates
view e2gallerypro/e2upload/Backend/Assets/getid3/module.audio.xiph.php @ 7:3f89d9a017e2 judyates
[svn r8] try this shit
author | rlm |
---|---|
date | Mon, 22 Feb 2010 11:34:16 -0500 |
parents | 3f6b44aa6b35 |
children |
line wrap: on
line source
1 <?php2 // +----------------------------------------------------------------------+3 // | PHP version 5 |4 // +----------------------------------------------------------------------+5 // | Copyright (c) 2002-2006 James Heinrich, Allan Hansen |6 // +----------------------------------------------------------------------+7 // | This source file is subject to version 2 of the GPL license, |8 // | that is bundled with this package in the file license.txt and is |9 // | available through the world-wide-web at the following url: |10 // | http://www.gnu.org/copyleft/gpl.html |11 // +----------------------------------------------------------------------+12 // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org |13 // +----------------------------------------------------------------------+14 // | Authors: James Heinrich <infoØgetid3*org> |15 // | Allan Hansen <ahØartemis*dk> |16 // +----------------------------------------------------------------------+17 // | module.audio.xiph.php |18 // | Module for analyzing Xiph.org audio file formats: |19 // | Ogg Vorbis, FLAC, OggFLAC and Speex - not Ogg Theora |20 // | dependencies: module.lib.image_size.php (optional) |21 // +----------------------------------------------------------------------+22 //23 // $Id: module.audio.xiph.php,v 1.5 2006/12/03 21:12:43 ah Exp $27 class getid3_xiph extends getid3_handler28 {30 public function Analyze() {32 $getid3 = $this->getid3;34 if ($getid3->option_tags_images) {35 $getid3->include_module('lib.image_size');36 }38 fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);40 $magic = fread($getid3->fp, 4);42 if ($magic == 'OggS') {43 return $this->ParseOgg();44 }46 if ($magic == 'fLaC') {47 return $this->ParseFLAC();48 }50 }54 private function ParseOgg() {56 $getid3 = $this->getid3;58 fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);60 $getid3->info['audio'] = $getid3->info['ogg'] = array ();61 $info_ogg = &$getid3->info['ogg'];62 $info_audio = &$getid3->info['audio'];64 $getid3->info['fileformat'] = 'ogg';67 //// Page 1 - Stream Header69 $ogg_page_info = $this->ParseOggPageHeader();70 $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;72 if (ftell($getid3->fp) >= getid3::FREAD_BUFFER_SIZE) {73 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?)');74 }76 $file_data = fread($getid3->fp, $ogg_page_info['page_length']);77 $file_data_offset = 0;80 // OggFLAC81 if (substr($file_data, 0, 4) == 'fLaC') {83 $info_audio['dataformat'] = 'flac';84 $info_audio['bitrate_mode'] = 'vbr';85 $info_audio['lossless'] = true;87 }90 // Ogg Vorbis91 elseif (substr($file_data, 1, 6) == 'vorbis') {93 $info_audio['dataformat'] = 'vorbis';94 $info_audio['lossless'] = false;96 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int($file_data[0]);97 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis'99 getid3_lib::ReadSequence('LittleEndian2Int', $info_ogg, $file_data, 7,100 array (101 'bitstreamversion' => 4,102 'numberofchannels' => 1,103 'samplerate' => 4,104 'bitrate_max' => 4,105 'bitrate_nominal' => 4,106 'bitrate_min' => 4107 )108 );110 $n28 = getid3_lib::LittleEndian2Int($file_data{28});111 $info_ogg['blocksize_small'] = pow(2, $n28 & 0x0F);112 $info_ogg['blocksize_large'] = pow(2, ($n28 & 0xF0) >> 4);113 $info_ogg['stop_bit'] = $n28;115 $info_audio['channels'] = $info_ogg['numberofchannels'];116 $info_audio['sample_rate'] = $info_ogg['samplerate'];118 $info_audio['bitrate_mode'] = 'vbr'; // overridden if actually abr120 if ($info_ogg['bitrate_max'] == 0xFFFFFFFF) {121 unset($info_ogg['bitrate_max']);122 $info_audio['bitrate_mode'] = 'abr';123 }125 if ($info_ogg['bitrate_nominal'] == 0xFFFFFFFF) {126 unset($info_ogg['bitrate_nominal']);127 }129 if ($info_ogg['bitrate_min'] == 0xFFFFFFFF) {130 unset($info_ogg['bitrate_min']);131 $info_audio['bitrate_mode'] = 'abr';132 }133 }136 // Speex137 elseif (substr($file_data, 0, 8) == 'Speex ') {139 // http://www.speex.org/manual/node10.html141 $info_audio['dataformat'] = 'speex';142 $getid3->info['mime_type'] = 'audio/speex';143 $info_audio['bitrate_mode'] = 'abr';144 $info_audio['lossless'] = false;146 getid3_lib::ReadSequence('LittleEndian2Int', $info_ogg['pageheader'][$ogg_page_info['page_seqno']], $file_data, 0,147 array (148 'speex_string' => -8, // hard-coded to 'Speex '149 'speex_version' => -20, // string150 'speex_version_id' => 4,151 'header_size' => 4,152 'rate' => 4,153 'mode' => 4,154 'mode_bitstream_version' => 4,155 'nb_channels' => 4,156 'bitrate' => 4,157 'framesize' => 4,158 'vbr' => 4,159 'frames_per_packet' => 4,160 'extra_headers' => 4,161 'reserved1' => 4,162 'reserved2' => 4163 )164 );166 $getid3->info['speex']['speex_version'] = trim($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['speex_version']);167 $getid3->info['speex']['sample_rate'] = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['rate'];168 $getid3->info['speex']['channels'] = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['nb_channels'];169 $getid3->info['speex']['vbr'] = (bool)$info_ogg['pageheader'][$ogg_page_info['page_seqno']]['vbr'];170 $getid3->info['speex']['band_type'] = getid3_xiph::SpeexBandModeLookup($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['mode']);172 $info_audio['sample_rate'] = $getid3->info['speex']['sample_rate'];173 $info_audio['channels'] = $getid3->info['speex']['channels'];175 if ($getid3->info['speex']['vbr']) {176 $info_audio['bitrate_mode'] = 'vbr';177 }178 }180 // Unsupported Ogg file181 else {183 throw new getid3_exception('Expecting either "Speex " or "vorbis" identifier strings, found neither');184 }187 //// Page 2 - Comment Header189 $ogg_page_info = $this->ParseOggPageHeader();190 $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;192 switch ($info_audio['dataformat']) {194 case 'vorbis':195 $file_data = fread($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length']);196 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($file_data, 0, 1));197 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis'198 $this->ParseVorbisCommentsFilepointer();199 break;201 case 'flac':202 if (!$this->FLACparseMETAdata()) {203 throw new getid3_exception('Failed to parse FLAC headers');204 }205 break;207 case 'speex':208 fseek($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length'], SEEK_CUR);209 $this->ParseVorbisCommentsFilepointer();210 break;211 }214 //// Last Page - Number of Samples216 fseek($getid3->fp, max($getid3->info['avdataend'] - getid3::FREAD_BUFFER_SIZE, 0), SEEK_SET);217 $last_chunk_of_ogg = strrev(fread($getid3->fp, getid3::FREAD_BUFFER_SIZE));219 if ($last_OggS_postion = strpos($last_chunk_of_ogg, 'SggO')) {220 fseek($getid3->fp, $getid3->info['avdataend'] - ($last_OggS_postion + strlen('SggO')), SEEK_SET);221 $getid3->info['avdataend'] = ftell($getid3->fp);222 $info_ogg['pageheader']['eos'] = $this->ParseOggPageHeader();223 $info_ogg['samples'] = $info_ogg['pageheader']['eos']['pcm_abs_position'];224 $info_ogg['bitrate_average'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / ($info_ogg['samples'] / $info_audio['sample_rate']);225 }227 if (!empty($info_ogg['bitrate_average'])) {228 $info_audio['bitrate'] = $info_ogg['bitrate_average'];229 } elseif (!empty($info_ogg['bitrate_nominal'])) {230 $info_audio['bitrate'] = $info_ogg['bitrate_nominal'];231 } elseif (!empty($info_ogg['bitrate_min']) && !empty($info_ogg['bitrate_max'])) {232 $info_audio['bitrate'] = ($info_ogg['bitrate_min'] + $info_ogg['bitrate_max']) / 2;233 }234 if (isset($info_audio['bitrate']) && !isset($getid3->info['playtime_seconds'])) {235 $getid3->info['playtime_seconds'] = (float)((($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $info_audio['bitrate']);236 }238 if (isset($info_ogg['vendor'])) {239 $info_audio['encoder'] = preg_replace('/^Encoded with /', '', $info_ogg['vendor']);241 // Vorbis only242 if ($info_audio['dataformat'] == 'vorbis') {244 // Vorbis 1.0 starts with Xiph.Org245 if (preg_match('/^Xiph.Org/', $info_audio['encoder'])) {247 if ($info_audio['bitrate_mode'] == 'abr') {249 // Set -b 128 on abr files250 $info_audio['encoder_options'] = '-b '.round($info_ogg['bitrate_nominal'] / 1000);252 } elseif (($info_audio['bitrate_mode'] == 'vbr') && ($info_audio['channels'] == 2) && ($info_audio['sample_rate'] >= 44100) && ($info_audio['sample_rate'] <= 48000)) {253 // Set -q N on vbr files254 $info_audio['encoder_options'] = '-q '.getid3_xiph::GetQualityFromNominalBitrate($info_ogg['bitrate_nominal']);255 }256 }258 if (empty($info_audio['encoder_options']) && !empty($info_ogg['bitrate_nominal'])) {259 $info_audio['encoder_options'] = 'Nominal bitrate: '.intval(round($info_ogg['bitrate_nominal'] / 1000)).'kbps';260 }261 }262 }264 return true;265 }269 private function ParseOggPageHeader() {271 $getid3 = $this->getid3;273 // http://xiph.org/ogg/vorbis/doc/framing.html274 $ogg_header['page_start_offset'] = ftell($getid3->fp); // where we started from in the file276 $file_data = fread($getid3->fp, getid3::FREAD_BUFFER_SIZE);277 $file_data_offset = 0;279 while ((substr($file_data, $file_data_offset++, 4) != 'OggS')) {280 if ((ftell($getid3->fp) - $ogg_header['page_start_offset']) >= getid3::FREAD_BUFFER_SIZE) {281 // should be found before here282 return false;283 }284 if ((($file_data_offset + 28) > strlen($file_data)) || (strlen($file_data) < 28)) {285 if (feof($getid3->fp) || (($file_data .= fread($getid3->fp, getid3::FREAD_BUFFER_SIZE)) === false)) {286 // get some more data, unless eof, in which case fail287 return false;288 }289 }290 }292 $file_data_offset += 3; // page, delimited by 'OggS'294 getid3_lib::ReadSequence('LittleEndian2Int', $ogg_header, $file_data, $file_data_offset,295 array (296 'stream_structver' => 1,297 'flags_raw' => 1,298 'pcm_abs_position' => 8,299 'stream_serialno' => 4,300 'page_seqno' => 4,301 'page_checksum' => 4,302 'page_segments' => 1303 )304 );306 $file_data_offset += 23;308 $ogg_header['flags']['fresh'] = (bool)($ogg_header['flags_raw'] & 0x01); // fresh packet309 $ogg_header['flags']['bos'] = (bool)($ogg_header['flags_raw'] & 0x02); // first page of logical bitstream (bos)310 $ogg_header['flags']['eos'] = (bool)($ogg_header['flags_raw'] & 0x04); // last page of logical bitstream (eos)312 $ogg_header['page_length'] = 0;313 for ($i = 0; $i < $ogg_header['page_segments']; $i++) {314 $ogg_header['segment_table'][$i] = getid3_lib::LittleEndian2Int($file_data{$file_data_offset++});315 $ogg_header['page_length'] += $ogg_header['segment_table'][$i];316 }317 $ogg_header['header_end_offset'] = $ogg_header['page_start_offset'] + $file_data_offset;318 $ogg_header['page_end_offset'] = $ogg_header['header_end_offset'] + $ogg_header['page_length'];319 fseek($getid3->fp, $ogg_header['header_end_offset'], SEEK_SET);321 return $ogg_header;322 }326 private function ParseVorbisCommentsFilepointer() {328 $getid3 = $this->getid3;330 $original_offset = ftell($getid3->fp);331 $comment_start_offset = $original_offset;332 $comment_data_offset = 0;333 $vorbis_comment_page = 1;335 switch ($getid3->info['audio']['dataformat']) {337 case 'vorbis':338 $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset']; // Second Ogg page, after header block339 fseek($getid3->fp, $comment_start_offset, SEEK_SET);340 $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments'];341 $comment_data = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset);342 $comment_data_offset += (strlen('vorbis') + 1);343 break;346 case 'flac':347 fseek($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4, SEEK_SET);348 $comment_data = fread($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['block_length']);349 break;352 case 'speex':353 $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset']; // Second Ogg page, after header block354 fseek($getid3->fp, $comment_start_offset, SEEK_SET);355 $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments'];356 $comment_data = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset);357 break;360 default:361 return false;362 }364 $vendor_size = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));365 $comment_data_offset += 4;367 $getid3->info['ogg']['vendor'] = substr($comment_data, $comment_data_offset, $vendor_size);368 $comment_data_offset += $vendor_size;370 $comments_count = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));371 $comment_data_offset += 4;373 $getid3->info['avdataoffset'] = $comment_start_offset + $comment_data_offset;375 for ($i = 0; $i < $comments_count; $i++) {377 $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] = $comment_start_offset + $comment_data_offset;379 if (ftell($getid3->fp) < ($getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + 4)) {380 $vorbis_comment_page++;382 $ogg_page_info = $this->ParseOggPageHeader();383 $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;385 // First, save what we haven't read yet386 $as_yet_unused_data = substr($comment_data, $comment_data_offset);388 // Then take that data off the end389 $comment_data = substr($comment_data, 0, $comment_data_offset);391 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct392 $comment_data .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);393 $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);395 // Finally, stick the unused data back on the end396 $comment_data .= $as_yet_unused_data;398 $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1));399 }400 $getid3->info['ogg']['comments_raw'][$i]['size'] = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));402 // replace avdataoffset with position just after the last vorbiscomment403 $getid3->info['avdataoffset'] = $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + $getid3->info['ogg']['comments_raw'][$i]['size'] + 4;405 $comment_data_offset += 4;406 while ((strlen($comment_data) - $comment_data_offset) < $getid3->info['ogg']['comments_raw'][$i]['size']) {408 if (($getid3->info['ogg']['comments_raw'][$i]['size'] > $getid3->info['avdataend']) || ($getid3->info['ogg']['comments_raw'][$i]['size'] < 0)) {409 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');410 }412 $vorbis_comment_page++;414 $ogg_page_info = $this->ParseOggPageHeader();415 $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;417 // First, save what we haven't read yet418 $as_yet_unused_data = substr($comment_data, $comment_data_offset);420 // Then take that data off the end421 $comment_data = substr($comment_data, 0, $comment_data_offset);423 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct424 $comment_data .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);425 $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);427 // Finally, stick the unused data back on the end428 $comment_data .= $as_yet_unused_data;430 //$comment_data .= fread($getid3->fp, $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_length']);431 $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1));433 //$filebaseoffset += $ogg_page_info['header_end_offset'] - $ogg_page_info['page_start_offset'];434 }435 $comment_string = substr($comment_data, $comment_data_offset, $getid3->info['ogg']['comments_raw'][$i]['size']);436 $comment_data_offset += $getid3->info['ogg']['comments_raw'][$i]['size'];438 if (!$comment_string) {440 // no comment?441 $getid3->warning('Blank Ogg comment ['.$i.']');443 } elseif (strstr($comment_string, '=')) {445 $comment_exploded = explode('=', $comment_string, 2);446 $getid3->info['ogg']['comments_raw'][$i]['key'] = strtoupper($comment_exploded[0]);447 $getid3->info['ogg']['comments_raw'][$i]['value'] = @$comment_exploded[1];448 $getid3->info['ogg']['comments_raw'][$i]['data'] = base64_decode($getid3->info['ogg']['comments_raw'][$i]['value']);450 $getid3->info['ogg']['comments'][strtolower($getid3->info['ogg']['comments_raw'][$i]['key'])][] = $getid3->info['ogg']['comments_raw'][$i]['value'];452 if ($getid3->option_tags_images) {453 $image_chunk_check = getid3_lib_image_size::get($getid3->info['ogg']['comments_raw'][$i]['data']);454 $getid3->info['ogg']['comments_raw'][$i]['image_mime'] = image_type_to_mime_type($image_chunk_check[2]);455 }457 if (!@$getid3->info['ogg']['comments_raw'][$i]['image_mime'] || ($getid3->info['ogg']['comments_raw'][$i]['image_mime'] == 'application/octet-stream')) {458 unset($getid3->info['ogg']['comments_raw'][$i]['image_mime']);459 unset($getid3->info['ogg']['comments_raw'][$i]['data']);460 }463 } else {465 $getid3->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$comment_string);466 }467 }470 // Replay Gain Adjustment471 // http://privatewww.essex.ac.uk/~djmrob/replaygain/472 if (isset($getid3->info['ogg']['comments']) && is_array($getid3->info['ogg']['comments'])) {473 foreach ($getid3->info['ogg']['comments'] as $index => $commentvalue) {474 switch ($index) {475 case 'rg_audiophile':476 case 'replaygain_album_gain':477 $getid3->info['replay_gain']['album']['adjustment'] = (float)$commentvalue[0];478 unset($getid3->info['ogg']['comments'][$index]);479 break;481 case 'rg_radio':482 case 'replaygain_track_gain':483 $getid3->info['replay_gain']['track']['adjustment'] = (float)$commentvalue[0];484 unset($getid3->info['ogg']['comments'][$index]);485 break;487 case 'replaygain_album_peak':488 $getid3->info['replay_gain']['album']['peak'] = (float)$commentvalue[0];489 unset($getid3->info['ogg']['comments'][$index]);490 break;492 case 'rg_peak':493 case 'replaygain_track_peak':494 $getid3->info['replay_gain']['track']['peak'] = (float)$commentvalue[0];495 unset($getid3->info['ogg']['comments'][$index]);496 break;498 case 'replaygain_reference_loudness':499 $getid3->info['replay_gain']['reference_volume'] = (float)$commentvalue[0];500 unset($getid3->info['ogg']['comments'][$index]);501 break;502 }503 }504 }506 fseek($getid3->fp, $original_offset, SEEK_SET);508 return true;509 }513 private function ParseFLAC() {515 $getid3 = $this->getid3;517 // http://flac.sourceforge.net/format.html519 $getid3->info['fileformat'] = 'flac';520 $getid3->info['audio']['dataformat'] = 'flac';521 $getid3->info['audio']['bitrate_mode'] = 'vbr';522 $getid3->info['audio']['lossless'] = true;524 return $this->FLACparseMETAdata();525 }529 private function FLACparseMETAdata() {531 $getid3 = $this->getid3;533 do {535 $meta_data_block_offset = ftell($getid3->fp);536 $meta_data_block_header = fread($getid3->fp, 4);537 $meta_data_last_block_flag = (bool)(getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x80);538 $meta_data_block_type = getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x7F;539 $meta_data_block_length = getid3_lib::BigEndian2Int(substr($meta_data_block_header, 1, 3));540 $meta_data_block_type_text = getid3_xiph::FLACmetaBlockTypeLookup($meta_data_block_type);542 if ($meta_data_block_length < 0) {543 throw new getid3_exception('corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset);544 }546 $getid3->info['flac'][$meta_data_block_type_text]['raw'] = array (547 'offset' => $meta_data_block_offset,548 'last_meta_block' => $meta_data_last_block_flag,549 'block_type' => $meta_data_block_type,550 'block_type_text' => $meta_data_block_type_text,551 'block_length' => $meta_data_block_length,552 'block_data' => @fread($getid3->fp, $meta_data_block_length)553 );554 $getid3->info['avdataoffset'] = ftell($getid3->fp);556 switch ($meta_data_block_type_text) {558 case 'STREAMINFO':559 if (!$this->FLACparseSTREAMINFO($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {560 return false;561 }562 break;564 case 'PADDING':565 // ignore566 break;568 case 'APPLICATION':569 if (!$this->FLACparseAPPLICATION($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {570 return false;571 }572 break;574 case 'SEEKTABLE':575 if (!$this->FLACparseSEEKTABLE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {576 return false;577 }578 break;580 case 'VORBIS_COMMENT':581 $old_offset = ftell($getid3->fp);582 fseek($getid3->fp, 0 - $meta_data_block_length, SEEK_CUR);583 $this->ParseVorbisCommentsFilepointer($getid3->fp, $getid3->info);584 fseek($getid3->fp, $old_offset, SEEK_SET);585 break;587 case 'CUESHEET':588 if (!$this->FLACparseCUESHEET($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {589 return false;590 }591 break;593 case 'PICTURE':594 if (!$this->FLACparsePICTURE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {595 return false;596 }597 break;599 default:600 $getid3->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset);601 }603 } while ($meta_data_last_block_flag === false);606 if (isset($getid3->info['flac']['STREAMINFO'])) {607 $getid3->info['flac']['compressed_audio_bytes'] = $getid3->info['avdataend'] - $getid3->info['avdataoffset'];608 $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);609 $getid3->info['flac']['compression_ratio'] = $getid3->info['flac']['compressed_audio_bytes'] / $getid3->info['flac']['uncompressed_audio_bytes'];610 }612 // set md5_data_source - built into flac 0.5+613 if (isset($getid3->info['flac']['STREAMINFO']['audio_signature'])) {615 if ($getid3->info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {616 $getid3->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');618 } else {620 $getid3->info['md5_data_source'] = '';621 $md5 = $getid3->info['flac']['STREAMINFO']['audio_signature'];622 for ($i = 0; $i < strlen($md5); $i++) {623 $getid3->info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT);624 }625 if (!preg_match('/^[0-9a-f]{32}$/', $getid3->info['md5_data_source'])) {626 unset($getid3->info['md5_data_source']);627 }629 }631 }633 $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample'];634 if ($getid3->info['audio']['bits_per_sample'] == 8) {635 // special case636 // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value637 // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed638 $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');639 }640 if (!empty($getid3->info['ogg']['vendor'])) {641 $getid3->info['audio']['encoder'] = $getid3->info['ogg']['vendor'];642 }644 return true;645 }649 private function FLACparseSTREAMINFO($meta_data_block_data) {651 $getid3 = $this->getid3;653 getid3_lib::ReadSequence('BigEndian2Int', $getid3->info['flac']['STREAMINFO'], $meta_data_block_data, 0,654 array (655 'min_block_size' => 2,656 'max_block_size' => 2,657 'min_frame_size' => 3,658 'max_frame_size' => 3659 )660 );662 $sample_rate_channels_sample_bits_stream_samples = getid3_lib::BigEndian2Bin(substr($meta_data_block_data, 10, 8));664 $getid3->info['flac']['STREAMINFO']['sample_rate'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 0, 20));665 $getid3->info['flac']['STREAMINFO']['channels'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 20, 3)) + 1;666 $getid3->info['flac']['STREAMINFO']['bits_per_sample'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 23, 5)) + 1;667 $getid3->info['flac']['STREAMINFO']['samples_stream'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 28, 36)); // bindec() returns float in case of int overrun668 $getid3->info['flac']['STREAMINFO']['audio_signature'] = substr($meta_data_block_data, 18, 16);670 if (!empty($getid3->info['flac']['STREAMINFO']['sample_rate'])) {672 $getid3->info['audio']['bitrate_mode'] = 'vbr';673 $getid3->info['audio']['sample_rate'] = $getid3->info['flac']['STREAMINFO']['sample_rate'];674 $getid3->info['audio']['channels'] = $getid3->info['flac']['STREAMINFO']['channels'];675 $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample'];676 $getid3->info['playtime_seconds'] = $getid3->info['flac']['STREAMINFO']['samples_stream'] / $getid3->info['flac']['STREAMINFO']['sample_rate'];677 $getid3->info['audio']['bitrate'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds'];679 } else {681 throw new getid3_exception('Corrupt METAdata block: STREAMINFO');682 }684 unset($getid3->info['flac']['STREAMINFO']['raw']);686 return true;687 }691 private function FLACparseAPPLICATION($meta_data_block_data) {693 $getid3 = $this->getid3;695 $application_id = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 0, 4));697 $getid3->info['flac']['APPLICATION'][$application_id]['name'] = getid3_xiph::FLACapplicationIDLookup($application_id);698 $getid3->info['flac']['APPLICATION'][$application_id]['data'] = substr($meta_data_block_data, 4);700 unset($getid3->info['flac']['APPLICATION']['raw']);702 return true;703 }707 private function FLACparseSEEKTABLE($meta_data_block_data) {709 $getid3 = $this->getid3;711 $offset = 0;712 $meta_data_block_length = strlen($meta_data_block_data);713 while ($offset < $meta_data_block_length) {714 $sample_number_string = substr($meta_data_block_data, $offset, 8);715 $offset += 8;716 if ($sample_number_string == "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") {718 // placeholder point719 @$getid3->info['flac']['SEEKTABLE']['placeholders']++;720 $offset += 10;722 } else {724 $sample_number = getid3_lib::BigEndian2Int($sample_number_string);726 $getid3->info['flac']['SEEKTABLE'][$sample_number]['offset'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));727 $offset += 8;729 $getid3->info['flac']['SEEKTABLE'][$sample_number]['samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 2));730 $offset += 2;732 }733 }735 unset($getid3->info['flac']['SEEKTABLE']['raw']);737 return true;738 }742 private function FLACparseCUESHEET($meta_data_block_data) {744 $getid3 = $this->getid3;746 $getid3->info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($meta_data_block_data, 0, 128), "\0");747 $getid3->info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 128, 8));748 $getid3->info['flac']['CUESHEET']['flags']['is_cd'] = (bool)(getid3_lib::BigEndian2Int($meta_data_block_data[136]) & 0x80);749 $getid3->info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int($meta_data_block_data[395]);751 $offset = 396;753 for ($track = 0; $track < $getid3->info['flac']['CUESHEET']['number_tracks']; $track++) {755 $track_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));756 $offset += 8;758 $track_number = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});760 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['sample_offset'] = $track_sample_offset;761 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['isrc'] = substr($meta_data_block_data, $offset, 12);762 $offset += 12;764 $track_flags_raw = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});765 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['is_audio'] = (bool)($track_flags_raw & 0x80);766 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['pre_emphasis'] = (bool)($track_flags_raw & 0x40);768 $offset += 13; // reserved770 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points'] = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});772 for ($index = 0; $index < $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points']; $index++) {774 $index_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));775 $offset += 8;777 $index_number = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});778 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['indexes'][$index_number] = $index_sample_offset;780 $offset += 3; // reserved781 }782 }784 unset($getid3->info['flac']['CUESHEET']['raw']);786 return true;787 }791 private function FLACparsePICTURE($meta_data_block_data) {793 $getid3 = $this->getid3;795 $picture = &$getid3->info['flac']['PICTURE'][sizeof($getid3->info['flac']['PICTURE']) - 1];797 $offset = 0;799 $picture['type'] = $this->FLACpictureTypeLookup(getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)));800 $offset += 4;802 $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));803 $offset += 4;805 $picture['mime_type'] = substr($meta_data_block_data, $offset, $length);806 $offset += $length;808 $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));809 $offset += 4;811 $picture['description'] = substr($meta_data_block_data, $offset, $length);812 $offset += $length;814 $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));815 $offset += 4;817 $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));818 $offset += 4;820 $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));821 $offset += 4;823 $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));824 $offset += 4;826 $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));827 $offset += 4;829 $picture['image_data'] = substr($meta_data_block_data, $offset, $length);830 $offset += $length;832 unset($getid3->info['flac']['PICTURE']['raw']);834 return true;835 }839 public static function SpeexBandModeLookup($mode) {841 static $lookup = array (842 0 => 'narrow',843 1 => 'wide',844 2 => 'ultra-wide'845 );846 return (isset($lookup[$mode]) ? $lookup[$mode] : null);847 }851 public static function OggPageSegmentLength($ogg_info_array, $segment_number=1) {853 for ($i = 0; $i < $segment_number; $i++) {854 $segment_length = 0;855 foreach ($ogg_info_array['segment_table'] as $key => $value) {856 $segment_length += $value;857 if ($value < 255) {858 break;859 }860 }861 }862 return $segment_length;863 }867 public static function GetQualityFromNominalBitrate($nominal_bitrate) {869 // decrease precision870 $nominal_bitrate = $nominal_bitrate / 1000;872 if ($nominal_bitrate < 128) {873 // q-1 to q4874 $qval = ($nominal_bitrate - 64) / 16;875 } elseif ($nominal_bitrate < 256) {876 // q4 to q8877 $qval = $nominal_bitrate / 32;878 } elseif ($nominal_bitrate < 320) {879 // q8 to q9880 $qval = ($nominal_bitrate + 256) / 64;881 } else {882 // q9 to q10883 $qval = ($nominal_bitrate + 1300) / 180;884 }885 return round($qval, 1); // 5 or 4.9886 }890 public static function FLACmetaBlockTypeLookup($block_type) {892 static $lookup = array (893 0 => 'STREAMINFO',894 1 => 'PADDING',895 2 => 'APPLICATION',896 3 => 'SEEKTABLE',897 4 => 'VORBIS_COMMENT',898 5 => 'CUESHEET',899 6 => 'PICTURE'900 );901 return (isset($lookup[$block_type]) ? $lookup[$block_type] : 'reserved');902 }906 public static function FLACapplicationIDLookup($application_id) {908 // http://flac.sourceforge.net/id.html910 static $lookup = array (911 0x46746F6C => 'flac-tools', // 'Ftol'912 0x46746F6C => 'Sound Font FLAC', // 'SFFL'913 0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // 'peem'914 0x786D6364 => 'xmcd'916 );917 return (isset($lookup[$application_id]) ? $lookup[$application_id] : 'reserved');918 }921 public static function FLACpictureTypeLookup($type_id) {923 static $lookup = array (925 0 => 'Other',926 1 => "32x32 pixels 'file icon' (PNG only)",927 2 => 'Other file icon',928 3 => 'Cover (front)',929 4 => 'Cover (back)',930 5 => 'Leaflet page',931 6 => 'Media (e.g. label side of CD)',932 7 => 'Lead artist/lead performer/soloist',933 8 => 'Artist/performer',934 9 => 'Conductor',935 10 => 'Band/Orchestra',936 11 => 'Composer',937 12 => 'Lyricist/text writer',938 13 => 'Recording Location',939 14 => 'During recording',940 15 => 'During performance',941 16 => 'Movie/video screen capture',942 17 => 'A bright coloured fish',943 18 => 'Illustration',944 19 => 'Band/artist logotype',945 20 => 'Publisher/Studio logotype'946 );947 return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');948 }950 }952 ?>