source: spip-zone/_core_/branches/spip-3.1/plugins/medias/lib/getid3/module.audio.ogg.php @ 113163

Last change on this file since 113163 was 113163, checked in by spip.franck@…, 5 months ago

Mise à jour de la lib getid en version 1.9.16, nous étions en 1.9.12
https://github.com/JamesHeinrich/getID3/blob/master/changelog.txt

File size: 42.0 KB
Line 
1<?php
2
3/////////////////////////////////////////////////////////////////
4/// getID3() by James Heinrich <info@getid3.org>               //
5//  available at https://github.com/JamesHeinrich/getID3       //
6//            or https://www.getid3.org                        //
7//            or http://getid3.sourceforge.net                 //
8//  see readme.txt for more details                            //
9/////////////////////////////////////////////////////////////////
10//                                                             //
11// module.audio.ogg.php                                        //
12// module for analyzing Ogg Vorbis, OggFLAC and Speex files    //
13// dependencies: module.audio.flac.php                         //
14//                                                            ///
15/////////////////////////////////////////////////////////////////
16
17getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
18
19class getid3_ogg extends getid3_handler
20{
21        /**
22         * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html
23         *
24         * @return bool
25         */
26        public function Analyze() {
27                $info = &$this->getid3->info;
28
29                $info['fileformat'] = 'ogg';
30
31                // Warn about illegal tags - only vorbiscomments are allowed
32                if (isset($info['id3v2'])) {
33                        $this->warning('Illegal ID3v2 tag present.');
34                }
35                if (isset($info['id3v1'])) {
36                        $this->warning('Illegal ID3v1 tag present.');
37                }
38                if (isset($info['ape'])) {
39                        $this->warning('Illegal APE tag present.');
40                }
41
42
43                // Page 1 - Stream Header
44
45                $this->fseek($info['avdataoffset']);
46
47                $oggpageinfo = $this->ParseOggPageHeader();
48                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
49
50                if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
51                        $this->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?)');
52                        unset($info['fileformat']);
53                        unset($info['ogg']);
54                        return false;
55                }
56
57                $filedata = $this->fread($oggpageinfo['page_length']);
58                $filedataoffset = 0;
59
60                if (substr($filedata, 0, 4) == 'fLaC') {
61
62                        $info['audio']['dataformat']   = 'flac';
63                        $info['audio']['bitrate_mode'] = 'vbr';
64                        $info['audio']['lossless']     = true;
65
66                } elseif (substr($filedata, 1, 6) == 'vorbis') {
67
68                        $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
69
70                } elseif (substr($filedata, 0, 8) == 'OpusHead') {
71
72                        if ($this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) === false) {
73                                return false;
74                        }
75
76                } elseif (substr($filedata, 0, 8) == 'Speex   ') {
77
78                        // http://www.speex.org/manual/node10.html
79
80                        $info['audio']['dataformat']   = 'speex';
81                        $info['mime_type']             = 'audio/speex';
82                        $info['audio']['bitrate_mode'] = 'abr';
83                        $info['audio']['lossless']     = false;
84
85                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string']           =                              substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex   '
86                        $filedataoffset += 8;
87                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']          =                              substr($filedata, $filedataoffset, 20);
88                        $filedataoffset += 20;
89                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
90                        $filedataoffset += 4;
91                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
92                        $filedataoffset += 4;
93                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
94                        $filedataoffset += 4;
95                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
96                        $filedataoffset += 4;
97                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
98                        $filedataoffset += 4;
99                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
100                        $filedataoffset += 4;
101                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
102                        $filedataoffset += 4;
103                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
104                        $filedataoffset += 4;
105                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']                    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
106                        $filedataoffset += 4;
107                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
108                        $filedataoffset += 4;
109                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers']          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
110                        $filedataoffset += 4;
111                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
112                        $filedataoffset += 4;
113                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
114                        $filedataoffset += 4;
115
116                        $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
117                        $info['speex']['sample_rate']   = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
118                        $info['speex']['channels']      = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
119                        $info['speex']['vbr']           = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
120                        $info['speex']['band_type']     = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
121
122                        $info['audio']['sample_rate']   = $info['speex']['sample_rate'];
123                        $info['audio']['channels']      = $info['speex']['channels'];
124                        if ($info['speex']['vbr']) {
125                                $info['audio']['bitrate_mode'] = 'vbr';
126                        }
127
128                } elseif (substr($filedata, 0, 7) == "\x80".'theora') {
129
130                        // http://www.theora.org/doc/Theora.pdf (section 6.2)
131
132                        $info['ogg']['pageheader']['theora']['theora_magic']             =                           substr($filedata, $filedataoffset,  7); // hard-coded to "\x80.'theora'
133                        $filedataoffset += 7;
134                        $info['ogg']['pageheader']['theora']['version_major']            = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
135                        $filedataoffset += 1;
136                        $info['ogg']['pageheader']['theora']['version_minor']            = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
137                        $filedataoffset += 1;
138                        $info['ogg']['pageheader']['theora']['version_revision']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
139                        $filedataoffset += 1;
140                        $info['ogg']['pageheader']['theora']['frame_width_macroblocks']  = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
141                        $filedataoffset += 2;
142                        $info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
143                        $filedataoffset += 2;
144                        $info['ogg']['pageheader']['theora']['resolution_x']             = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
145                        $filedataoffset += 3;
146                        $info['ogg']['pageheader']['theora']['resolution_y']             = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
147                        $filedataoffset += 3;
148                        $info['ogg']['pageheader']['theora']['picture_offset_x']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
149                        $filedataoffset += 1;
150                        $info['ogg']['pageheader']['theora']['picture_offset_y']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
151                        $filedataoffset += 1;
152                        $info['ogg']['pageheader']['theora']['frame_rate_numerator']     = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  4));
153                        $filedataoffset += 4;
154                        $info['ogg']['pageheader']['theora']['frame_rate_denominator']   = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  4));
155                        $filedataoffset += 4;
156                        $info['ogg']['pageheader']['theora']['pixel_aspect_numerator']   = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
157                        $filedataoffset += 3;
158                        $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
159                        $filedataoffset += 3;
160                        $info['ogg']['pageheader']['theora']['color_space_id']           = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
161                        $filedataoffset += 1;
162                        $info['ogg']['pageheader']['theora']['nominal_bitrate']          = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
163                        $filedataoffset += 3;
164                        $info['ogg']['pageheader']['theora']['flags']                    = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
165                        $filedataoffset += 2;
166
167                        $info['ogg']['pageheader']['theora']['quality']         = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10;
168                        $info['ogg']['pageheader']['theora']['kfg_shift']       = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >>  5;
169                        $info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >>  3;
170                        $info['ogg']['pageheader']['theora']['reserved']        = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >>  0; // should be 0
171                        $info['ogg']['pageheader']['theora']['color_space']     = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']);
172                        $info['ogg']['pageheader']['theora']['pixel_format']    = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']);
173
174                        $info['video']['dataformat']   = 'theora';
175                        $info['mime_type']             = 'video/ogg';
176                        //$info['audio']['bitrate_mode'] = 'abr';
177                        //$info['audio']['lossless']     = false;
178                        $info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x'];
179                        $info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y'];
180                        if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) {
181                                $info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator'];
182                        }
183                        if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) {
184                                $info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'];
185                        }
186                        $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable');
187
188
189                } elseif (substr($filedata, 0, 8) == "fishead\x00") {
190
191                        // Ogg Skeleton version 3.0 Format Specification
192                        // http://xiph.org/ogg/doc/skeleton.html
193                        $filedataoffset += 8;
194                        $info['ogg']['skeleton']['fishead']['raw']['version_major']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
195                        $filedataoffset += 2;
196                        $info['ogg']['skeleton']['fishead']['raw']['version_minor']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
197                        $filedataoffset += 2;
198                        $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
199                        $filedataoffset += 8;
200                        $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
201                        $filedataoffset += 8;
202                        $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
203                        $filedataoffset += 8;
204                        $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
205                        $filedataoffset += 8;
206                        $info['ogg']['skeleton']['fishead']['raw']['utc']                          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
207                        $filedataoffset += 20;
208
209                        $info['ogg']['skeleton']['fishead']['version']          = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
210                        $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
211                        $info['ogg']['skeleton']['fishead']['basetime']         = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']         / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
212                        $info['ogg']['skeleton']['fishead']['utc']              = $info['ogg']['skeleton']['fishead']['raw']['utc'];
213
214
215                        $counter = 0;
216                        do {
217                                $oggpageinfo = $this->ParseOggPageHeader();
218                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
219                                $filedata = $this->fread($oggpageinfo['page_length']);
220                                $this->fseek($oggpageinfo['page_end_offset']);
221
222                                if (substr($filedata, 0, 8) == "fisbone\x00") {
223
224                                        $filedataoffset = 8;
225                                        $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
226                                        $filedataoffset += 4;
227                                        $info['ogg']['skeleton']['fisbone']['raw']['serial_number']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
228                                        $filedataoffset += 4;
229                                        $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
230                                        $filedataoffset += 4;
231                                        $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
232                                        $filedataoffset += 8;
233                                        $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
234                                        $filedataoffset += 8;
235                                        $info['ogg']['skeleton']['fisbone']['raw']['basegranule']             = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
236                                        $filedataoffset += 8;
237                                        $info['ogg']['skeleton']['fisbone']['raw']['preroll']                 = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
238                                        $filedataoffset += 4;
239                                        $info['ogg']['skeleton']['fisbone']['raw']['granuleshift']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
240                                        $filedataoffset += 1;
241                                        $info['ogg']['skeleton']['fisbone']['raw']['padding']                 =                              substr($filedata, $filedataoffset,  3);
242                                        $filedataoffset += 3;
243
244                                } elseif (substr($filedata, 1, 6) == 'theora') {
245
246                                        $info['video']['dataformat'] = 'theora1';
247                                        $this->error('Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']');
248                                        //break;
249
250                                } elseif (substr($filedata, 1, 6) == 'vorbis') {
251
252                                        $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
253
254                                } else {
255                                        $this->error('unexpected');
256                                        //break;
257                                }
258                        //} while ($oggpageinfo['page_seqno'] == 0);
259                        } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
260
261                        $this->fseek($oggpageinfo['page_start_offset']);
262
263                        $this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']');
264                        //return false;
265
266                } elseif (substr($filedata, 0, 5) == "\x7F".'FLAC') {
267                        // https://xiph.org/flac/ogg_mapping.html
268
269                        $info['audio']['dataformat']   = 'flac';
270                        $info['audio']['bitrate_mode'] = 'vbr';
271                        $info['audio']['lossless']     = true;
272
273                        $info['ogg']['flac']['header']['version_major']  =                         ord(substr($filedata,  5, 1));
274                        $info['ogg']['flac']['header']['version_minor']  =                         ord(substr($filedata,  6, 1));
275                        $info['ogg']['flac']['header']['header_packets'] =   getid3_lib::BigEndian2Int(substr($filedata,  7, 2)) + 1; // "A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one. This number may be zero (0x0000) to signify 'unknown' but be aware that some decoders may not be able to handle such streams."
276                        $info['ogg']['flac']['header']['magic']          =                             substr($filedata,  9, 4);
277                        if ($info['ogg']['flac']['header']['magic'] != 'fLaC') {
278                                $this->error('Ogg-FLAC expecting "fLaC", found "'.$info['ogg']['flac']['header']['magic'].'" ('.trim(getid3_lib::PrintHexBytes($info['ogg']['flac']['header']['magic'])).')');
279                                return false;
280                        }
281                        $info['ogg']['flac']['header']['STREAMINFO_bytes'] = getid3_lib::BigEndian2Int(substr($filedata, 13, 4));
282                        $info['flac']['STREAMINFO'] = getid3_flac::parseSTREAMINFOdata(substr($filedata, 17, 34));
283                        if (!empty($info['flac']['STREAMINFO']['sample_rate'])) {
284                                $info['audio']['bitrate_mode']    = 'vbr';
285                                $info['audio']['sample_rate']     = $info['flac']['STREAMINFO']['sample_rate'];
286                                $info['audio']['channels']        = $info['flac']['STREAMINFO']['channels'];
287                                $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
288                                $info['playtime_seconds']         = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
289                        }
290
291                } else {
292
293                        $this->error('Expecting one of "vorbis", "Speex", "OpusHead", "vorbis", "fishhead", "theora", "fLaC" identifier strings, found "'.substr($filedata, 0, 8).'"');
294                        unset($info['ogg']);
295                        unset($info['mime_type']);
296                        return false;
297
298                }
299
300                // Page 2 - Comment Header
301                $oggpageinfo = $this->ParseOggPageHeader();
302                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
303
304                switch ($info['audio']['dataformat']) {
305                        case 'vorbis':
306                                $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
307                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
308                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] =                              substr($filedata, 1, 6); // hard-coded to 'vorbis'
309
310                                $this->ParseVorbisComments();
311                                break;
312
313                        case 'flac':
314                                $flac = new getid3_flac($this->getid3);
315                                if (!$flac->parseMETAdata()) {
316                                        $this->error('Failed to parse FLAC headers');
317                                        return false;
318                                }
319                                unset($flac);
320                                break;
321
322                        case 'speex':
323                                $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
324                                $this->ParseVorbisComments();
325                                break;
326
327                        case 'opus':
328                                $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
329                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags'
330                                if(substr($filedata, 0, 8)  != 'OpusTags') {
331                                        $this->error('Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"');
332                                        return false;
333                                }
334
335                                $this->ParseVorbisComments();
336                                break;
337
338                }
339
340                // Last Page - Number of Samples
341                if (!getid3_lib::intValueSupported($info['avdataend'])) {
342
343                        $this->warning('Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)');
344
345                } else {
346
347                        $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
348                        $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
349                        if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
350                                $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
351                                $info['avdataend'] = $this->ftell();
352                                $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
353                                $info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
354                                if ($info['ogg']['samples'] == 0) {
355                                        $this->error('Corrupt Ogg file: eos.number of samples == zero');
356                                        return false;
357                                }
358                                if (!empty($info['audio']['sample_rate'])) {
359                                        $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
360                                }
361                        }
362
363                }
364
365                if (!empty($info['ogg']['bitrate_average'])) {
366                        $info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
367                } elseif (!empty($info['ogg']['bitrate_nominal'])) {
368                        $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
369                } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
370                        $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
371                }
372                if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
373                        if ($info['audio']['bitrate'] == 0) {
374                                $this->error('Corrupt Ogg file: bitrate_audio == zero');
375                                return false;
376                        }
377                        $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
378                }
379
380                if (isset($info['ogg']['vendor'])) {
381                        $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
382
383                        // Vorbis only
384                        if ($info['audio']['dataformat'] == 'vorbis') {
385
386                                // Vorbis 1.0 starts with Xiph.Org
387                                if  (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
388
389                                        if ($info['audio']['bitrate_mode'] == 'abr') {
390
391                                                // Set -b 128 on abr files
392                                                $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
393
394                                        } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
395                                                // Set -q N on vbr files
396                                                $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
397
398                                        }
399                                }
400
401                                if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
402                                        $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
403                                }
404                        }
405                }
406
407                return true;
408        }
409
410        /**
411         * @param string $filedata
412         * @param int    $filedataoffset
413         * @param array  $oggpageinfo
414         *
415         * @return bool
416         */
417        public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
418                $info = &$this->getid3->info;
419                $info['audio']['dataformat'] = 'vorbis';
420                $info['audio']['lossless']   = false;
421
422                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
423                $filedataoffset += 1;
424                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
425                $filedataoffset += 6;
426                $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
427                $filedataoffset += 4;
428                $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
429                $filedataoffset += 1;
430                $info['audio']['channels']       = $info['ogg']['numberofchannels'];
431                $info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
432                $filedataoffset += 4;
433                if ($info['ogg']['samplerate'] == 0) {
434                        $this->error('Corrupt Ogg file: sample rate == zero');
435                        return false;
436                }
437                $info['audio']['sample_rate']    = $info['ogg']['samplerate'];
438                $info['ogg']['samples']          = 0; // filled in later
439                $info['ogg']['bitrate_average']  = 0; // filled in later
440                $info['ogg']['bitrate_max']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
441                $filedataoffset += 4;
442                $info['ogg']['bitrate_nominal']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
443                $filedataoffset += 4;
444                $info['ogg']['bitrate_min']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
445                $filedataoffset += 4;
446                $info['ogg']['blocksize_small']  = pow(2,  getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
447                $info['ogg']['blocksize_large']  = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
448                $info['ogg']['stop_bit']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
449
450                $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
451                if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
452                        unset($info['ogg']['bitrate_max']);
453                        $info['audio']['bitrate_mode'] = 'abr';
454                }
455                if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
456                        unset($info['ogg']['bitrate_nominal']);
457                }
458                if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
459                        unset($info['ogg']['bitrate_min']);
460                        $info['audio']['bitrate_mode'] = 'abr';
461                }
462                return true;
463        }
464
465        /**
466         * @link http://tools.ietf.org/html/draft-ietf-codec-oggopus-03
467         *
468         * @param string $filedata
469         * @param int    $filedataoffset
470         * @param array  $oggpageinfo
471         *
472         * @return bool
473         */
474        public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
475                $info = &$this->getid3->info;
476                $info['audio']['dataformat']   = 'opus';
477                $info['mime_type']             = 'audio/ogg; codecs=opus';
478
479                /** @todo find a usable way to detect abr (vbr that is padded to be abr) */
480                $info['audio']['bitrate_mode'] = 'vbr';
481
482                $info['audio']['lossless']     = false;
483
484                $info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead'
485                $filedataoffset += 8;
486                $info['ogg']['pageheader']['opus']['version']    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
487                $filedataoffset += 1;
488
489                if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) {
490                        $this->error('Unknown opus version number (only accepting 1-15)');
491                        return false;
492                }
493
494                $info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
495                $filedataoffset += 1;
496
497                if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) {
498                        $this->error('Invalid channel count in opus header (must not be zero)');
499                        return false;
500                }
501
502                $info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
503                $filedataoffset += 2;
504
505                $info['ogg']['pageheader']['opus']['input_sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
506                $filedataoffset += 4;
507
508                //$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
509                //$filedataoffset += 2;
510
511                //$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
512                //$filedataoffset += 1;
513
514                $info['opus']['opus_version']       = $info['ogg']['pageheader']['opus']['version'];
515                $info['opus']['sample_rate_input']  = $info['ogg']['pageheader']['opus']['input_sample_rate'];
516                $info['opus']['out_channel_count']  = $info['ogg']['pageheader']['opus']['out_channel_count'];
517
518                $info['audio']['channels']          = $info['opus']['out_channel_count'];
519                $info['audio']['sample_rate_input'] = $info['opus']['sample_rate_input'];
520                $info['audio']['sample_rate']       = 48000; // "All Opus audio is coded at 48 kHz, and should also be decoded at 48 kHz for playback (unless the target hardware does not support this sampling rate). However, this field may be used to resample the audio back to the original sampling rate, for example, when saving the output to a file." -- https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html
521                return true;
522        }
523
524        /**
525         * @return array|false
526         */
527        public function ParseOggPageHeader() {
528                // http://xiph.org/ogg/vorbis/doc/framing.html
529                $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
530
531                $filedata = $this->fread($this->getid3->fread_buffer_size());
532                $filedataoffset = 0;
533                while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
534                        if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
535                                // should be found before here
536                                return false;
537                        }
538                        if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
539                                if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) {
540                                        // get some more data, unless eof, in which case fail
541                                        return false;
542                                }
543                        }
544                }
545                $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
546
547                $oggheader['stream_structver']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
548                $filedataoffset += 1;
549                $oggheader['flags_raw']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
550                $filedataoffset += 1;
551                $oggheader['flags']['fresh']    = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
552                $oggheader['flags']['bos']      = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
553                $oggheader['flags']['eos']      = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
554
555                $oggheader['pcm_abs_position']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
556                $filedataoffset += 8;
557                $oggheader['stream_serialno']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
558                $filedataoffset += 4;
559                $oggheader['page_seqno']        = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
560                $filedataoffset += 4;
561                $oggheader['page_checksum']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
562                $filedataoffset += 4;
563                $oggheader['page_segments']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
564                $filedataoffset += 1;
565                $oggheader['page_length'] = 0;
566                for ($i = 0; $i < $oggheader['page_segments']; $i++) {
567                        $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
568                        $filedataoffset += 1;
569                        $oggheader['page_length'] += $oggheader['segment_table'][$i];
570                }
571                $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
572                $oggheader['page_end_offset']   = $oggheader['header_end_offset'] + $oggheader['page_length'];
573                $this->fseek($oggheader['header_end_offset']);
574
575                return $oggheader;
576        }
577
578        /**
579         * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
580         *
581         * @return bool
582         */
583        public function ParseVorbisComments() {
584                $info = &$this->getid3->info;
585
586                $OriginalOffset = $this->ftell();
587                $commentdata = null;
588                $commentdataoffset = 0;
589                $VorbisCommentPage = 1;
590                $CommentStartOffset = 0;
591
592                switch ($info['audio']['dataformat']) {
593                        case 'vorbis':
594                        case 'speex':
595                        case 'opus':
596                                $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset'];  // Second Ogg page, after header block
597                                $this->fseek($CommentStartOffset);
598                                $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
599                                $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
600
601                                if ($info['audio']['dataformat'] == 'vorbis') {
602                                        $commentdataoffset += (strlen('vorbis') + 1);
603                                }
604                                else if ($info['audio']['dataformat'] == 'opus') {
605                                        $commentdataoffset += strlen('OpusTags');
606                                }
607
608                                break;
609
610                        case 'flac':
611                                $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
612                                $this->fseek($CommentStartOffset);
613                                $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
614                                break;
615
616                        default:
617                                return false;
618                                break;
619                }
620
621                $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
622                $commentdataoffset += 4;
623
624                $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
625                $commentdataoffset += $VendorSize;
626
627                $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
628                $commentdataoffset += 4;
629                $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
630
631                $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
632                $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
633                for ($i = 0; $i < $CommentsCount; $i++) {
634
635                        if ($i >= 10000) {
636                                // https://github.com/owncloud/music/issues/212#issuecomment-43082336
637                                $this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments');
638                                break;
639                        }
640
641                        $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
642
643                        if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
644                                if ($oggpageinfo = $this->ParseOggPageHeader()) {
645                                        $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
646
647                                        $VorbisCommentPage++;
648
649                                        // First, save what we haven't read yet
650                                        $AsYetUnusedData = substr($commentdata, $commentdataoffset);
651
652                                        // Then take that data off the end
653                                        $commentdata     = substr($commentdata, 0, $commentdataoffset);
654
655                                        // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
656                                        $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
657                                        $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
658
659                                        // Finally, stick the unused data back on the end
660                                        $commentdata .= $AsYetUnusedData;
661
662                                        //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
663                                        $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
664                                }
665
666                        }
667                        $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
668
669                        // replace avdataoffset with position just after the last vorbiscomment
670                        $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
671
672                        $commentdataoffset += 4;
673                        while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
674                                if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
675                                        $this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments');
676                                        break 2;
677                                }
678
679                                $VorbisCommentPage++;
680
681                                $oggpageinfo = $this->ParseOggPageHeader();
682                                $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
683
684                                // First, save what we haven't read yet
685                                $AsYetUnusedData = substr($commentdata, $commentdataoffset);
686
687                                // Then take that data off the end
688                                $commentdata     = substr($commentdata, 0, $commentdataoffset);
689
690                                // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
691                                $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
692                                $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
693
694                                // Finally, stick the unused data back on the end
695                                $commentdata .= $AsYetUnusedData;
696
697                                //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
698                                if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
699                                        $this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
700                                        break;
701                                }
702                                $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
703                                if ($readlength <= 0) {
704                                        $this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
705                                        break;
706                                }
707                                $commentdata .= $this->fread($readlength);
708
709                                //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
710                        }
711                        $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
712                        $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
713                        $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
714
715                        if (!$commentstring) {
716
717                                // no comment?
718                                $this->warning('Blank Ogg comment ['.$i.']');
719
720                        } elseif (strstr($commentstring, '=')) {
721
722                                $commentexploded = explode('=', $commentstring, 2);
723                                $ThisFileInfo_ogg_comments_raw[$i]['key']   = strtoupper($commentexploded[0]);
724                                $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
725
726                                if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
727
728                                        // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
729                                        // 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.
730                                        // http://flac.sourceforge.net/format.html#metadata_block_picture
731                                        $flac = new getid3_flac($this->getid3);
732                                        $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
733                                        $flac->parsePICTURE();
734                                        $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
735                                        unset($flac);
736
737                                } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
738
739                                        $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
740                                        $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
741                                        /** @todo use 'coverartmime' where available */
742                                        $imageinfo = getid3_lib::GetDataImageSize($data);
743                                        if ($imageinfo === false || !isset($imageinfo['mime'])) {
744                                                $this->warning('COVERART vorbiscomment tag contains invalid image');
745                                                continue;
746                                        }
747
748                                        $ogg = new self($this->getid3);
749                                        $ogg->setStringMode($data);
750                                        $info['ogg']['comments']['picture'][] = array(
751                                                'image_mime'   => $imageinfo['mime'],
752                                                'datalength'   => strlen($data),
753                                                'picturetype'  => 'cover art',
754                                                'image_height' => $imageinfo['height'],
755                                                'image_width'  => $imageinfo['width'],
756                                                'data'         => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
757                                        );
758                                        unset($ogg);
759
760                                } else {
761
762                                        $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
763
764                                }
765
766                        } else {
767
768                                $this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring);
769
770                        }
771                        unset($ThisFileInfo_ogg_comments_raw[$i]);
772                }
773                unset($ThisFileInfo_ogg_comments_raw);
774
775
776                // Replay Gain Adjustment
777                // http://privatewww.essex.ac.uk/~djmrob/replaygain/
778                if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
779                        foreach ($info['ogg']['comments'] as $index => $commentvalue) {
780                                switch ($index) {
781                                        case 'rg_audiophile':
782                                        case 'replaygain_album_gain':
783                                                $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
784                                                unset($info['ogg']['comments'][$index]);
785                                                break;
786
787                                        case 'rg_radio':
788                                        case 'replaygain_track_gain':
789                                                $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
790                                                unset($info['ogg']['comments'][$index]);
791                                                break;
792
793                                        case 'replaygain_album_peak':
794                                                $info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
795                                                unset($info['ogg']['comments'][$index]);
796                                                break;
797
798                                        case 'rg_peak':
799                                        case 'replaygain_track_peak':
800                                                $info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
801                                                unset($info['ogg']['comments'][$index]);
802                                                break;
803
804                                        case 'replaygain_reference_loudness':
805                                                $info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
806                                                unset($info['ogg']['comments'][$index]);
807                                                break;
808
809                                        default:
810                                                // do nothing
811                                                break;
812                                }
813                        }
814                }
815
816                $this->fseek($OriginalOffset);
817
818                return true;
819        }
820
821        /**
822         * @param int $mode
823         *
824         * @return string|null
825         */
826        public static function SpeexBandModeLookup($mode) {
827                static $SpeexBandModeLookup = array();
828                if (empty($SpeexBandModeLookup)) {
829                        $SpeexBandModeLookup[0] = 'narrow';
830                        $SpeexBandModeLookup[1] = 'wide';
831                        $SpeexBandModeLookup[2] = 'ultra-wide';
832                }
833                return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
834        }
835
836        /**
837         * @param array $OggInfoArray
838         * @param int   $SegmentNumber
839         *
840         * @return int
841         */
842        public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
843                $segmentlength = 0;
844                for ($i = 0; $i < $SegmentNumber; $i++) {
845                        $segmentlength = 0;
846                        foreach ($OggInfoArray['segment_table'] as $key => $value) {
847                                $segmentlength += $value;
848                                if ($value < 255) {
849                                        break;
850                                }
851                        }
852                }
853                return $segmentlength;
854        }
855
856        /**
857         * @param int $nominal_bitrate
858         *
859         * @return float
860         */
861        public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
862
863                // decrease precision
864                $nominal_bitrate = $nominal_bitrate / 1000;
865
866                if ($nominal_bitrate < 128) {
867                        // q-1 to q4
868                        $qval = ($nominal_bitrate - 64) / 16;
869                } elseif ($nominal_bitrate < 256) {
870                        // q4 to q8
871                        $qval = $nominal_bitrate / 32;
872                } elseif ($nominal_bitrate < 320) {
873                        // q8 to q9
874                        $qval = ($nominal_bitrate + 256) / 64;
875                } else {
876                        // q9 to q10
877                        $qval = ($nominal_bitrate + 1300) / 180;
878                }
879                //return $qval; // 5.031324
880                //return intval($qval); // 5
881                return round($qval, 1); // 5 or 4.9
882        }
883
884        /**
885         * @param int $colorspace_id
886         *
887         * @return string|null
888         */
889        public static function TheoraColorSpace($colorspace_id) {
890                // http://www.theora.org/doc/Theora.pdf (table 6.3)
891                static $TheoraColorSpaceLookup = array();
892                if (empty($TheoraColorSpaceLookup)) {
893                        $TheoraColorSpaceLookup[0] = 'Undefined';
894                        $TheoraColorSpaceLookup[1] = 'Rec. 470M';
895                        $TheoraColorSpaceLookup[2] = 'Rec. 470BG';
896                        $TheoraColorSpaceLookup[3] = 'Reserved';
897                }
898                return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null);
899        }
900
901        /**
902         * @param int $pixelformat_id
903         *
904         * @return string|null
905         */
906        public static function TheoraPixelFormat($pixelformat_id) {
907                // http://www.theora.org/doc/Theora.pdf (table 6.4)
908                static $TheoraPixelFormatLookup = array();
909                if (empty($TheoraPixelFormatLookup)) {
910                        $TheoraPixelFormatLookup[0] = '4:2:0';
911                        $TheoraPixelFormatLookup[1] = 'Reserved';
912                        $TheoraPixelFormatLookup[2] = '4:2:2';
913                        $TheoraPixelFormatLookup[3] = '4:4:4';
914                }
915                return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null);
916        }
917
918}
Note: See TracBrowser for help on using the repository browser.