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