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