source: extensions/piwigo_videojs/include/getid3/module.audio.ogg.php @ 31945

Last change on this file since 31945 was 24676, checked in by ddtddt, 11 years ago

[extensions] - piwigo_videojs - add file for translate

File size: 29.9 KB
Line 
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
16getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
17
18class 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}
Note: See TracBrowser for help on using the repository browser.