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