Completed
Pull Request — master (#2)
by Stephen
09:27
created

getid3_ogg   D

Complexity

Total Complexity 115

Size/Duplication

Total Lines 821
Duplicated Lines 7.92 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 65
loc 821
rs 4
c 0
b 0
f 0
wmc 115
lcom 1
cbo 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like getid3_ogg often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use getid3_ogg, and based on these observations, apply Extract Interface, too.

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