source: extensions/charlies_content/getid3/getid3/module.audio.xiph.php @ 3318

Revision 3318, 41.1 KB checked in by vdigital, 11 years ago (diff)

+ Add Charlies' content to depository

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Author Date Id Revision
Line 
1<?php
2// +----------------------------------------------------------------------+
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$
24
25       
26       
27class getid3_xiph extends getid3_handler
28{
29   
30    public function Analyze() {
31       
32        $getid3 = $this->getid3;
33       
34        if ($getid3->option_tags_images) {       
35            $getid3->include_module('lib.image_size');
36        }
37       
38        fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);
39       
40        $magic = fread($getid3->fp, 4);
41       
42        if ($magic == 'OggS') {
43            return $this->ParseOgg();
44        }
45       
46        if ($magic == 'fLaC') {
47            return $this->ParseFLAC();
48        }
49       
50    }
51   
52   
53   
54    private function ParseOgg() {
55       
56        $getid3 = $this->getid3;
57       
58        fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);
59       
60        $getid3->info['audio'] = $getid3->info['ogg'] = array ();
61        $info_ogg   = &$getid3->info['ogg'];                           
62        $info_audio = &$getid3->info['audio'];
63       
64        $getid3->info['fileformat'] = 'ogg';
65
66
67        //// Page 1 - Stream Header
68
69        $ogg_page_info = $this->ParseOggPageHeader();
70        $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
71
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        }
75
76        $file_data = fread($getid3->fp, $ogg_page_info['page_length']);
77        $file_data_offset = 0;
78
79
80        // OggFLAC
81        if (substr($file_data, 0, 4) == 'fLaC') {
82
83            $info_audio['dataformat']   = 'flac';
84            $info_audio['bitrate_mode'] = 'vbr';
85            $info_audio['lossless']     = true;
86
87        } 
88   
89   
90        // Ogg Vorbis
91        elseif (substr($file_data, 1, 6) == 'vorbis') {
92
93            $info_audio['dataformat'] = 'vorbis';
94            $info_audio['lossless']   = false;
95
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'
98           
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'      => 4
107                )
108            );
109                                                                                                                     
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;
114           
115            $info_audio['channels']       = $info_ogg['numberofchannels'];
116            $info_audio['sample_rate']    = $info_ogg['samplerate'];
117
118            $info_audio['bitrate_mode'] = 'vbr';     // overridden if actually abr
119
120            if ($info_ogg['bitrate_max'] == 0xFFFFFFFF) {
121                unset($info_ogg['bitrate_max']);
122                $info_audio['bitrate_mode'] = 'abr';
123            }
124           
125            if ($info_ogg['bitrate_nominal'] == 0xFFFFFFFF) {
126                unset($info_ogg['bitrate_nominal']);
127            }
128           
129            if ($info_ogg['bitrate_min'] == 0xFFFFFFFF) {
130                unset($info_ogg['bitrate_min']);
131                $info_audio['bitrate_mode'] = 'abr';
132            }
133        }
134   
135
136        // Speex
137        elseif (substr($file_data, 0, 8) == 'Speex   ') {
138
139            // http://www.speex.org/manual/node10.html
140
141            $info_audio['dataformat']   = 'speex';
142            $getid3->info['mime_type']  = 'audio/speex';
143            $info_audio['bitrate_mode'] = 'abr';
144            $info_audio['lossless']     = false;
145
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,            // string                 
150                    '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'              => 4
163                )
164            );
165               
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']);
171
172            $info_audio['sample_rate'] = $getid3->info['speex']['sample_rate'];
173            $info_audio['channels']    = $getid3->info['speex']['channels'];
174           
175            if ($getid3->info['speex']['vbr']) {
176                $info_audio['bitrate_mode'] = 'vbr';
177            }
178        }
179
180        // Unsupported Ogg file
181        else {
182
183            throw new getid3_exception('Expecting either "Speex   " or "vorbis" identifier strings, found neither');
184        }
185
186
187        //// Page 2 - Comment Header
188
189        $ogg_page_info = $this->ParseOggPageHeader();
190        $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
191
192        switch ($info_audio['dataformat']) {
193
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;
200
201            case 'flac':
202                if (!$this->FLACparseMETAdata()) {
203                    throw new getid3_exception('Failed to parse FLAC headers');
204                }
205                break;
206
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        }
212
213
214        //// Last Page - Number of Samples
215
216        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));
218       
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        }
226
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        }
237
238        if (isset($info_ogg['vendor'])) {
239            $info_audio['encoder'] = preg_replace('/^Encoded with /', '', $info_ogg['vendor']);
240
241            // Vorbis only
242            if ($info_audio['dataformat'] == 'vorbis') {
243
244                // Vorbis 1.0 starts with Xiph.Org
245                if  (preg_match('/^Xiph.Org/', $info_audio['encoder'])) {
246
247                    if ($info_audio['bitrate_mode'] == 'abr') {
248
249                        // Set -b 128 on abr files
250                        $info_audio['encoder_options'] = '-b '.round($info_ogg['bitrate_nominal'] / 1000);
251
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 files
254                        $info_audio['encoder_options'] = '-q '.getid3_xiph::GetQualityFromNominalBitrate($info_ogg['bitrate_nominal']);
255                    }
256                }
257
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        }
263
264        return true;
265    }
266
267
268
269    private function ParseOggPageHeader() {
270       
271        $getid3 = $this->getid3;
272       
273        // http://xiph.org/ogg/vorbis/doc/framing.html
274        $ogg_header['page_start_offset'] = ftell($getid3->fp);      // where we started from in the file
275       
276        $file_data = fread($getid3->fp, getid3::FREAD_BUFFER_SIZE);
277        $file_data_offset = 0;
278       
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 here
282                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 fail
287                    return false;
288                }
289            }
290        }
291       
292        $file_data_offset += 3; // page, delimited by 'OggS'
293       
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'    => 1
303            )
304        );
305       
306        $file_data_offset += 23;
307
308        $ogg_header['flags']['fresh'] = (bool)($ogg_header['flags_raw'] & 0x01); // fresh packet
309        $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)
311
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);
320
321        return $ogg_header;
322    }
323
324
325   
326    private function ParseVorbisCommentsFilepointer() {
327       
328        $getid3 = $this->getid3;
329
330        $original_offset      = ftell($getid3->fp);
331        $comment_start_offset = $original_offset;
332        $comment_data_offset  = 0;
333        $vorbis_comment_page  = 1;
334
335        switch ($getid3->info['audio']['dataformat']) {
336           
337            case 'vorbis':
338                $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset'];  // Second Ogg page, after header block
339                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;
344               
345
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;
350               
351
352            case 'speex':
353                $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset'];  // Second Ogg page, after header block
354                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;
358               
359
360            default:
361                return false;
362        }
363
364        $vendor_size = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
365        $comment_data_offset += 4;
366
367        $getid3->info['ogg']['vendor'] = substr($comment_data, $comment_data_offset, $vendor_size);
368        $comment_data_offset += $vendor_size;
369
370        $comments_count = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
371        $comment_data_offset += 4;
372       
373        $getid3->info['avdataoffset'] = $comment_start_offset + $comment_data_offset;
374
375        for ($i = 0; $i < $comments_count; $i++) {
376
377            $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] = $comment_start_offset + $comment_data_offset;
378
379            if (ftell($getid3->fp) < ($getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + 4)) {
380                $vorbis_comment_page++;
381
382                $ogg_page_info = $this->ParseOggPageHeader();
383                $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
384
385                // First, save what we haven't read yet
386                $as_yet_unused_data = substr($comment_data, $comment_data_offset);
387
388                // Then take that data off the end
389                $comment_data = substr($comment_data, 0, $comment_data_offset);
390
391                // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
392                $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']);
394
395                // Finally, stick the unused data back on the end
396                $comment_data .= $as_yet_unused_data;
397
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));
401
402            // replace avdataoffset with position just after the last vorbiscomment
403            $getid3->info['avdataoffset'] = $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + $getid3->info['ogg']['comments_raw'][$i]['size'] + 4;
404
405            $comment_data_offset += 4;
406            while ((strlen($comment_data) - $comment_data_offset) < $getid3->info['ogg']['comments_raw'][$i]['size']) {
407           
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                }
411
412                $vorbis_comment_page++;
413
414                $ogg_page_info = $this->ParseOggPageHeader();
415                $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
416
417                // First, save what we haven't read yet
418                $as_yet_unused_data = substr($comment_data, $comment_data_offset);
419
420                // Then take that data off the end
421                $comment_data     = substr($comment_data, 0, $comment_data_offset);
422
423                // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
424                $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']);
426
427                // Finally, stick the unused data back on the end
428                $comment_data .= $as_yet_unused_data;
429
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));
432
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'];
437
438            if (!$comment_string) {
439
440                // no comment?
441                $getid3->warning('Blank Ogg comment ['.$i.']');
442
443            } elseif (strstr($comment_string, '=')) {
444
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']);
449
450                $getid3->info['ogg']['comments'][strtolower($getid3->info['ogg']['comments_raw'][$i]['key'])][] = $getid3->info['ogg']['comments_raw'][$i]['value'];
451
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                }
456               
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                }
461               
462
463            } else {
464
465                $getid3->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$comment_string);
466            }
467        }
468
469
470        // Replay Gain Adjustment
471        // 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;
480
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;
486
487                    case 'replaygain_album_peak':
488                        $getid3->info['replay_gain']['album']['peak'] = (float)$commentvalue[0];
489                        unset($getid3->info['ogg']['comments'][$index]);
490                        break;
491
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;
497                       
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        }
505
506        fseek($getid3->fp, $original_offset, SEEK_SET);
507
508        return true;
509    }
510
511
512
513    private function ParseFLAC() {
514       
515        $getid3 = $this->getid3;
516       
517        // http://flac.sourceforge.net/format.html
518
519        $getid3->info['fileformat']            = 'flac';
520        $getid3->info['audio']['dataformat']   = 'flac';
521        $getid3->info['audio']['bitrate_mode'] = 'vbr';
522        $getid3->info['audio']['lossless']     = true;
523
524        return $this->FLACparseMETAdata();
525    }
526
527
528
529    private function FLACparseMETAdata() {
530       
531        $getid3 = $this->getid3;
532
533        do {
534           
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);
541
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            }
545
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);
555
556            switch ($meta_data_block_type_text) {
557
558                case 'STREAMINFO':
559                    if (!$this->FLACparseSTREAMINFO($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
560                        return false;
561                    }
562                    break;
563
564                case 'PADDING':
565                    // ignore
566                    break;
567
568                case 'APPLICATION':
569                    if (!$this->FLACparseAPPLICATION($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
570                        return false;
571                    }
572                    break;
573
574                case 'SEEKTABLE':
575                    if (!$this->FLACparseSEEKTABLE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
576                        return false;
577                    }
578                    break;
579
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;
586
587                case 'CUESHEET':
588                    if (!$this->FLACparseCUESHEET($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
589                        return false;
590                    }
591                    break;
592                   
593                case 'PICTURE':
594                    if (!$this->FLACparsePICTURE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
595                        return false;
596                    }
597                    break;
598
599                default:
600                    $getid3->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset);
601            }
602
603        } while ($meta_data_last_block_flag === false);
604
605
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        }
611
612        // set md5_data_source - built into flac 0.5+
613        if (isset($getid3->info['flac']['STREAMINFO']['audio_signature'])) {
614
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)');
617
618            } else {
619
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                }
628
629            }
630
631        }
632
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 case
636            // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
637            // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
638            $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        }
643
644        return true;
645    }
646
647
648
649    private function FLACparseSTREAMINFO($meta_data_block_data) {
650       
651        $getid3 = $this->getid3;
652       
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' => 3
659            )
660        );
661
662        $sample_rate_channels_sample_bits_stream_samples = getid3_lib::BigEndian2Bin(substr($meta_data_block_data, 10, 8));
663       
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 overrun
668        $getid3->info['flac']['STREAMINFO']['audio_signature'] = substr($meta_data_block_data, 18, 16);
669
670        if (!empty($getid3->info['flac']['STREAMINFO']['sample_rate'])) {
671
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'];
678
679        } else {
680
681            throw new getid3_exception('Corrupt METAdata block: STREAMINFO');
682        }
683       
684        unset($getid3->info['flac']['STREAMINFO']['raw']);
685
686        return true;
687    }
688
689
690
691    private function FLACparseAPPLICATION($meta_data_block_data) {
692       
693        $getid3 = $this->getid3;
694       
695        $application_id = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 0, 4));
696       
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);
699       
700        unset($getid3->info['flac']['APPLICATION']['raw']);
701
702        return true;
703    }
704
705
706
707    private function FLACparseSEEKTABLE($meta_data_block_data) {
708       
709        $getid3 = $this->getid3;
710       
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") {
717
718                // placeholder point
719                @$getid3->info['flac']['SEEKTABLE']['placeholders']++;
720                $offset += 10;
721
722            } else {
723
724                $sample_number = getid3_lib::BigEndian2Int($sample_number_string);
725               
726                $getid3->info['flac']['SEEKTABLE'][$sample_number]['offset']  = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
727                $offset += 8;
728               
729                $getid3->info['flac']['SEEKTABLE'][$sample_number]['samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 2));
730                $offset += 2;
731
732            }
733        }
734       
735        unset($getid3->info['flac']['SEEKTABLE']['raw']);
736       
737        return true;
738    }
739
740
741
742    private function FLACparseCUESHEET($meta_data_block_data) {
743       
744        $getid3 = $this->getid3;
745       
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]);
750
751        $offset = 396;
752       
753        for ($track = 0; $track < $getid3->info['flac']['CUESHEET']['number_tracks']; $track++) {
754       
755            $track_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
756            $offset += 8;
757
758            $track_number        = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
759
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;
763
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);
767
768            $offset += 13; // reserved
769
770            $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points'] = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
771
772            for ($index = 0; $index < $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points']; $index++) {
773               
774                $index_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
775                $offset += 8;
776               
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;
779               
780                $offset += 3; // reserved
781            }
782        }
783       
784        unset($getid3->info['flac']['CUESHEET']['raw']);
785       
786        return true;
787    }
788   
789   
790   
791    private function FLACparsePICTURE($meta_data_block_data) {
792       
793        $getid3 = $this->getid3;
794       
795        $picture = &$getid3->info['flac']['PICTURE'][sizeof($getid3->info['flac']['PICTURE']) - 1];
796       
797        $offset = 0;
798       
799        $picture['type'] = $this->FLACpictureTypeLookup(getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)));
800        $offset += 4;
801       
802        $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
803        $offset += 4;
804       
805        $picture['mime_type'] = substr($meta_data_block_data, $offset, $length);
806        $offset += $length;
807       
808        $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
809        $offset += 4;
810       
811        $picture['description'] = substr($meta_data_block_data, $offset, $length);
812        $offset += $length;
813       
814        $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
815        $offset += 4;
816       
817        $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
818        $offset += 4;
819       
820        $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
821        $offset += 4;
822       
823        $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
824        $offset += 4;
825       
826        $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
827        $offset += 4;
828       
829        $picture['image_data'] = substr($meta_data_block_data, $offset, $length);
830        $offset += $length;
831       
832        unset($getid3->info['flac']['PICTURE']['raw']);
833       
834        return true;
835    }
836   
837   
838   
839    public static function SpeexBandModeLookup($mode) {
840       
841        static $lookup = array (
842            0 => 'narrow',
843            1 => 'wide',
844            2 => 'ultra-wide'
845        );
846        return (isset($lookup[$mode]) ? $lookup[$mode] : null);
847    }
848
849
850
851    public static function OggPageSegmentLength($ogg_info_array, $segment_number=1) {
852       
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    }
864
865
866
867    public static function GetQualityFromNominalBitrate($nominal_bitrate) {
868
869        // decrease precision
870        $nominal_bitrate = $nominal_bitrate / 1000;
871
872        if ($nominal_bitrate < 128) {
873            // q-1 to q4
874            $qval = ($nominal_bitrate - 64) / 16;
875        } elseif ($nominal_bitrate < 256) {
876            // q4 to q8
877            $qval = $nominal_bitrate / 32;
878        } elseif ($nominal_bitrate < 320) {
879            // q8 to q9
880            $qval = ($nominal_bitrate + 256) / 64;
881        } else {
882            // q9 to q10
883            $qval = ($nominal_bitrate + 1300) / 180;
884        }
885        return round($qval, 1); // 5 or 4.9
886    }
887   
888   
889   
890    public static function FLACmetaBlockTypeLookup($block_type) {
891   
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    }
903
904
905
906    public static function FLACapplicationIDLookup($application_id) {
907       
908        // http://flac.sourceforge.net/id.html
909       
910        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'
915           
916        );
917        return (isset($lookup[$application_id]) ? $lookup[$application_id] : 'reserved');
918    }
919
920
921    public static function FLACpictureTypeLookup($type_id) {
922       
923        static $lookup = array (
924           
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    }
949
950}
951
952?>
Note: See TracBrowser for help on using the repository browser.