getid3_xiph::SpeexBandModeLookup()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 8
rs 10
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
class getid3_xiph extends getid3_handler
26
{
27
28
    public function Analyze()
29
    {
30
        $getid3 = $this->getid3;
31
32
        if ($getid3->option_tags_images) {
33
            $getid3->include_module('lib.image_size');
34
        }
35
36
        fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);
37
38
        $magic = fread($getid3->fp, 4);
39
40
        if ('OggS' == $magic) {
41
            return $this->ParseOgg();
42
        }
43
44
        if ('fLaC' == $magic) {
45
            return $this->ParseFLAC();
46
        }
47
    }
48
49
    private function ParseOgg()
50
    {
51
        $getid3 = $this->getid3;
52
53
        fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);
54
55
        $getid3->info['audio'] = $getid3->info['ogg'] = [];
56
        $info_ogg              = &$getid3->info['ogg'];
57
        $info_audio            = &$getid3->info['audio'];
58
59
        $getid3->info['fileformat'] = 'ogg';
60
61
        //// Page 1 - Stream Header
62
63
        $ogg_page_info                                        = $this->ParseOggPageHeader();
64
        $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
65
66
        if (ftell($getid3->fp) >= getid3::FREAD_BUFFER_SIZE) {
67
            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?)');
68
        }
69
70
        $file_data        = fread($getid3->fp, $ogg_page_info['page_length']);
71
        $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...
72
73
        // OggFLAC
74
        if ('fLaC' == substr($file_data, 0, 4)) {
75
            $info_audio['dataformat']   = 'flac';
76
            $info_audio['bitrate_mode'] = 'vbr';
77
            $info_audio['lossless']     = true;
78
        } // Ogg Vorbis
79
        elseif ('vorbis' == substr($file_data, 1, 6)) {
80
            $info_audio['dataformat'] = 'vorbis';
81
            $info_audio['lossless']   = false;
82
83
            $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int($file_data[0]);
84
            $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis'
85
86
            getid3_lib::ReadSequence(
87
                'LittleEndian2Int',
88
                $info_ogg,
89
                $file_data,
90
                7,
91
                [
92
                    'bitstreamversion' => 4,
93
                    'numberofchannels' => 1,
94
                    'samplerate'       => 4,
95
                    'bitrate_max'      => 4,
96
                    'bitrate_nominal'  => 4,
97
                    'bitrate_min'      => 4
98
                ]
99
            );
100
101
            $n28                         = getid3_lib::LittleEndian2Int($file_data{28});
102
            $info_ogg['blocksize_small'] = pow(2, $n28 & 0x0F);
103
            $info_ogg['blocksize_large'] = pow(2, ($n28 & 0xF0) >> 4);
104
            $info_ogg['stop_bit']        = $n28;
105
106
            $info_audio['channels']    = $info_ogg['numberofchannels'];
107
            $info_audio['sample_rate'] = $info_ogg['samplerate'];
108
109
            $info_audio['bitrate_mode'] = 'vbr';     // overridden if actually abr
110
111
            if (0xFFFFFFFF == $info_ogg['bitrate_max']) {
112
                unset($info_ogg['bitrate_max']);
113
                $info_audio['bitrate_mode'] = 'abr';
114
            }
115
116
            if (0xFFFFFFFF == $info_ogg['bitrate_nominal']) {
117
                unset($info_ogg['bitrate_nominal']);
118
            }
119
120
            if (0xFFFFFFFF == $info_ogg['bitrate_min']) {
121
                unset($info_ogg['bitrate_min']);
122
                $info_audio['bitrate_mode'] = 'abr';
123
            }
124
        } // Speex
125
        elseif ('Speex   ' == substr($file_data, 0, 8)) {
126
            // http://www.speex.org/manual/node10.html
127
128
            $info_audio['dataformat']   = 'speex';
129
            $getid3->info['mime_type']  = 'audio/speex';
130
            $info_audio['bitrate_mode'] = 'abr';
131
            $info_audio['lossless']     = false;
132
133
            getid3_lib::ReadSequence(
134
                'LittleEndian2Int',
135
                $info_ogg['pageheader'][$ogg_page_info['page_seqno']],
136
                $file_data,
137
                0,
138
                [
139
                    'speex_string'           => -8,        // hard-coded to 'Speex   '
140
                    'speex_version'          => -20,        // string
141
                    'speex_version_id'       => 4,
142
                    'header_size'            => 4,
143
                    'rate'                   => 4,
144
                    'mode'                   => 4,
145
                    'mode_bitstream_version' => 4,
146
                    'nb_channels'            => 4,
147
                    'bitrate'                => 4,
148
                    'framesize'              => 4,
149
                    'vbr'                    => 4,
150
                    'frames_per_packet'      => 4,
151
                    'extra_headers'          => 4,
152
                    'reserved1'              => 4,
153
                    'reserved2'              => 4
154
                ]
155
            );
156
157
            $getid3->info['speex']['speex_version'] = trim($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['speex_version']);
158
            $getid3->info['speex']['sample_rate']   = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['rate'];
159
            $getid3->info['speex']['channels']      = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['nb_channels'];
160
            $getid3->info['speex']['vbr']           = (bool)$info_ogg['pageheader'][$ogg_page_info['page_seqno']]['vbr'];
161
            $getid3->info['speex']['band_type']     = getid3_xiph::SpeexBandModeLookup($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['mode']);
162
163
            $info_audio['sample_rate'] = $getid3->info['speex']['sample_rate'];
164
            $info_audio['channels']    = $getid3->info['speex']['channels'];
165
166
            if ($getid3->info['speex']['vbr']) {
167
                $info_audio['bitrate_mode'] = 'vbr';
168
            }
169
        } // Unsupported Ogg file
170
        else {
171
            throw new getid3_exception('Expecting either "Speex   " or "vorbis" identifier strings, found neither');
172
        }
173
174
        //// Page 2 - Comment Header
175
176
        $ogg_page_info                                        = $this->ParseOggPageHeader();
177
        $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
178
179
        switch ($info_audio['dataformat']) {
180
            case 'vorbis':
181
                $file_data                                                           = fread($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length']);
182
                $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($file_data, 0, 1));
183
                $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis'
184
                $this->ParseVorbisCommentsFilepointer();
185
                break;
186
187
            case 'flac':
188
                if (!$this->FLACparseMETAdata()) {
189
                    throw new getid3_exception('Failed to parse FLAC headers');
190
                }
191
                break;
192
193
            case 'speex':
194
                fseek($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length'], SEEK_CUR);
195
                $this->ParseVorbisCommentsFilepointer();
196
                break;
197
        }
198
199
        //// Last Page - Number of Samples
200
201
        fseek($getid3->fp, max($getid3->info['avdataend'] - getid3::FREAD_BUFFER_SIZE, 0), SEEK_SET);
202
        $last_chunk_of_ogg = strrev(fread($getid3->fp, getid3::FREAD_BUFFER_SIZE));
203
204
        if ($last_OggS_postion = strpos($last_chunk_of_ogg, 'SggO')) {
205
            fseek($getid3->fp, $getid3->info['avdataend'] - ($last_OggS_postion + strlen('SggO')), SEEK_SET);
206
            $getid3->info['avdataend']     = ftell($getid3->fp);
207
            $info_ogg['pageheader']['eos'] = $this->ParseOggPageHeader();
208
            $info_ogg['samples']           = $info_ogg['pageheader']['eos']['pcm_abs_position'];
209
            $info_ogg['bitrate_average']   = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / ($info_ogg['samples'] / $info_audio['sample_rate']);
210
        }
211
212
        if (!empty($info_ogg['bitrate_average'])) {
213
            $info_audio['bitrate'] = $info_ogg['bitrate_average'];
214
        } elseif (!empty($info_ogg['bitrate_nominal'])) {
215
            $info_audio['bitrate'] = $info_ogg['bitrate_nominal'];
216
        } elseif (!empty($info_ogg['bitrate_min']) && !empty($info_ogg['bitrate_max'])) {
217
            $info_audio['bitrate'] = ($info_ogg['bitrate_min'] + $info_ogg['bitrate_max']) / 2;
218
        }
219
        if (isset($info_audio['bitrate']) && !isset($getid3->info['playtime_seconds'])) {
220
            $getid3->info['playtime_seconds'] = (float)((($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $info_audio['bitrate']);
221
        }
222
223
        if (isset($info_ogg['vendor'])) {
224
            $info_audio['encoder'] = preg_replace('/^Encoded with /', '', $info_ogg['vendor']);
225
226
            // Vorbis only
227
            if ('vorbis' == $info_audio['dataformat']) {
228
                // Vorbis 1.0 starts with Xiph.Org
229
                if (preg_match('/^Xiph.Org/', $info_audio['encoder'])) {
230
                    if ('abr' == $info_audio['bitrate_mode']) {
231
                        // Set -b 128 on abr files
232
                        $info_audio['encoder_options'] = '-b ' . round($info_ogg['bitrate_nominal'] / 1000);
233
                    } elseif (('vbr' == $info_audio['bitrate_mode']) && (2 == $info_audio['channels']) && ($info_audio['sample_rate'] >= 44100) && ($info_audio['sample_rate'] <= 48000)) {
234
                        // Set -q N on vbr files
235
                        $info_audio['encoder_options'] = '-q ' . getid3_xiph::GetQualityFromNominalBitrate($info_ogg['bitrate_nominal']);
236
                    }
237
                }
238
239
                if (empty($info_audio['encoder_options']) && !empty($info_ogg['bitrate_nominal'])) {
240
                    $info_audio['encoder_options'] = 'Nominal bitrate: ' . intval(round($info_ogg['bitrate_nominal'] / 1000)) . 'kbps';
241
                }
242
            }
243
        }
244
245
        return true;
246
    }
247
248
    private function ParseOggPageHeader()
249
    {
250
        $getid3 = $this->getid3;
251
252
        // http://xiph.org/ogg/vorbis/doc/framing.html
253
        $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...
254
255
        $file_data        = fread($getid3->fp, getid3::FREAD_BUFFER_SIZE);
256
        $file_data_offset = 0;
257
258
        while (('OggS' != substr($file_data, $file_data_offset++, 4))) {
259
            if ((ftell($getid3->fp) - $ogg_header['page_start_offset']) >= getid3::FREAD_BUFFER_SIZE) {
260
                // should be found before here
261
                return false;
262
            }
263
            if ((($file_data_offset + 28) > strlen($file_data)) || (strlen($file_data) < 28)) {
264
                if (feof($getid3->fp) || (false === ($file_data .= fread($getid3->fp, getid3::FREAD_BUFFER_SIZE)))) {
265
                    // get some more data, unless eof, in which case fail
266
                    return false;
267
                }
268
            }
269
        }
270
271
        $file_data_offset += 3; // page, delimited by 'OggS'
272
273
        getid3_lib::ReadSequence(
274
            'LittleEndian2Int',
275
            $ogg_header,
276
            $file_data,
277
            $file_data_offset,
278
            [
279
                'stream_structver' => 1,
280
                'flags_raw'        => 1,
281
                'pcm_abs_position' => 8,
282
                'stream_serialno'  => 4,
283
                'page_seqno'       => 4,
284
                'page_checksum'    => 4,
285
                'page_segments'    => 1
286
            ]
287
        );
288
289
        $file_data_offset += 23;
290
291
        $ogg_header['flags']['fresh'] = (bool)($ogg_header['flags_raw'] & 0x01); // fresh packet
292
        $ogg_header['flags']['bos']   = (bool)($ogg_header['flags_raw'] & 0x02); // first page of logical bitstream (bos)
293
        $ogg_header['flags']['eos']   = (bool)($ogg_header['flags_raw'] & 0x04); // last page of logical bitstream (eos)
294
295
        $ogg_header['page_length'] = 0;
296
        for ($i = 0; $i < $ogg_header['page_segments']; $i++) {
297
            $ogg_header['segment_table'][$i] = getid3_lib::LittleEndian2Int($file_data{$file_data_offset++});
298
            $ogg_header['page_length']       += $ogg_header['segment_table'][$i];
299
        }
300
        $ogg_header['header_end_offset'] = $ogg_header['page_start_offset'] + $file_data_offset;
301
        $ogg_header['page_end_offset']   = $ogg_header['header_end_offset'] + $ogg_header['page_length'];
302
        fseek($getid3->fp, $ogg_header['header_end_offset'], SEEK_SET);
303
304
        return $ogg_header;
305
    }
306
307
    private function ParseVorbisCommentsFilepointer()
308
    {
309
        $getid3 = $this->getid3;
310
311
        $original_offset      = ftell($getid3->fp);
312
        $comment_start_offset = $original_offset;
313
        $comment_data_offset  = 0;
314
        $vorbis_comment_page  = 1;
315
316
        switch ($getid3->info['audio']['dataformat']) {
317
            case 'vorbis':
318
                $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset'];  // Second Ogg page, after header block
319
                fseek($getid3->fp, $comment_start_offset, SEEK_SET);
320
                $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments'];
321
                $comment_data        = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset);
322
                $comment_data_offset += (strlen('vorbis') + 1);
323
                break;
324
325
            case 'flac':
326
                fseek($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4, SEEK_SET);
327
                $comment_data = fread($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['block_length']);
328
                break;
329
330
            case 'speex':
331
                $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset'];  // Second Ogg page, after header block
332
                fseek($getid3->fp, $comment_start_offset, SEEK_SET);
333
                $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments'];
334
                $comment_data        = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset);
335
                break;
336
337
            default:
338
                return false;
339
        }
340
341
        $vendor_size         = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
342
        $comment_data_offset += 4;
343
344
        $getid3->info['ogg']['vendor'] = substr($comment_data, $comment_data_offset, $vendor_size);
345
        $comment_data_offset           += $vendor_size;
346
347
        $comments_count      = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
348
        $comment_data_offset += 4;
349
350
        $getid3->info['avdataoffset'] = $comment_start_offset + $comment_data_offset;
351
352
        for ($i = 0; $i < $comments_count; $i++) {
353
            $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] = $comment_start_offset + $comment_data_offset;
354
355
            if (ftell($getid3->fp) < ($getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + 4)) {
356
                $vorbis_comment_page++;
357
358
                $ogg_page_info                                                   = $this->ParseOggPageHeader();
359
                $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
360
361
                // First, save what we haven't read yet
362
                $as_yet_unused_data = substr($comment_data, $comment_data_offset);
363
364
                // Then take that data off the end
365
                $comment_data = substr($comment_data, 0, $comment_data_offset);
366
367
                // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
368
                $comment_data        .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
369
                $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
370
371
                // Finally, stick the unused data back on the end
372
                $comment_data .= $as_yet_unused_data;
373
374
                $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1));
375
            }
376
            $getid3->info['ogg']['comments_raw'][$i]['size'] = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
377
378
            // replace avdataoffset with position just after the last vorbiscomment
379
            $getid3->info['avdataoffset'] = $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + $getid3->info['ogg']['comments_raw'][$i]['size'] + 4;
380
381
            $comment_data_offset += 4;
382
            while ((strlen($comment_data) - $comment_data_offset) < $getid3->info['ogg']['comments_raw'][$i]['size']) {
383
                if (($getid3->info['ogg']['comments_raw'][$i]['size'] > $getid3->info['avdataend']) || ($getid3->info['ogg']['comments_raw'][$i]['size'] < 0)) {
384
                    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');
385
                }
386
387
                $vorbis_comment_page++;
388
389
                $ogg_page_info                                                   = $this->ParseOggPageHeader();
390
                $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
391
392
                // First, save what we haven't read yet
393
                $as_yet_unused_data = substr($comment_data, $comment_data_offset);
394
395
                // Then take that data off the end
396
                $comment_data = substr($comment_data, 0, $comment_data_offset);
397
398
                // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
399
                $comment_data        .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
400
                $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
401
402
                // Finally, stick the unused data back on the end
403
                $comment_data .= $as_yet_unused_data;
404
405
                //$comment_data .= fread($getid3->fp, $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_length']);
406
                $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1));
407
                //$filebaseoffset += $ogg_page_info['header_end_offset'] - $ogg_page_info['page_start_offset'];
408
            }
409
            $comment_string      = substr($comment_data, $comment_data_offset, $getid3->info['ogg']['comments_raw'][$i]['size']);
410
            $comment_data_offset += $getid3->info['ogg']['comments_raw'][$i]['size'];
411
412
            if (!$comment_string) {
413
                // no comment?
414
                $getid3->warning('Blank Ogg comment [' . $i . ']');
415
            } elseif (strstr($comment_string, '=')) {
416
                $comment_exploded                                 = explode('=', $comment_string, 2);
417
                $getid3->info['ogg']['comments_raw'][$i]['key']   = strtoupper($comment_exploded[0]);
418
                $getid3->info['ogg']['comments_raw'][$i]['value'] = @$comment_exploded[1];
419
                $getid3->info['ogg']['comments_raw'][$i]['data']  = base64_decode($getid3->info['ogg']['comments_raw'][$i]['value']);
420
421
                $getid3->info['ogg']['comments'][strtolower($getid3->info['ogg']['comments_raw'][$i]['key'])][] = $getid3->info['ogg']['comments_raw'][$i]['value'];
422
423
                if ($getid3->option_tags_images) {
424
                    $image_chunk_check                                     = getid3_lib_image_size::get($getid3->info['ogg']['comments_raw'][$i]['data']);
425
                    $getid3->info['ogg']['comments_raw'][$i]['image_mime'] = image_type_to_mime_type($image_chunk_check[2]);
426
                }
427
428
                if (!@$getid3->info['ogg']['comments_raw'][$i]['image_mime'] || ('application/octet-stream' == $getid3->info['ogg']['comments_raw'][$i]['image_mime'])) {
429
                    unset($getid3->info['ogg']['comments_raw'][$i]['image_mime']);
430
                    unset($getid3->info['ogg']['comments_raw'][$i]['data']);
431
                }
432
            } else {
433
                $getid3->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair [' . $i . ']: ' . $comment_string);
434
            }
435
        }
436
437
        // Replay Gain Adjustment
438
        // http://privatewww.essex.ac.uk/~djmrob/replaygain/
439
        if (isset($getid3->info['ogg']['comments']) && is_array($getid3->info['ogg']['comments'])) {
440
            foreach ($getid3->info['ogg']['comments'] as $index => $commentvalue) {
441
                switch ($index) {
442
                    case 'rg_audiophile':
443
                    case 'replaygain_album_gain':
444
                        $getid3->info['replay_gain']['album']['adjustment'] = (float)$commentvalue[0];
445
                        unset($getid3->info['ogg']['comments'][$index]);
446
                        break;
447
448
                    case 'rg_radio':
449
                    case 'replaygain_track_gain':
450
                        $getid3->info['replay_gain']['track']['adjustment'] = (float)$commentvalue[0];
451
                        unset($getid3->info['ogg']['comments'][$index]);
452
                        break;
453
454
                    case 'replaygain_album_peak':
455
                        $getid3->info['replay_gain']['album']['peak'] = (float)$commentvalue[0];
456
                        unset($getid3->info['ogg']['comments'][$index]);
457
                        break;
458
459
                    case 'rg_peak':
460
                    case 'replaygain_track_peak':
461
                        $getid3->info['replay_gain']['track']['peak'] = (float)$commentvalue[0];
462
                        unset($getid3->info['ogg']['comments'][$index]);
463
                        break;
464
465
                    case 'replaygain_reference_loudness':
466
                        $getid3->info['replay_gain']['reference_volume'] = (float)$commentvalue[0];
467
                        unset($getid3->info['ogg']['comments'][$index]);
468
                        break;
469
                }
470
            }
471
        }
472
473
        fseek($getid3->fp, $original_offset, SEEK_SET);
474
475
        return true;
476
    }
477
478
    private function ParseFLAC()
479
    {
480
        $getid3 = $this->getid3;
481
482
        // http://flac.sourceforge.net/format.html
483
484
        $getid3->info['fileformat']            = 'flac';
485
        $getid3->info['audio']['dataformat']   = 'flac';
486
        $getid3->info['audio']['bitrate_mode'] = 'vbr';
487
        $getid3->info['audio']['lossless']     = true;
488
489
        return $this->FLACparseMETAdata();
490
    }
491
492
    private function FLACparseMETAdata()
493
    {
494
        $getid3 = $this->getid3;
495
496
        do {
497
            $meta_data_block_offset    = ftell($getid3->fp);
498
            $meta_data_block_header    = fread($getid3->fp, 4);
499
            $meta_data_last_block_flag = (bool)(getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x80);
500
            $meta_data_block_type      = getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x7F;
501
            $meta_data_block_length    = getid3_lib::BigEndian2Int(substr($meta_data_block_header, 1, 3));
502
            $meta_data_block_type_text = getid3_xiph::FLACmetaBlockTypeLookup($meta_data_block_type);
503
504
            if ($meta_data_block_length < 0) {
505
                throw new getid3_exception('corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE (' . $meta_data_block_type . ') at offset ' . $meta_data_block_offset);
506
            }
507
508
            $getid3->info['flac'][$meta_data_block_type_text]['raw'] = [
509
                'offset'          => $meta_data_block_offset,
510
                'last_meta_block' => $meta_data_last_block_flag,
511
                'block_type'      => $meta_data_block_type,
512
                'block_type_text' => $meta_data_block_type_text,
513
                'block_length'    => $meta_data_block_length,
514
                'block_data'      => @fread($getid3->fp, $meta_data_block_length)
515
            ];
516
            $getid3->info['avdataoffset']                            = ftell($getid3->fp);
517
518
            switch ($meta_data_block_type_text) {
519
                case 'STREAMINFO':
520
                    if (!$this->FLACparseSTREAMINFO($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
521
                        return false;
522
                    }
523
                    break;
524
525
                case 'PADDING':
526
                    // ignore
527
                    break;
528
529
                case 'APPLICATION':
530
                    if (!$this->FLACparseAPPLICATION($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
531
                        return false;
532
                    }
533
                    break;
534
535
                case 'SEEKTABLE':
536
                    if (!$this->FLACparseSEEKTABLE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
537
                        return false;
538
                    }
539
                    break;
540
541
                case 'VORBIS_COMMENT':
542
                    $old_offset = ftell($getid3->fp);
543
                    fseek($getid3->fp, 0 - $meta_data_block_length, SEEK_CUR);
544
                    $this->ParseVorbisCommentsFilepointer($getid3->fp, $getid3->info);
0 ignored issues
show
Unused Code introduced by
The call to getid3_xiph::ParseVorbisCommentsFilepointer() has too many arguments starting with $getid3->fp. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

544
                    $this->/** @scrutinizer ignore-call */ 
545
                           ParseVorbisCommentsFilepointer($getid3->fp, $getid3->info);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
545
                    fseek($getid3->fp, $old_offset, SEEK_SET);
546
                    break;
547
548
                case 'CUESHEET':
549
                    if (!$this->FLACparseCUESHEET($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
550
                        return false;
551
                    }
552
                    break;
553
554
                case 'PICTURE':
555
                    if (!$this->FLACparsePICTURE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
556
                        return false;
557
                    }
558
                    break;
559
560
                default:
561
                    $getid3->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE (' . $meta_data_block_type . ') at offset ' . $meta_data_block_offset);
562
            }
563
        } while (false === $meta_data_last_block_flag);
564
565
        if (isset($getid3->info['flac']['STREAMINFO'])) {
566
            $getid3->info['flac']['compressed_audio_bytes']   = $getid3->info['avdataend'] - $getid3->info['avdataoffset'];
567
            $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);
568
            $getid3->info['flac']['compression_ratio']        = $getid3->info['flac']['compressed_audio_bytes'] / $getid3->info['flac']['uncompressed_audio_bytes'];
569
        }
570
571
        // set md5_data_source - built into flac 0.5+
572
        if (isset($getid3->info['flac']['STREAMINFO']['audio_signature'])) {
573
            if ($getid3->info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
574
                $getid3->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
575
            } else {
576
                $getid3->info['md5_data_source'] = '';
577
                $md5                             = $getid3->info['flac']['STREAMINFO']['audio_signature'];
578
                for ($i = 0; $i < strlen($md5); $i++) {
579
                    $getid3->info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT);
580
                }
581
                if (!preg_match('/^[0-9a-f]{32}$/', $getid3->info['md5_data_source'])) {
582
                    unset($getid3->info['md5_data_source']);
583
                }
584
            }
585
        }
586
587
        $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample'];
588
        if (8 == $getid3->info['audio']['bits_per_sample']) {
589
            // special case
590
            // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
591
            // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
592
            $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');
593
        }
594
        if (!empty($getid3->info['ogg']['vendor'])) {
595
            $getid3->info['audio']['encoder'] = $getid3->info['ogg']['vendor'];
596
        }
597
598
        return true;
599
    }
600
601
    private function FLACparseSTREAMINFO($meta_data_block_data)
602
    {
603
        $getid3 = $this->getid3;
604
605
        getid3_lib::ReadSequence(
606
            'BigEndian2Int',
607
            $getid3->info['flac']['STREAMINFO'],
608
            $meta_data_block_data,
609
            0,
610
            [
611
                'min_block_size' => 2,
612
                'max_block_size' => 2,
613
                'min_frame_size' => 3,
614
                'max_frame_size' => 3
615
            ]
616
        );
617
618
        $sample_rate_channels_sample_bits_stream_samples = getid3_lib::BigEndian2Bin(substr($meta_data_block_data, 10, 8));
619
620
        $getid3->info['flac']['STREAMINFO']['sample_rate']     = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 0, 20));
621
        $getid3->info['flac']['STREAMINFO']['channels']        = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 20, 3)) + 1;
622
        $getid3->info['flac']['STREAMINFO']['bits_per_sample'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 23, 5)) + 1;
623
        $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
624
        $getid3->info['flac']['STREAMINFO']['audio_signature'] = substr($meta_data_block_data, 18, 16);
625
626
        if (!empty($getid3->info['flac']['STREAMINFO']['sample_rate'])) {
627
            $getid3->info['audio']['bitrate_mode']    = 'vbr';
628
            $getid3->info['audio']['sample_rate']     = $getid3->info['flac']['STREAMINFO']['sample_rate'];
629
            $getid3->info['audio']['channels']        = $getid3->info['flac']['STREAMINFO']['channels'];
630
            $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample'];
631
            $getid3->info['playtime_seconds']         = $getid3->info['flac']['STREAMINFO']['samples_stream'] / $getid3->info['flac']['STREAMINFO']['sample_rate'];
632
            $getid3->info['audio']['bitrate']         = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds'];
633
        } else {
634
            throw new getid3_exception('Corrupt METAdata block: STREAMINFO');
635
        }
636
637
        unset($getid3->info['flac']['STREAMINFO']['raw']);
638
639
        return true;
640
    }
641
642
    private function FLACparseAPPLICATION($meta_data_block_data)
643
    {
644
        $getid3 = $this->getid3;
645
646
        $application_id = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 0, 4));
647
648
        $getid3->info['flac']['APPLICATION'][$application_id]['name'] = getid3_xiph::FLACapplicationIDLookup($application_id);
649
        $getid3->info['flac']['APPLICATION'][$application_id]['data'] = substr($meta_data_block_data, 4);
650
651
        unset($getid3->info['flac']['APPLICATION']['raw']);
652
653
        return true;
654
    }
655
656
    private function FLACparseSEEKTABLE($meta_data_block_data)
657
    {
658
        $getid3 = $this->getid3;
659
660
        $offset                 = 0;
661
        $meta_data_block_length = strlen($meta_data_block_data);
662
        while ($offset < $meta_data_block_length) {
663
            $sample_number_string = substr($meta_data_block_data, $offset, 8);
664
            $offset               += 8;
665
            if ("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" == $sample_number_string) {
666
                // placeholder point
667
                @$getid3->info['flac']['SEEKTABLE']['placeholders']++;
668
                $offset += 10;
669
            } else {
670
                $sample_number = getid3_lib::BigEndian2Int($sample_number_string);
671
672
                $getid3->info['flac']['SEEKTABLE'][$sample_number]['offset'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
673
                $offset                                                      += 8;
674
675
                $getid3->info['flac']['SEEKTABLE'][$sample_number]['samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 2));
676
                $offset                                                       += 2;
677
            }
678
        }
679
680
        unset($getid3->info['flac']['SEEKTABLE']['raw']);
681
682
        return true;
683
    }
684
685
    private function FLACparseCUESHEET($meta_data_block_data)
686
    {
687
        $getid3 = $this->getid3;
688
689
        $getid3->info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($meta_data_block_data, 0, 128), "\0");
690
        $getid3->info['flac']['CUESHEET']['lead_in_samples']      = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 128, 8));
691
        $getid3->info['flac']['CUESHEET']['flags']['is_cd']       = (bool)(getid3_lib::BigEndian2Int($meta_data_block_data[136]) & 0x80);
692
        $getid3->info['flac']['CUESHEET']['number_tracks']        = getid3_lib::BigEndian2Int($meta_data_block_data[395]);
693
694
        $offset = 396;
695
696
        for ($track = 0; $track < $getid3->info['flac']['CUESHEET']['number_tracks']; $track++) {
697
            $track_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
698
            $offset              += 8;
699
700
            $track_number = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
701
702
            $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['sample_offset'] = $track_sample_offset;
703
            $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['isrc']          = substr($meta_data_block_data, $offset, 12);
704
            $offset                                                                     += 12;
705
706
            $track_flags_raw                                                                    = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
707
            $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['is_audio']     = (bool)($track_flags_raw & 0x80);
708
            $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['pre_emphasis'] = (bool)($track_flags_raw & 0x40);
709
710
            $offset += 13; // reserved
711
712
            $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points'] = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
713
714
            for ($index = 0; $index < $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points']; $index++) {
715
                $index_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
716
                $offset              += 8;
717
718
                $index_number                                                                        = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
719
                $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['indexes'][$index_number] = $index_sample_offset;
720
721
                $offset += 3; // reserved
722
            }
723
        }
724
725
        unset($getid3->info['flac']['CUESHEET']['raw']);
726
727
        return true;
728
    }
729
730
    private function FLACparsePICTURE($meta_data_block_data)
731
    {
732
        $getid3 = $this->getid3;
733
734
        $picture = &$getid3->info['flac']['PICTURE'][sizeof($getid3->info['flac']['PICTURE']) - 1];
735
736
        $offset = 0;
737
738
        $picture['type'] = $this->FLACpictureTypeLookup(getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)));
739
        $offset          += 4;
740
741
        $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
742
        $offset += 4;
743
744
        $picture['mime_type'] = substr($meta_data_block_data, $offset, $length);
745
        $offset               += $length;
746
747
        $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
748
        $offset += 4;
749
750
        $picture['description'] = substr($meta_data_block_data, $offset, $length);
751
        $offset                 += $length;
752
753
        $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
754
        $offset           += 4;
755
756
        $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
757
        $offset            += 4;
758
759
        $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
760
        $offset                 += 4;
761
762
        $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
763
        $offset                    += 4;
764
765
        $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
766
        $offset += 4;
767
768
        $picture['image_data'] = substr($meta_data_block_data, $offset, $length);
769
        $offset                += $length;
770
771
        unset($getid3->info['flac']['PICTURE']['raw']);
772
773
        return true;
774
    }
775
776
    public static function SpeexBandModeLookup($mode)
777
    {
778
        static $lookup = [
779
            0 => 'narrow',
780
            1 => 'wide',
781
            2 => 'ultra-wide'
782
        ];
783
        return (isset($lookup[$mode]) ? $lookup[$mode] : null);
784
    }
785
786
    public static function OggPageSegmentLength($ogg_info_array, $segment_number = 1)
787
    {
788
        for ($i = 0; $i < $segment_number; $i++) {
789
            $segment_length = 0;
790
            foreach ($ogg_info_array['segment_table'] as $key => $value) {
791
                $segment_length += $value;
792
                if ($value < 255) {
793
                    break;
794
                }
795
            }
796
        }
797
        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...
798
    }
799
800
    public static function GetQualityFromNominalBitrate($nominal_bitrate)
801
    {
802
        // decrease precision
803
        $nominal_bitrate = $nominal_bitrate / 1000;
804
805
        if ($nominal_bitrate < 128) {
806
            // q-1 to q4
807
            $qval = ($nominal_bitrate - 64) / 16;
808
        } elseif ($nominal_bitrate < 256) {
809
            // q4 to q8
810
            $qval = $nominal_bitrate / 32;
811
        } elseif ($nominal_bitrate < 320) {
812
            // q8 to q9
813
            $qval = ($nominal_bitrate + 256) / 64;
814
        } else {
815
            // q9 to q10
816
            $qval = ($nominal_bitrate + 1300) / 180;
817
        }
818
        return round($qval, 1); // 5 or 4.9
819
    }
820
821
    public static function FLACmetaBlockTypeLookup($block_type)
822
    {
823
        static $lookup = [
824
            0 => 'STREAMINFO',
825
            1 => 'PADDING',
826
            2 => 'APPLICATION',
827
            3 => 'SEEKTABLE',
828
            4 => 'VORBIS_COMMENT',
829
            5 => 'CUESHEET',
830
            6 => 'PICTURE'
831
        ];
832
        return (isset($lookup[$block_type]) ? $lookup[$block_type] : 'reserved');
833
    }
834
835
    public static function FLACapplicationIDLookup($application_id)
836
    {
837
        // http://flac.sourceforge.net/id.html
838
839
        static $lookup = [
840
            0x46746F6C => 'flac-tools',                                                 // 'Ftol'
841
            0x46746F6C => 'Sound Font FLAC',                                            // 'SFFL'
842
            0x7065656D => 'Parseable Embedded Extensible Metadata (specification)',     //  'peem'
843
            0x786D6364 => 'xmcd'
844
845
        ];
846
        return (isset($lookup[$application_id]) ? $lookup[$application_id] : 'reserved');
847
    }
848
849
    public static function FLACpictureTypeLookup($type_id)
850
    {
851
        static $lookup = [
852
853
            0  => 'Other',
854
            1  => "32x32 pixels 'file icon' (PNG only)",
855
            2  => 'Other file icon',
856
            3  => 'Cover (front)',
857
            4  => 'Cover (back)',
858
            5  => 'Leaflet page',
859
            6  => 'Media (e.g. label side of CD)',
860
            7  => 'Lead artist/lead performer/soloist',
861
            8  => 'Artist/performer',
862
            9  => 'Conductor',
863
            10 => 'Band/Orchestra',
864
            11 => 'Composer',
865
            12 => 'Lyricist/text writer',
866
            13 => 'Recording Location',
867
            14 => 'During recording',
868
            15 => 'During performance',
869
            16 => 'Movie/video screen capture',
870
            17 => 'A bright coloured fish',
871
            18 => 'Illustration',
872
            19 => 'Band/artist logotype',
873
            20 => 'Publisher/Studio logotype'
874
        ];
875
        return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
876
    }
877
878
}
879
880
881