Passed
Branch master (f2d2e3)
by Michael
18:45
created

getid3_xiph   F

Complexity

Total Complexity 116

Size/Duplication

Total Lines 921
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 921
rs 1.679
wmc 116

17 Methods

Rating   Name   Duplication   Size   Complexity  
A GetQualityFromNominalBitrate() 0 19 4
A FLACparseSTREAMINFO() 0 38 2
F ParseVorbisCommentsFilepointer() 0 183 25
A FLACparseSEEKTABLE() 0 31 3
F FLACparseMETAdata() 0 116 22
A OggPageSegmentLength() 0 12 4
A Analyze() 0 18 4
F ParseOgg() 0 211 30
A SpeexBandModeLookup() 0 8 2
A ParseFLAC() 0 12 1
A FLACpictureTypeLookup() 0 27 2
A FLACparseAPPLICATION() 0 12 1
A FLACparsePICTURE() 0 44 1
B ParseOggPageHeader() 0 53 8
A FLACmetaBlockTypeLookup() 0 12 2
A FLACparseCUESHEET() 0 45 3
A FLACapplicationIDLookup() 0 12 2

How to fix   Complexity   

Complex Class

Complex classes like getid3_xiph often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use getid3_xiph, and based on these observations, apply Extract Interface, too.

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: module.audio.xiph.php,v 1.5 2006/12/03 21:12:43 ah Exp $
24
25
        
26
        
27
class 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;
0 ignored issues
show
Unused Code introduced by
The assignment to $file_data_offset is dead and can be removed.
Loading history...
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
0 ignored issues
show
Comprehensibility Best Practice introduced by
$ogg_header was never initialized. Although not strictly required by PHP, it is generally a good practice to add $ogg_header = array(); before regardless.
Loading history...
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $segment_length does not seem to be defined for all execution paths leading up to this point.
Loading history...
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
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...