~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/ID3/module.audio.ogg.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/////////////////////////////////////////////////////////////////
 
3
/// getID3() by James Heinrich <info@getid3.org>               //
 
4
//  available at http://getid3.sourceforge.net                 //
 
5
//            or http://www.getid3.org                         //
 
6
/////////////////////////////////////////////////////////////////
 
7
// See readme.txt for more details                             //
 
8
/////////////////////////////////////////////////////////////////
 
9
//                                                             //
 
10
// module.audio.ogg.php                                        //
 
11
// module for analyzing Ogg Vorbis, OggFLAC and Speex files    //
 
12
// dependencies: module.audio.flac.php                         //
 
13
//                                                            ///
 
14
/////////////////////////////////////////////////////////////////
 
15
 
 
16
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
 
17
 
 
18
class getid3_ogg extends getid3_handler
 
19
{
 
20
        // http://xiph.org/vorbis/doc/Vorbis_I_spec.html
 
21
        public function Analyze() {
 
22
                $info = &$this->getid3->info;
 
23
 
 
24
                $info['fileformat'] = 'ogg';
 
25
 
 
26
                // Warn about illegal tags - only vorbiscomments are allowed
 
27
                if (isset($info['id3v2'])) {
 
28
                        $info['warning'][] = 'Illegal ID3v2 tag present.';
 
29
                }
 
30
                if (isset($info['id3v1'])) {
 
31
                        $info['warning'][] = 'Illegal ID3v1 tag present.';
 
32
                }
 
33
                if (isset($info['ape'])) {
 
34
                        $info['warning'][] = 'Illegal APE tag present.';
 
35
                }
 
36
 
 
37
 
 
38
                // Page 1 - Stream Header
 
39
 
 
40
                $this->fseek($info['avdataoffset']);
 
41
 
 
42
                $oggpageinfo = $this->ParseOggPageHeader();
 
43
                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 
44
 
 
45
                if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
 
46
                        $info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)';
 
47
                        unset($info['fileformat']);
 
48
                        unset($info['ogg']);
 
49
                        return false;
 
50
                }
 
51
 
 
52
                $filedata = $this->fread($oggpageinfo['page_length']);
 
53
                $filedataoffset = 0;
 
54
 
 
55
                if (substr($filedata, 0, 4) == 'fLaC') {
 
56
 
 
57
                        $info['audio']['dataformat']   = 'flac';
 
58
                        $info['audio']['bitrate_mode'] = 'vbr';
 
59
                        $info['audio']['lossless']     = true;
 
60
 
 
61
                } elseif (substr($filedata, 1, 6) == 'vorbis') {
 
62
 
 
63
                        $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
 
64
 
 
65
                } elseif (substr($filedata, 0, 8) == 'Speex   ') {
 
66
 
 
67
                        // http://www.speex.org/manual/node10.html
 
68
 
 
69
                        $info['audio']['dataformat']   = 'speex';
 
70
                        $info['mime_type']             = 'audio/speex';
 
71
                        $info['audio']['bitrate_mode'] = 'abr';
 
72
                        $info['audio']['lossless']     = false;
 
73
 
 
74
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string']           =                              substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex   '
 
75
                        $filedataoffset += 8;
 
76
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']          =                              substr($filedata, $filedataoffset, 20);
 
77
                        $filedataoffset += 20;
 
78
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
79
                        $filedataoffset += 4;
 
80
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
81
                        $filedataoffset += 4;
 
82
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
83
                        $filedataoffset += 4;
 
84
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
85
                        $filedataoffset += 4;
 
86
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
87
                        $filedataoffset += 4;
 
88
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
89
                        $filedataoffset += 4;
 
90
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
91
                        $filedataoffset += 4;
 
92
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
93
                        $filedataoffset += 4;
 
94
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']                    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
95
                        $filedataoffset += 4;
 
96
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
97
                        $filedataoffset += 4;
 
98
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers']          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
99
                        $filedataoffset += 4;
 
100
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
101
                        $filedataoffset += 4;
 
102
                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
103
                        $filedataoffset += 4;
 
104
 
 
105
                        $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
 
106
                        $info['speex']['sample_rate']   = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
 
107
                        $info['speex']['channels']      = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
 
108
                        $info['speex']['vbr']           = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
 
109
                        $info['speex']['band_type']     = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
 
110
 
 
111
                        $info['audio']['sample_rate']   = $info['speex']['sample_rate'];
 
112
                        $info['audio']['channels']      = $info['speex']['channels'];
 
113
                        if ($info['speex']['vbr']) {
 
114
                                $info['audio']['bitrate_mode'] = 'vbr';
 
115
                        }
 
116
 
 
117
 
 
118
                } elseif (substr($filedata, 0, 8) == "fishead\x00") {
 
119
 
 
120
                        // Ogg Skeleton version 3.0 Format Specification
 
121
                        // http://xiph.org/ogg/doc/skeleton.html
 
122
                        $filedataoffset += 8;
 
123
                        $info['ogg']['skeleton']['fishead']['raw']['version_major']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
 
124
                        $filedataoffset += 2;
 
125
                        $info['ogg']['skeleton']['fishead']['raw']['version_minor']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
 
126
                        $filedataoffset += 2;
 
127
                        $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 
128
                        $filedataoffset += 8;
 
129
                        $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 
130
                        $filedataoffset += 8;
 
131
                        $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 
132
                        $filedataoffset += 8;
 
133
                        $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 
134
                        $filedataoffset += 8;
 
135
                        $info['ogg']['skeleton']['fishead']['raw']['utc']                          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
 
136
                        $filedataoffset += 20;
 
137
 
 
138
                        $info['ogg']['skeleton']['fishead']['version']          = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
 
139
                        $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
 
140
                        $info['ogg']['skeleton']['fishead']['basetime']         = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']         / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
 
141
                        $info['ogg']['skeleton']['fishead']['utc']              = $info['ogg']['skeleton']['fishead']['raw']['utc'];
 
142
 
 
143
 
 
144
                        $counter = 0;
 
145
                        do {
 
146
                                $oggpageinfo = $this->ParseOggPageHeader();
 
147
                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
 
148
                                $filedata = $this->fread($oggpageinfo['page_length']);
 
149
                                $this->fseek($oggpageinfo['page_end_offset']);
 
150
 
 
151
                                if (substr($filedata, 0, 8) == "fisbone\x00") {
 
152
 
 
153
                                        $filedataoffset = 8;
 
154
                                        $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 
155
                                        $filedataoffset += 4;
 
156
                                        $info['ogg']['skeleton']['fisbone']['raw']['serial_number']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 
157
                                        $filedataoffset += 4;
 
158
                                        $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 
159
                                        $filedataoffset += 4;
 
160
                                        $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 
161
                                        $filedataoffset += 8;
 
162
                                        $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 
163
                                        $filedataoffset += 8;
 
164
                                        $info['ogg']['skeleton']['fisbone']['raw']['basegranule']             = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 
165
                                        $filedataoffset += 8;
 
166
                                        $info['ogg']['skeleton']['fisbone']['raw']['preroll']                 = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 
167
                                        $filedataoffset += 4;
 
168
                                        $info['ogg']['skeleton']['fisbone']['raw']['granuleshift']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
 
169
                                        $filedataoffset += 1;
 
170
                                        $info['ogg']['skeleton']['fisbone']['raw']['padding']                 =                              substr($filedata, $filedataoffset,  3);
 
171
                                        $filedataoffset += 3;
 
172
 
 
173
                                } elseif (substr($filedata, 1, 6) == 'theora') {
 
174
 
 
175
                                        $info['video']['dataformat'] = 'theora';
 
176
                                        $info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']';
 
177
                                        //break;
 
178
 
 
179
                                } elseif (substr($filedata, 1, 6) == 'vorbis') {
 
180
 
 
181
                                        $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
 
182
 
 
183
                                } else {
 
184
                                        $info['error'][] = 'unexpected';
 
185
                                        //break;
 
186
                                }
 
187
                        //} while ($oggpageinfo['page_seqno'] == 0);
 
188
                        } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
 
189
 
 
190
                        $this->fseek($oggpageinfo['page_start_offset']);
 
191
 
 
192
                        $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
 
193
                        //return false;
 
194
 
 
195
                } else {
 
196
 
 
197
                        $info['error'][] = 'Expecting either "Speex   " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
 
198
                        unset($info['ogg']);
 
199
                        unset($info['mime_type']);
 
200
                        return false;
 
201
 
 
202
                }
 
203
 
 
204
                // Page 2 - Comment Header
 
205
                $oggpageinfo = $this->ParseOggPageHeader();
 
206
                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 
207
 
 
208
                switch ($info['audio']['dataformat']) {
 
209
                        case 'vorbis':
 
210
                                $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 
211
                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
 
212
                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] =                              substr($filedata, 1, 6); // hard-coded to 'vorbis'
 
213
 
 
214
                                $this->ParseVorbisComments();
 
215
                                break;
 
216
 
 
217
                        case 'flac':
 
218
                                $flac = new getid3_flac($this->getid3);
 
219
                                if (!$flac->parseMETAdata()) {
 
220
                                        $info['error'][] = 'Failed to parse FLAC headers';
 
221
                                        return false;
 
222
                                }
 
223
                                unset($flac);
 
224
                                break;
 
225
 
 
226
                        case 'speex':
 
227
                                $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
 
228
                                $this->ParseVorbisComments();
 
229
                                break;
 
230
                }
 
231
 
 
232
 
 
233
                // Last Page - Number of Samples
 
234
                if (!getid3_lib::intValueSupported($info['avdataend'])) {
 
235
 
 
236
                        $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
 
237
 
 
238
                } else {
 
239
 
 
240
                        $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
 
241
                        $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
 
242
                        if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
 
243
                                $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
 
244
                                $info['avdataend'] = $this->ftell();
 
245
                                $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
 
246
                                $info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
 
247
                                if ($info['ogg']['samples'] == 0) {
 
248
                                        $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
 
249
                                        return false;
 
250
                                }
 
251
                                if (!empty($info['audio']['sample_rate'])) {
 
252
                                        $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
 
253
                                }
 
254
                        }
 
255
 
 
256
                }
 
257
 
 
258
                if (!empty($info['ogg']['bitrate_average'])) {
 
259
                        $info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
 
260
                } elseif (!empty($info['ogg']['bitrate_nominal'])) {
 
261
                        $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
 
262
                } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
 
263
                        $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
 
264
                }
 
265
                if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
 
266
                        if ($info['audio']['bitrate'] == 0) {
 
267
                                $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
 
268
                                return false;
 
269
                        }
 
270
                        $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
 
271
                }
 
272
 
 
273
                if (isset($info['ogg']['vendor'])) {
 
274
                        $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
 
275
 
 
276
                        // Vorbis only
 
277
                        if ($info['audio']['dataformat'] == 'vorbis') {
 
278
 
 
279
                                // Vorbis 1.0 starts with Xiph.Org
 
280
                                if  (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
 
281
 
 
282
                                        if ($info['audio']['bitrate_mode'] == 'abr') {
 
283
 
 
284
                                                // Set -b 128 on abr files
 
285
                                                $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
 
286
 
 
287
                                        } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
 
288
                                                // Set -q N on vbr files
 
289
                                                $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
 
290
 
 
291
                                        }
 
292
                                }
 
293
 
 
294
                                if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
 
295
                                        $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
 
296
                                }
 
297
                        }
 
298
                }
 
299
 
 
300
                return true;
 
301
        }
 
302
 
 
303
        public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
 
304
                $info = &$this->getid3->info;
 
305
                $info['audio']['dataformat'] = 'vorbis';
 
306
                $info['audio']['lossless']   = false;
 
307
 
 
308
                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 
309
                $filedataoffset += 1;
 
310
                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
 
311
                $filedataoffset += 6;
 
312
                $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
313
                $filedataoffset += 4;
 
314
                $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 
315
                $filedataoffset += 1;
 
316
                $info['audio']['channels']       = $info['ogg']['numberofchannels'];
 
317
                $info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
318
                $filedataoffset += 4;
 
319
                if ($info['ogg']['samplerate'] == 0) {
 
320
                        $info['error'][] = 'Corrupt Ogg file: sample rate == zero';
 
321
                        return false;
 
322
                }
 
323
                $info['audio']['sample_rate']    = $info['ogg']['samplerate'];
 
324
                $info['ogg']['samples']          = 0; // filled in later
 
325
                $info['ogg']['bitrate_average']  = 0; // filled in later
 
326
                $info['ogg']['bitrate_max']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
327
                $filedataoffset += 4;
 
328
                $info['ogg']['bitrate_nominal']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
329
                $filedataoffset += 4;
 
330
                $info['ogg']['bitrate_min']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
331
                $filedataoffset += 4;
 
332
                $info['ogg']['blocksize_small']  = pow(2,  getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
 
333
                $info['ogg']['blocksize_large']  = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
 
334
                $info['ogg']['stop_bit']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
 
335
 
 
336
                $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
 
337
                if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
 
338
                        unset($info['ogg']['bitrate_max']);
 
339
                        $info['audio']['bitrate_mode'] = 'abr';
 
340
                }
 
341
                if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
 
342
                        unset($info['ogg']['bitrate_nominal']);
 
343
                }
 
344
                if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
 
345
                        unset($info['ogg']['bitrate_min']);
 
346
                        $info['audio']['bitrate_mode'] = 'abr';
 
347
                }
 
348
                return true;
 
349
        }
 
350
 
 
351
        public function ParseOggPageHeader() {
 
352
                // http://xiph.org/ogg/vorbis/doc/framing.html
 
353
                $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
 
354
 
 
355
                $filedata = $this->fread($this->getid3->fread_buffer_size());
 
356
                $filedataoffset = 0;
 
357
                while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
 
358
                        if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
 
359
                                // should be found before here
 
360
                                return false;
 
361
                        }
 
362
                        if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
 
363
                                if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
 
364
                                        // get some more data, unless eof, in which case fail
 
365
                                        return false;
 
366
                                }
 
367
                        }
 
368
                }
 
369
                $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
 
370
 
 
371
                $oggheader['stream_structver']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 
372
                $filedataoffset += 1;
 
373
                $oggheader['flags_raw']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 
374
                $filedataoffset += 1;
 
375
                $oggheader['flags']['fresh']    = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
 
376
                $oggheader['flags']['bos']      = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
 
377
                $oggheader['flags']['eos']      = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
 
378
 
 
379
                $oggheader['pcm_abs_position']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
 
380
                $filedataoffset += 8;
 
381
                $oggheader['stream_serialno']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
382
                $filedataoffset += 4;
 
383
                $oggheader['page_seqno']        = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
384
                $filedataoffset += 4;
 
385
                $oggheader['page_checksum']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 
386
                $filedataoffset += 4;
 
387
                $oggheader['page_segments']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 
388
                $filedataoffset += 1;
 
389
                $oggheader['page_length'] = 0;
 
390
                for ($i = 0; $i < $oggheader['page_segments']; $i++) {
 
391
                        $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 
392
                        $filedataoffset += 1;
 
393
                        $oggheader['page_length'] += $oggheader['segment_table'][$i];
 
394
                }
 
395
                $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
 
396
                $oggheader['page_end_offset']   = $oggheader['header_end_offset'] + $oggheader['page_length'];
 
397
                $this->fseek($oggheader['header_end_offset']);
 
398
 
 
399
                return $oggheader;
 
400
        }
 
401
 
 
402
    // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
 
403
        public function ParseVorbisComments() {
 
404
                $info = &$this->getid3->info;
 
405
 
 
406
                $OriginalOffset = $this->ftell();
 
407
                $commentdataoffset = 0;
 
408
                $VorbisCommentPage = 1;
 
409
 
 
410
                switch ($info['audio']['dataformat']) {
 
411
                        case 'vorbis':
 
412
                        case 'speex':
 
413
                                $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset'];  // Second Ogg page, after header block
 
414
                                $this->fseek($CommentStartOffset);
 
415
                                $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
 
416
                                $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
 
417
 
 
418
                                if ($info['audio']['dataformat'] == 'vorbis') {
 
419
                                        $commentdataoffset += (strlen('vorbis') + 1);
 
420
                                }
 
421
                                break;
 
422
 
 
423
                        case 'flac':
 
424
                                $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
 
425
                                $this->fseek($CommentStartOffset);
 
426
                                $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
 
427
                                break;
 
428
 
 
429
                        default:
 
430
                                return false;
 
431
                }
 
432
 
 
433
                $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 
434
                $commentdataoffset += 4;
 
435
 
 
436
                $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
 
437
                $commentdataoffset += $VendorSize;
 
438
 
 
439
                $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 
440
                $commentdataoffset += 4;
 
441
                $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
 
442
 
 
443
                $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
 
444
                $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
 
445
                for ($i = 0; $i < $CommentsCount; $i++) {
 
446
 
 
447
                        $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
 
448
 
 
449
                        if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
 
450
                                if ($oggpageinfo = $this->ParseOggPageHeader()) {
 
451
                                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 
452
 
 
453
                                        $VorbisCommentPage++;
 
454
 
 
455
                                        // First, save what we haven't read yet
 
456
                                        $AsYetUnusedData = substr($commentdata, $commentdataoffset);
 
457
 
 
458
                                        // Then take that data off the end
 
459
                                        $commentdata     = substr($commentdata, 0, $commentdataoffset);
 
460
 
 
461
                                        // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
 
462
                                        $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 
463
                                        $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 
464
 
 
465
                                        // Finally, stick the unused data back on the end
 
466
                                        $commentdata .= $AsYetUnusedData;
 
467
 
 
468
                                        //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 
469
                                        $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
 
470
                                }
 
471
 
 
472
                        }
 
473
                        $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 
474
 
 
475
                        // replace avdataoffset with position just after the last vorbiscomment
 
476
                        $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
 
477
 
 
478
                        $commentdataoffset += 4;
 
479
                        while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
 
480
                                if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
 
481
                                        $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
 
482
                                        break 2;
 
483
                                }
 
484
 
 
485
                                $VorbisCommentPage++;
 
486
 
 
487
                                $oggpageinfo = $this->ParseOggPageHeader();
 
488
                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 
489
 
 
490
                                // First, save what we haven't read yet
 
491
                                $AsYetUnusedData = substr($commentdata, $commentdataoffset);
 
492
 
 
493
                                // Then take that data off the end
 
494
                                $commentdata     = substr($commentdata, 0, $commentdataoffset);
 
495
 
 
496
                                // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
 
497
                                $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 
498
                                $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 
499
 
 
500
                                // Finally, stick the unused data back on the end
 
501
                                $commentdata .= $AsYetUnusedData;
 
502
 
 
503
                                //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 
504
                                if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
 
505
                                        $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
 
506
                                        break;
 
507
                                }
 
508
                                $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
 
509
                                if ($readlength <= 0) {
 
510
                                        $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
 
511
                                        break;
 
512
                                }
 
513
                                $commentdata .= $this->fread($readlength);
 
514
 
 
515
                                //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
 
516
                        }
 
517
                        $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
 
518
                        $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
 
519
                        $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
 
520
 
 
521
                        if (!$commentstring) {
 
522
 
 
523
                                // no comment?
 
524
                                $info['warning'][] = 'Blank Ogg comment ['.$i.']';
 
525
 
 
526
                        } elseif (strstr($commentstring, '=')) {
 
527
 
 
528
                                $commentexploded = explode('=', $commentstring, 2);
 
529
                                $ThisFileInfo_ogg_comments_raw[$i]['key']   = strtoupper($commentexploded[0]);
 
530
                                $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
 
531
 
 
532
                                if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
 
533
 
 
534
                                        // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
 
535
                                        // The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard.
 
536
                                        // http://flac.sourceforge.net/format.html#metadata_block_picture
 
537
                                        $flac = new getid3_flac($this->getid3);
 
538
                                        $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
 
539
                                        $flac->parsePICTURE();
 
540
                                        $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
 
541
                                        unset($flac);
 
542
 
 
543
                                } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
 
544
 
 
545
                                        $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
 
546
                                        $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
 
547
                                        /** @todo use 'coverartmime' where available */
 
548
                                        $imageinfo = getid3_lib::GetDataImageSize($data);
 
549
                                        if ($imageinfo === false || !isset($imageinfo['mime'])) {
 
550
                                                $this->warning('COVERART vorbiscomment tag contains invalid image');
 
551
                                                continue;
 
552
                                        }
 
553
 
 
554
                                        $ogg = new self($this->getid3);
 
555
                                        $ogg->setStringMode($data);
 
556
                                        $info['ogg']['comments']['picture'][] = array(
 
557
                                                'image_mime' => $imageinfo['mime'],
 
558
                                                'data'       => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
 
559
                                        );
 
560
                                        unset($ogg);
 
561
 
 
562
                                } else {
 
563
 
 
564
                                        $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
 
565
 
 
566
                                }
 
567
 
 
568
                        } else {
 
569
 
 
570
                                $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
 
571
 
 
572
                        }
 
573
                        unset($ThisFileInfo_ogg_comments_raw[$i]);
 
574
                }
 
575
                unset($ThisFileInfo_ogg_comments_raw);
 
576
 
 
577
 
 
578
                // Replay Gain Adjustment
 
579
                // http://privatewww.essex.ac.uk/~djmrob/replaygain/
 
580
                if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
 
581
                        foreach ($info['ogg']['comments'] as $index => $commentvalue) {
 
582
                                switch ($index) {
 
583
                                        case 'rg_audiophile':
 
584
                                        case 'replaygain_album_gain':
 
585
                                                $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
 
586
                                                unset($info['ogg']['comments'][$index]);
 
587
                                                break;
 
588
 
 
589
                                        case 'rg_radio':
 
590
                                        case 'replaygain_track_gain':
 
591
                                                $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
 
592
                                                unset($info['ogg']['comments'][$index]);
 
593
                                                break;
 
594
 
 
595
                                        case 'replaygain_album_peak':
 
596
                                                $info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
 
597
                                                unset($info['ogg']['comments'][$index]);
 
598
                                                break;
 
599
 
 
600
                                        case 'rg_peak':
 
601
                                        case 'replaygain_track_peak':
 
602
                                                $info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
 
603
                                                unset($info['ogg']['comments'][$index]);
 
604
                                                break;
 
605
 
 
606
                                        case 'replaygain_reference_loudness':
 
607
                                                $info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
 
608
                                                unset($info['ogg']['comments'][$index]);
 
609
                                                break;
 
610
 
 
611
                                        default:
 
612
                                                // do nothing
 
613
                                                break;
 
614
                                }
 
615
                        }
 
616
                }
 
617
 
 
618
                $this->fseek($OriginalOffset);
 
619
 
 
620
                return true;
 
621
        }
 
622
 
 
623
        public static function SpeexBandModeLookup($mode) {
 
624
                static $SpeexBandModeLookup = array();
 
625
                if (empty($SpeexBandModeLookup)) {
 
626
                        $SpeexBandModeLookup[0] = 'narrow';
 
627
                        $SpeexBandModeLookup[1] = 'wide';
 
628
                        $SpeexBandModeLookup[2] = 'ultra-wide';
 
629
                }
 
630
                return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
 
631
        }
 
632
 
 
633
 
 
634
        public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
 
635
                for ($i = 0; $i < $SegmentNumber; $i++) {
 
636
                        $segmentlength = 0;
 
637
                        foreach ($OggInfoArray['segment_table'] as $key => $value) {
 
638
                                $segmentlength += $value;
 
639
                                if ($value < 255) {
 
640
                                        break;
 
641
                                }
 
642
                        }
 
643
                }
 
644
                return $segmentlength;
 
645
        }
 
646
 
 
647
 
 
648
        public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
 
649
 
 
650
                // decrease precision
 
651
                $nominal_bitrate = $nominal_bitrate / 1000;
 
652
 
 
653
                if ($nominal_bitrate < 128) {
 
654
                        // q-1 to q4
 
655
                        $qval = ($nominal_bitrate - 64) / 16;
 
656
                } elseif ($nominal_bitrate < 256) {
 
657
                        // q4 to q8
 
658
                        $qval = $nominal_bitrate / 32;
 
659
                } elseif ($nominal_bitrate < 320) {
 
660
                        // q8 to q9
 
661
                        $qval = ($nominal_bitrate + 256) / 64;
 
662
                } else {
 
663
                        // q9 to q10
 
664
                        $qval = ($nominal_bitrate + 1300) / 180;
 
665
                }
 
666
                //return $qval; // 5.031324
 
667
                //return intval($qval); // 5
 
668
                return round($qval, 1); // 5 or 4.9
 
669
        }
 
670
 
 
671
}