Blame view

sources/3rdparty/getid3/module.audio.ogg.php 29.9 KB
03e52840d   Kload   Init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  <?php
  /////////////////////////////////////////////////////////////////
  /// getID3() by James Heinrich <info@getid3.org>               //
  //  available at http://getid3.sourceforge.net                 //
  //            or http://www.getid3.org                         //
  /////////////////////////////////////////////////////////////////
  // See readme.txt for more details                             //
  /////////////////////////////////////////////////////////////////
  //                                                             //
  // module.audio.ogg.php                                        //
  // module for analyzing Ogg Vorbis, OggFLAC and Speex files    //
  // dependencies: module.audio.flac.php                         //
  //                                                            ///
  /////////////////////////////////////////////////////////////////
  
  getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
  
  class getid3_ogg extends getid3_handler
  {
31b7f2792   Kload   Upgrade to ownclo...
20
21
  	// http://xiph.org/vorbis/doc/Vorbis_I_spec.html
  	public function Analyze() {
03e52840d   Kload   Init
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  		$info = &$this->getid3->info;
  
  		$info['fileformat'] = 'ogg';
  
  		// Warn about illegal tags - only vorbiscomments are allowed
  		if (isset($info['id3v2'])) {
  			$info['warning'][] = 'Illegal ID3v2 tag present.';
  		}
  		if (isset($info['id3v1'])) {
  			$info['warning'][] = 'Illegal ID3v1 tag present.';
  		}
  		if (isset($info['ape'])) {
  			$info['warning'][] = 'Illegal APE tag present.';
  		}
  
  
  		// Page 1 - Stream Header
31b7f2792   Kload   Upgrade to ownclo...
39
  		$this->fseek($info['avdataoffset']);
03e52840d   Kload   Init
40
41
42
  
  		$oggpageinfo = $this->ParseOggPageHeader();
  		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
31b7f2792   Kload   Upgrade to ownclo...
43
  		if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
03e52840d   Kload   Init
44
45
46
47
48
  			$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?)';
  			unset($info['fileformat']);
  			unset($info['ogg']);
  			return false;
  		}
31b7f2792   Kload   Upgrade to ownclo...
49
  		$filedata = $this->fread($oggpageinfo['page_length']);
03e52840d   Kload   Init
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
  		$filedataoffset = 0;
  
  		if (substr($filedata, 0, 4) == 'fLaC') {
  
  			$info['audio']['dataformat']   = 'flac';
  			$info['audio']['bitrate_mode'] = 'vbr';
  			$info['audio']['lossless']     = true;
  
  		} elseif (substr($filedata, 1, 6) == 'vorbis') {
  
  			$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
  
  		} elseif (substr($filedata, 0, 8) == 'Speex   ') {
  
  			// http://www.speex.org/manual/node10.html
  
  			$info['audio']['dataformat']   = 'speex';
  			$info['mime_type']             = 'audio/speex';
  			$info['audio']['bitrate_mode'] = 'abr';
  			$info['audio']['lossless']     = false;
  
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string']           =                              substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex   '
  			$filedataoffset += 8;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']          =                              substr($filedata, $filedataoffset, 20);
  			$filedataoffset += 20;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']                    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers']          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  			$filedataoffset += 4;
  
  			$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
  			$info['speex']['sample_rate']   = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
  			$info['speex']['channels']      = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
  			$info['speex']['vbr']           = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
  			$info['speex']['band_type']     = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
  
  			$info['audio']['sample_rate']   = $info['speex']['sample_rate'];
  			$info['audio']['channels']      = $info['speex']['channels'];
  			if ($info['speex']['vbr']) {
  				$info['audio']['bitrate_mode'] = 'vbr';
  			}
  
  
  		} elseif (substr($filedata, 0, 8) == "fishead\x00") {
  
  			// Ogg Skeleton version 3.0 Format Specification
  			// http://xiph.org/ogg/doc/skeleton.html
  			$filedataoffset += 8;
  			$info['ogg']['skeleton']['fishead']['raw']['version_major']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
  			$filedataoffset += 2;
  			$info['ogg']['skeleton']['fishead']['raw']['version_minor']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
  			$filedataoffset += 2;
  			$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
  			$filedataoffset += 8;
  			$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
  			$filedataoffset += 8;
  			$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
  			$filedataoffset += 8;
  			$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
  			$filedataoffset += 8;
  			$info['ogg']['skeleton']['fishead']['raw']['utc']                          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
  			$filedataoffset += 20;
  
  			$info['ogg']['skeleton']['fishead']['version']          = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
  			$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
  			$info['ogg']['skeleton']['fishead']['basetime']         = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']         / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
  			$info['ogg']['skeleton']['fishead']['utc']              = $info['ogg']['skeleton']['fishead']['raw']['utc'];
  
  
  			$counter = 0;
  			do {
  				$oggpageinfo = $this->ParseOggPageHeader();
  				$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
31b7f2792   Kload   Upgrade to ownclo...
145
146
  				$filedata = $this->fread($oggpageinfo['page_length']);
  				$this->fseek($oggpageinfo['page_end_offset']);
03e52840d   Kload   Init
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
  
  				if (substr($filedata, 0, 8) == "fisbone\x00") {
  
  					$filedataoffset = 8;
  					$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
  					$filedataoffset += 4;
  					$info['ogg']['skeleton']['fisbone']['raw']['serial_number']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
  					$filedataoffset += 4;
  					$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
  					$filedataoffset += 4;
  					$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
  					$filedataoffset += 8;
  					$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
  					$filedataoffset += 8;
  					$info['ogg']['skeleton']['fisbone']['raw']['basegranule']             = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
  					$filedataoffset += 8;
  					$info['ogg']['skeleton']['fisbone']['raw']['preroll']                 = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
  					$filedataoffset += 4;
  					$info['ogg']['skeleton']['fisbone']['raw']['granuleshift']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
  					$filedataoffset += 1;
  					$info['ogg']['skeleton']['fisbone']['raw']['padding']                 =                              substr($filedata, $filedataoffset,  3);
  					$filedataoffset += 3;
  
  				} elseif (substr($filedata, 1, 6) == 'theora') {
  
  					$info['video']['dataformat'] = 'theora';
31b7f2792   Kload   Upgrade to ownclo...
173
174
  					$info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']';
  					//break;
03e52840d   Kload   Init
175
176
177
178
179
180
  
  				} elseif (substr($filedata, 1, 6) == 'vorbis') {
  
  					$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
  
  				} else {
31b7f2792   Kload   Upgrade to ownclo...
181
182
  					$info['error'][] = 'unexpected';
  					//break;
03e52840d   Kload   Init
183
184
185
  				}
  			//} while ($oggpageinfo['page_seqno'] == 0);
  			} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
03e52840d   Kload   Init
186

31b7f2792   Kload   Upgrade to ownclo...
187
  			$this->fseek($oggpageinfo['page_start_offset']);
03e52840d   Kload   Init
188

31b7f2792   Kload   Upgrade to ownclo...
189
190
  			$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
  			//return false;
03e52840d   Kload   Init
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
  
  		} else {
  
  			$info['error'][] = 'Expecting either "Speex   " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
  			unset($info['ogg']);
  			unset($info['mime_type']);
  			return false;
  
  		}
  
  		// Page 2 - Comment Header
  		$oggpageinfo = $this->ParseOggPageHeader();
  		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  
  		switch ($info['audio']['dataformat']) {
  			case 'vorbis':
31b7f2792   Kload   Upgrade to ownclo...
207
  				$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
03e52840d   Kload   Init
208
209
  				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
  				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] =                              substr($filedata, 1, 6); // hard-coded to 'vorbis'
31b7f2792   Kload   Upgrade to ownclo...
210
  				$this->ParseVorbisComments();
03e52840d   Kload   Init
211
212
213
  				break;
  
  			case 'flac':
31b7f2792   Kload   Upgrade to ownclo...
214
215
  				$flac = new getid3_flac($this->getid3);
  				if (!$flac->parseMETAdata()) {
03e52840d   Kload   Init
216
217
218
  					$info['error'][] = 'Failed to parse FLAC headers';
  					return false;
  				}
31b7f2792   Kload   Upgrade to ownclo...
219
  				unset($flac);
03e52840d   Kload   Init
220
221
222
  				break;
  
  			case 'speex':
31b7f2792   Kload   Upgrade to ownclo...
223
224
  				$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
  				$this->ParseVorbisComments();
03e52840d   Kload   Init
225
  				break;
03e52840d   Kload   Init
226
  		}
03e52840d   Kload   Init
227
  		// Last Page - Number of Samples
03e52840d   Kload   Init
228
229
230
231
232
  		if (!getid3_lib::intValueSupported($info['avdataend'])) {
  
  			$info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
  
  		} else {
31b7f2792   Kload   Upgrade to ownclo...
233
234
  			$this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
  			$LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
03e52840d   Kload   Init
235
  			if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
31b7f2792   Kload   Upgrade to ownclo...
236
237
  				$this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
  				$info['avdataend'] = $this->ftell();
03e52840d   Kload   Init
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
  				$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
  				$info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
  				if ($info['ogg']['samples'] == 0) {
  					$info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
  					return false;
  				}
  				if (!empty($info['audio']['sample_rate'])) {
  					$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
  				}
  			}
  
  		}
  
  		if (!empty($info['ogg']['bitrate_average'])) {
  			$info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
  		} elseif (!empty($info['ogg']['bitrate_nominal'])) {
  			$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
  		} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
  			$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
  		}
  		if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
  			if ($info['audio']['bitrate'] == 0) {
  				$info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
  				return false;
  			}
  			$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
  		}
  
  		if (isset($info['ogg']['vendor'])) {
  			$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
  
  			// Vorbis only
  			if ($info['audio']['dataformat'] == 'vorbis') {
  
  				// Vorbis 1.0 starts with Xiph.Org
  				if  (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
  
  					if ($info['audio']['bitrate_mode'] == 'abr') {
  
  						// Set -b 128 on abr files
  						$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
  
  					} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
  						// Set -q N on vbr files
  						$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
  
  					}
  				}
  
  				if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
  					$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
  				}
  			}
  		}
  
  		return true;
  	}
31b7f2792   Kload   Upgrade to ownclo...
295
  	public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
03e52840d   Kload   Init
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
  		$info = &$this->getid3->info;
  		$info['audio']['dataformat'] = 'vorbis';
  		$info['audio']['lossless']   = false;
  
  		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  		$filedataoffset += 1;
  		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
  		$filedataoffset += 6;
  		$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  		$filedataoffset += 4;
  		$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  		$filedataoffset += 1;
  		$info['audio']['channels']       = $info['ogg']['numberofchannels'];
  		$info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  		$filedataoffset += 4;
  		if ($info['ogg']['samplerate'] == 0) {
  			$info['error'][] = 'Corrupt Ogg file: sample rate == zero';
  			return false;
  		}
  		$info['audio']['sample_rate']    = $info['ogg']['samplerate'];
  		$info['ogg']['samples']          = 0; // filled in later
  		$info['ogg']['bitrate_average']  = 0; // filled in later
  		$info['ogg']['bitrate_max']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  		$filedataoffset += 4;
  		$info['ogg']['bitrate_nominal']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  		$filedataoffset += 4;
  		$info['ogg']['bitrate_min']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  		$filedataoffset += 4;
  		$info['ogg']['blocksize_small']  = pow(2,  getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
  		$info['ogg']['blocksize_large']  = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
  		$info['ogg']['stop_bit']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
  
  		$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
  		if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
  			unset($info['ogg']['bitrate_max']);
  			$info['audio']['bitrate_mode'] = 'abr';
  		}
  		if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
  			unset($info['ogg']['bitrate_nominal']);
  		}
  		if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
  			unset($info['ogg']['bitrate_min']);
  			$info['audio']['bitrate_mode'] = 'abr';
  		}
  		return true;
  	}
31b7f2792   Kload   Upgrade to ownclo...
342
  	public function ParseOggPageHeader() {
03e52840d   Kload   Init
343
  		// http://xiph.org/ogg/vorbis/doc/framing.html
31b7f2792   Kload   Upgrade to ownclo...
344
  		$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
03e52840d   Kload   Init
345

31b7f2792   Kload   Upgrade to ownclo...
346
  		$filedata = $this->fread($this->getid3->fread_buffer_size());
03e52840d   Kload   Init
347
348
  		$filedataoffset = 0;
  		while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
31b7f2792   Kload   Upgrade to ownclo...
349
  			if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
03e52840d   Kload   Init
350
351
352
353
  				// should be found before here
  				return false;
  			}
  			if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
31b7f2792   Kload   Upgrade to ownclo...
354
  				if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
03e52840d   Kload   Init
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
  					// get some more data, unless eof, in which case fail
  					return false;
  				}
  			}
  		}
  		$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
  
  		$oggheader['stream_structver']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  		$filedataoffset += 1;
  		$oggheader['flags_raw']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  		$filedataoffset += 1;
  		$oggheader['flags']['fresh']    = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
  		$oggheader['flags']['bos']      = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
  		$oggheader['flags']['eos']      = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
  
  		$oggheader['pcm_abs_position']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  		$filedataoffset += 8;
  		$oggheader['stream_serialno']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  		$filedataoffset += 4;
  		$oggheader['page_seqno']        = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  		$filedataoffset += 4;
  		$oggheader['page_checksum']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  		$filedataoffset += 4;
  		$oggheader['page_segments']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  		$filedataoffset += 1;
  		$oggheader['page_length'] = 0;
  		for ($i = 0; $i < $oggheader['page_segments']; $i++) {
  			$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  			$filedataoffset += 1;
  			$oggheader['page_length'] += $oggheader['segment_table'][$i];
  		}
  		$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
  		$oggheader['page_end_offset']   = $oggheader['header_end_offset'] + $oggheader['page_length'];
31b7f2792   Kload   Upgrade to ownclo...
388
  		$this->fseek($oggheader['header_end_offset']);
03e52840d   Kload   Init
389
390
391
  
  		return $oggheader;
  	}
31b7f2792   Kload   Upgrade to ownclo...
392
393
      // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
  	public function ParseVorbisComments() {
03e52840d   Kload   Init
394
  		$info = &$this->getid3->info;
31b7f2792   Kload   Upgrade to ownclo...
395
  		$OriginalOffset = $this->ftell();
03e52840d   Kload   Init
396
397
398
399
400
  		$commentdataoffset = 0;
  		$VorbisCommentPage = 1;
  
  		switch ($info['audio']['dataformat']) {
  			case 'vorbis':
31b7f2792   Kload   Upgrade to ownclo...
401
  			case 'speex':
03e52840d   Kload   Init
402
  				$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset'];  // Second Ogg page, after header block
31b7f2792   Kload   Upgrade to ownclo...
403
  				$this->fseek($CommentStartOffset);
03e52840d   Kload   Init
404
  				$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
31b7f2792   Kload   Upgrade to ownclo...
405
  				$commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
03e52840d   Kload   Init
406

31b7f2792   Kload   Upgrade to ownclo...
407
408
409
  				if ($info['audio']['dataformat'] == 'vorbis') {
  					$commentdataoffset += (strlen('vorbis') + 1);
  				}
03e52840d   Kload   Init
410
411
412
413
  				break;
  
  			case 'flac':
  				$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
31b7f2792   Kload   Upgrade to ownclo...
414
415
  				$this->fseek($CommentStartOffset);
  				$commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
03e52840d   Kload   Init
416
417
418
419
  				break;
  
  			default:
  				return false;
03e52840d   Kload   Init
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
  		}
  
  		$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
  		$commentdataoffset += 4;
  
  		$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
  		$commentdataoffset += $VendorSize;
  
  		$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
  		$commentdataoffset += 4;
  		$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
  
  		$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
  		$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
  		for ($i = 0; $i < $CommentsCount; $i++) {
  
  			$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
31b7f2792   Kload   Upgrade to ownclo...
437
  			if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
03e52840d   Kload   Init
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
  				if ($oggpageinfo = $this->ParseOggPageHeader()) {
  					$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  
  					$VorbisCommentPage++;
  
  					// First, save what we haven't read yet
  					$AsYetUnusedData = substr($commentdata, $commentdataoffset);
  
  					// Then take that data off the end
  					$commentdata     = substr($commentdata, 0, $commentdataoffset);
  
  					// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
  					$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
  					$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
  
  					// Finally, stick the unused data back on the end
  					$commentdata .= $AsYetUnusedData;
31b7f2792   Kload   Upgrade to ownclo...
455
456
  					//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
  					$commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
03e52840d   Kload   Init
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
  				}
  
  			}
  			$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
  
  			// replace avdataoffset with position just after the last vorbiscomment
  			$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
  
  			$commentdataoffset += 4;
  			while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
  				if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
  					$info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
  					break 2;
  				}
  
  				$VorbisCommentPage++;
  
  				$oggpageinfo = $this->ParseOggPageHeader();
  				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  
  				// First, save what we haven't read yet
  				$AsYetUnusedData = substr($commentdata, $commentdataoffset);
  
  				// Then take that data off the end
  				$commentdata     = substr($commentdata, 0, $commentdataoffset);
  
  				// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
  				$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
  				$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
  
  				// Finally, stick the unused data back on the end
  				$commentdata .= $AsYetUnusedData;
31b7f2792   Kload   Upgrade to ownclo...
489
  				//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
03e52840d   Kload   Init
490
  				if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
31b7f2792   Kload   Upgrade to ownclo...
491
  					$info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
03e52840d   Kload   Init
492
493
  					break;
  				}
31b7f2792   Kload   Upgrade to ownclo...
494
  				$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
03e52840d   Kload   Init
495
  				if ($readlength <= 0) {
31b7f2792   Kload   Upgrade to ownclo...
496
  					$info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
03e52840d   Kload   Init
497
498
  					break;
  				}
31b7f2792   Kload   Upgrade to ownclo...
499
  				$commentdata .= $this->fread($readlength);
03e52840d   Kload   Init
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
  
  				//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
  			}
  			$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
  			$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
  			$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
  
  			if (!$commentstring) {
  
  				// no comment?
  				$info['warning'][] = 'Blank Ogg comment ['.$i.']';
  
  			} elseif (strstr($commentstring, '=')) {
  
  				$commentexploded = explode('=', $commentstring, 2);
31b7f2792   Kload   Upgrade to ownclo...
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
  				$ThisFileInfo_ogg_comments_raw[$i]['key']   = strtoupper($commentexploded[0]);
  				$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
  
  				if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
  
  					// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
  					// 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.
  					// http://flac.sourceforge.net/format.html#metadata_block_picture
  					$flac = new getid3_flac($this->getid3);
  					$flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
  					$flac->parsePICTURE();
  					$info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
  					unset($flac);
  
  				} elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
  
  					$data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
  					$this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
  					/** @todo use 'coverartmime' where available */
  					$imageinfo = getid3_lib::GetDataImageSize($data);
  					if ($imageinfo === false || !isset($imageinfo['mime'])) {
  						$this->warning('COVERART vorbiscomment tag contains invalid image');
  						continue;
03e52840d   Kload   Init
538
  					}
03e52840d   Kload   Init
539

31b7f2792   Kload   Upgrade to ownclo...
540
541
542
543
544
545
546
  					$ogg = new self($this->getid3);
  					$ogg->setStringMode($data);
  					$info['ogg']['comments']['picture'][] = array(
  						'image_mime' => $imageinfo['mime'],
  						'data'       => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
  					);
  					unset($ogg);
03e52840d   Kload   Init
547
  				} else {
03e52840d   Kload   Init
548

31b7f2792   Kload   Upgrade to ownclo...
549
  					$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
03e52840d   Kload   Init
550

31b7f2792   Kload   Upgrade to ownclo...
551
  				}
03e52840d   Kload   Init
552
553
554
555
556
557
  
  			} else {
  
  				$info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
  
  			}
31b7f2792   Kload   Upgrade to ownclo...
558
  			unset($ThisFileInfo_ogg_comments_raw[$i]);
03e52840d   Kload   Init
559
  		}
31b7f2792   Kload   Upgrade to ownclo...
560
  		unset($ThisFileInfo_ogg_comments_raw);
03e52840d   Kload   Init
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
  
  
  		// Replay Gain Adjustment
  		// http://privatewww.essex.ac.uk/~djmrob/replaygain/
  		if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
  			foreach ($info['ogg']['comments'] as $index => $commentvalue) {
  				switch ($index) {
  					case 'rg_audiophile':
  					case 'replaygain_album_gain':
  						$info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
  						unset($info['ogg']['comments'][$index]);
  						break;
  
  					case 'rg_radio':
  					case 'replaygain_track_gain':
  						$info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
  						unset($info['ogg']['comments'][$index]);
  						break;
  
  					case 'replaygain_album_peak':
  						$info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
  						unset($info['ogg']['comments'][$index]);
  						break;
  
  					case 'rg_peak':
  					case 'replaygain_track_peak':
  						$info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
  						unset($info['ogg']['comments'][$index]);
  						break;
  
  					case 'replaygain_reference_loudness':
  						$info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
  						unset($info['ogg']['comments'][$index]);
  						break;
  
  					default:
  						// do nothing
  						break;
  				}
  			}
  		}
31b7f2792   Kload   Upgrade to ownclo...
602
  		$this->fseek($OriginalOffset);
03e52840d   Kload   Init
603
604
605
  
  		return true;
  	}
31b7f2792   Kload   Upgrade to ownclo...
606
  	public static function SpeexBandModeLookup($mode) {
03e52840d   Kload   Init
607
608
609
610
611
612
613
614
  		static $SpeexBandModeLookup = array();
  		if (empty($SpeexBandModeLookup)) {
  			$SpeexBandModeLookup[0] = 'narrow';
  			$SpeexBandModeLookup[1] = 'wide';
  			$SpeexBandModeLookup[2] = 'ultra-wide';
  		}
  		return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
  	}
31b7f2792   Kload   Upgrade to ownclo...
615
  	public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
03e52840d   Kload   Init
616
617
618
619
620
621
622
623
624
625
626
  		for ($i = 0; $i < $SegmentNumber; $i++) {
  			$segmentlength = 0;
  			foreach ($OggInfoArray['segment_table'] as $key => $value) {
  				$segmentlength += $value;
  				if ($value < 255) {
  					break;
  				}
  			}
  		}
  		return $segmentlength;
  	}
31b7f2792   Kload   Upgrade to ownclo...
627
  	public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
03e52840d   Kload   Init
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
  
  		// decrease precision
  		$nominal_bitrate = $nominal_bitrate / 1000;
  
  		if ($nominal_bitrate < 128) {
  			// q-1 to q4
  			$qval = ($nominal_bitrate - 64) / 16;
  		} elseif ($nominal_bitrate < 256) {
  			// q4 to q8
  			$qval = $nominal_bitrate / 32;
  		} elseif ($nominal_bitrate < 320) {
  			// q8 to q9
  			$qval = ($nominal_bitrate + 256) / 64;
  		} else {
  			// q9 to q10
  			$qval = ($nominal_bitrate + 1300) / 180;
  		}
  		//return $qval; // 5.031324
  		//return intval($qval); // 5
  		return round($qval, 1); // 5 or 4.9
  	}
  
  }