Diff: STRATO-apps/wordpress_03/app/wp-includes/ID3/module.audio.ogg.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
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'] = getid3_lib::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'], $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']);
214 + $info['ogg']['skeleton']['fishead']['basetime'] = getid3_lib::SafeDiv($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'] = getid3_lib::SafeDiv($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 + if (substr($LastChunkOfOgg, 13, 8) === "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") {
354 + // https://github.com/JamesHeinrich/getID3/issues/450
355 + // "Sometimes, Opus encoders (WhatsApp voice registrations and others) add a special last header with a granule duration of 0xFFFFFFFFFFFFFF.
356 + // This value indicates "this is the end," but must be ignored; otherwise, it makes calculations wrong."
357 + $LastOggSpostion = strpos($LastChunkOfOgg, 'SggO', $LastOggSpostion + 1);
358 + }
359 + $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
360 + $info['avdataend'] = $this->ftell();
361 + $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
362 + $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
363 + if ($info['ogg']['samples'] == 0) {
364 + $this->error('Corrupt Ogg file: eos.number of samples == zero');
365 + return false;
366 + }
367 + if (!empty($info['audio']['sample_rate'])) {
368 + $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) * $info['audio']['sample_rate'] / $info['ogg']['samples'];
369 + }
370 + }
371 +
372 + }
373 +
374 + if (!empty($info['ogg']['bitrate_average'])) {
375 + $info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
376 + } elseif (!empty($info['ogg']['bitrate_nominal'])) {
377 + $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
378 + } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
379 + $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
380 + }
381 + if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
382 + if ($info['audio']['bitrate'] == 0) {
383 + $this->error('Corrupt Ogg file: bitrate_audio == zero');
384 + return false;
385 + }
386 + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
387 + }
388 +
389 + if (isset($info['ogg']['vendor'])) {
390 + $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
391 +
392 + // Vorbis only
393 + if ($info['audio']['dataformat'] == 'vorbis') {
394 +
395 + // Vorbis 1.0 starts with Xiph.Org
396 + if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
397 +
398 + if ($info['audio']['bitrate_mode'] == 'abr') {
399 +
400 + // Set -b 128 on abr files
401 + $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
402 +
403 + } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
404 + // Set -q N on vbr files
405 + $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
406 +
407 + }
408 + }
409 +
410 + if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
411 + $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
412 + }
413 + }
414 + }
415 +
416 + return true;
417 + }
418 +
419 + /**
420 + * @param string $filedata
421 + * @param int $filedataoffset
422 + * @param array $oggpageinfo
423 + *
424 + * @return bool
425 + */
426 + public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
427 + $info = &$this->getid3->info;
428 + $info['audio']['dataformat'] = 'vorbis';
429 + $info['audio']['lossless'] = false;
430 +
431 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
432 + $filedataoffset += 1;
433 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
434 + $filedataoffset += 6;
435 + $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
436 + $filedataoffset += 4;
437 + $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
438 + $filedataoffset += 1;
439 + $info['audio']['channels'] = $info['ogg']['numberofchannels'];
440 + $info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
441 + $filedataoffset += 4;
442 + if ($info['ogg']['samplerate'] == 0) {
443 + $this->error('Corrupt Ogg file: sample rate == zero');
444 + return false;
445 + }
446 + $info['audio']['sample_rate'] = $info['ogg']['samplerate'];
447 + $info['ogg']['samples'] = 0; // filled in later
448 + $info['ogg']['bitrate_average'] = 0; // filled in later
449 + $info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
450 + $filedataoffset += 4;
451 + $info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
452 + $filedataoffset += 4;
453 + $info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
454 + $filedataoffset += 4;
455 + $info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
456 + $info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
457 + $info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
458 +
459 + $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
460 + if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
461 + unset($info['ogg']['bitrate_max']);
462 + $info['audio']['bitrate_mode'] = 'abr';
463 + }
464 + if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
465 + unset($info['ogg']['bitrate_nominal']);
466 + }
467 + if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
468 + unset($info['ogg']['bitrate_min']);
469 + $info['audio']['bitrate_mode'] = 'abr';
470 + }
471 + return true;
472 + }
473 +
474 + /**
475 + * @link http://tools.ietf.org/html/draft-ietf-codec-oggopus-03
476 + *
477 + * @param string $filedata
478 + * @param int $filedataoffset
479 + * @param array $oggpageinfo
480 + *
481 + * @return bool
482 + */
483 + public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
484 + $info = &$this->getid3->info;
485 + $info['audio']['dataformat'] = 'opus';
486 + $info['mime_type'] = 'audio/ogg; codecs=opus';
487 +
488 + /** @todo find a usable way to detect abr (vbr that is padded to be abr) */
489 + $info['audio']['bitrate_mode'] = 'vbr';
490 +
491 + $info['audio']['lossless'] = false;
492 +
493 + $info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead'
494 + $filedataoffset += 8;
495 + $info['ogg']['pageheader']['opus']['version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
496 + $filedataoffset += 1;
497 +
498 + if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) {
499 + $this->error('Unknown opus version number (only accepting 1-15)');
500 + return false;
501 + }
502 +
503 + $info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
504 + $filedataoffset += 1;
505 +
506 + if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) {
507 + $this->error('Invalid channel count in opus header (must not be zero)');
508 + return false;
509 + }
510 +
511 + $info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
512 + $filedataoffset += 2;
513 +
514 + $info['ogg']['pageheader']['opus']['input_sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
515 + $filedataoffset += 4;
516 +
517 + //$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
518 + //$filedataoffset += 2;
519 +
520 + //$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
521 + //$filedataoffset += 1;
522 +
523 + $info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version'];
524 + $info['opus']['sample_rate_input'] = $info['ogg']['pageheader']['opus']['input_sample_rate'];
525 + $info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count'];
526 +
527 + $info['audio']['channels'] = $info['opus']['out_channel_count'];
528 + $info['audio']['sample_rate_input'] = $info['opus']['sample_rate_input'];
529 + $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
530 + return true;
531 + }
532 +
533 + /**
534 + * @return array|false
535 + */
536 + public function ParseOggPageHeader() {
537 + // http://xiph.org/ogg/vorbis/doc/framing.html
538 + $oggheader = array();
539 + $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
540 +
541 + $filedata = $this->fread($this->getid3->fread_buffer_size());
542 + $filedataoffset = 0;
543 + while (substr($filedata, $filedataoffset++, 4) != 'OggS') {
544 + if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
545 + // should be found before here
546 + return false;
547 + }
548 + if (($filedataoffset + 28) > strlen($filedata)) {
549 + if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) {
550 + // get some more data, unless eof, in which case fail
551 + return false;
552 + }
553 + }
554 + }
555 + $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
556 +
557 + $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
558 + $filedataoffset += 1;
559 + $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
560 + $filedataoffset += 1;
561 + $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
562 + $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
563 + $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
564 +
565 + $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
566 + $filedataoffset += 8;
567 + $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
568 + $filedataoffset += 4;
569 + $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
570 + $filedataoffset += 4;
571 + $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
572 + $filedataoffset += 4;
573 + $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
574 + $filedataoffset += 1;
575 + $oggheader['page_length'] = 0;
576 + for ($i = 0; $i < $oggheader['page_segments']; $i++) {
577 + $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
578 + $filedataoffset += 1;
579 + $oggheader['page_length'] += $oggheader['segment_table'][$i];
580 + }
581 + $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
582 + $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length'];
583 + $this->fseek($oggheader['header_end_offset']);
584 +
585 + return $oggheader;
586 + }
587 +
588 + /**
589 + * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
590 + *
591 + * @return bool
592 + */
593 + public function ParseVorbisComments() {
594 + $info = &$this->getid3->info;
595 +
596 + $OriginalOffset = $this->ftell();
597 + $commentdata = null;
598 + $commentdataoffset = 0;
599 + $VorbisCommentPage = 1;
600 + $CommentStartOffset = 0;
601 +
602 + switch ($info['audio']['dataformat']) {
603 + case 'vorbis':
604 + case 'speex':
605 + case 'opus':
606 + $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
607 + $this->fseek($CommentStartOffset);
608 + $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
609 + $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
610 +
611 + if ($info['audio']['dataformat'] == 'vorbis') {
612 + $commentdataoffset += (strlen('vorbis') + 1);
613 + }
614 + else if ($info['audio']['dataformat'] == 'opus') {
615 + $commentdataoffset += strlen('OpusTags');
616 + }
617 +
618 + break;
619 +
620 + case 'flac':
621 + $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
622 + $this->fseek($CommentStartOffset);
623 + $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
624 + break;
625 +
626 + default:
627 + return false;
628 + }
629 +
630 + $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
631 + $commentdataoffset += 4;
632 +
633 + $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
634 + $commentdataoffset += $VendorSize;
635 +
636 + $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
637 + $commentdataoffset += 4;
638 + $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
639 +
640 + $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
641 + $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
642 + for ($i = 0; $i < $CommentsCount; $i++) {
643 +
644 + if ($i >= 10000) {
645 + // https://github.com/owncloud/music/issues/212#issuecomment-43082336
646 + $this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments');
647 + break;
648 + }
649 +
650 + $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
651 +
652 + if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
653 + if ($oggpageinfo = $this->ParseOggPageHeader()) {
654 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
655 +
656 + $VorbisCommentPage++;
657 +
658 + // First, save what we haven't read yet
659 + $AsYetUnusedData = substr($commentdata, $commentdataoffset);
660 +
661 + // Then take that data off the end
662 + $commentdata = substr($commentdata, 0, $commentdataoffset);
663 +
664 + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
665 + $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
666 + $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
667 +
668 + // Finally, stick the unused data back on the end
669 + $commentdata .= $AsYetUnusedData;
670 +
671 + //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
672 + $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
673 + }
674 +
675 + }
676 + $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
677 +
678 + // replace avdataoffset with position just after the last vorbiscomment
679 + $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
680 +
681 + $commentdataoffset += 4;
682 + while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
683 + if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
684 + $this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments');
685 + break 2;
686 + }
687 +
688 + $VorbisCommentPage++;
689 +
690 + if ($oggpageinfo = $this->ParseOggPageHeader()) {
691 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
692 +
693 + // First, save what we haven't read yet
694 + $AsYetUnusedData = substr($commentdata, $commentdataoffset);
695 +
696 + // Then take that data off the end
697 + $commentdata = substr($commentdata, 0, $commentdataoffset);
698 +
699 + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
700 + $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
701 + $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
702 +
703 + // Finally, stick the unused data back on the end
704 + $commentdata .= $AsYetUnusedData;
705 +
706 + //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
707 + if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
708 + $this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
709 + break;
710 + }
711 + $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
712 + if ($readlength <= 0) {
713 + $this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
714 + break;
715 + }
716 + $commentdata .= $this->fread($readlength);
717 +
718 + //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
719 + } else {
720 + $this->warning('failed to ParseOggPageHeader() at offset '.$this->ftell());
721 + break;
722 + }
723 + }
724 + $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
725 + $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
726 + $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
727 +
728 + if (!$commentstring) {
729 +
730 + // no comment?
731 + $this->warning('Blank Ogg comment ['.$i.']');
732 +
733 + } elseif (strstr($commentstring, '=')) {
734 +
735 + $commentexploded = explode('=', $commentstring, 2);
736 + $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]);
737 + $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
738 +
739 + if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
740 +
741 + // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
742 + // 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.
743 + // http://flac.sourceforge.net/format.html#metadata_block_picture
744 + $flac = new getid3_flac($this->getid3);
745 + $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
746 + $flac->parsePICTURE();
747 + $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
748 + unset($flac);
749 +
750 + } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
751 +
752 + $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
753 + $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
754 + /** @todo use 'coverartmime' where available */
755 + $imageinfo = getid3_lib::GetDataImageSize($data);
756 + if ($imageinfo === false || !isset($imageinfo['mime'])) {
757 + $this->warning('COVERART vorbiscomment tag contains invalid image');
758 + continue;
759 + }
760 +
761 + $ogg = new self($this->getid3);
762 + $ogg->setStringMode($data);
763 + $info['ogg']['comments']['picture'][] = array(
764 + 'image_mime' => $imageinfo['mime'],
765 + 'datalength' => strlen($data),
766 + 'picturetype' => 'cover art',
767 + 'image_height' => $imageinfo['height'],
768 + 'image_width' => $imageinfo['width'],
769 + 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
770 + );
771 + unset($ogg);
772 +
773 + } else {
774 +
775 + $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
776 +
777 + }
778 +
779 + } else {
780 +
781 + $this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring);
782 +
783 + }
784 + unset($ThisFileInfo_ogg_comments_raw[$i]);
785 + }
786 + unset($ThisFileInfo_ogg_comments_raw);
787 +
788 +
789 + // Replay Gain Adjustment
790 + // http://privatewww.essex.ac.uk/~djmrob/replaygain/
791 + if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
792 + foreach ($info['ogg']['comments'] as $index => $commentvalue) {
793 + switch ($index) {
794 + case 'rg_audiophile':
795 + case 'replaygain_album_gain':
796 + $info['replay_gain']['album']['adjustment'] = (float) $commentvalue[0];
797 + unset($info['ogg']['comments'][$index]);
798 + break;
799 +
800 + case 'rg_radio':
801 + case 'replaygain_track_gain':
802 + $info['replay_gain']['track']['adjustment'] = (float) $commentvalue[0];
803 + unset($info['ogg']['comments'][$index]);
804 + break;
805 +
806 + case 'replaygain_album_peak':
807 + $info['replay_gain']['album']['peak'] = (float) $commentvalue[0];
808 + unset($info['ogg']['comments'][$index]);
809 + break;
810 +
811 + case 'rg_peak':
812 + case 'replaygain_track_peak':
813 + $info['replay_gain']['track']['peak'] = (float) $commentvalue[0];
814 + unset($info['ogg']['comments'][$index]);
815 + break;
816 +
817 + case 'replaygain_reference_loudness':
818 + $info['replay_gain']['reference_volume'] = (float) $commentvalue[0];
819 + unset($info['ogg']['comments'][$index]);
820 + break;
821 +
822 + default:
823 + // do nothing
824 + break;
825 + }
826 + }
827 + }
828 +
829 + $this->fseek($OriginalOffset);
830 +
831 + return true;
832 + }
833 +
834 + /**
835 + * @param int $mode
836 + *
837 + * @return string|null
838 + */
839 + public static function SpeexBandModeLookup($mode) {
840 + static $SpeexBandModeLookup = array();
841 + if (empty($SpeexBandModeLookup)) {
842 + $SpeexBandModeLookup[0] = 'narrow';
843 + $SpeexBandModeLookup[1] = 'wide';
844 + $SpeexBandModeLookup[2] = 'ultra-wide';
845 + }
846 + return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
847 + }
848 +
849 + /**
850 + * @param array $OggInfoArray
851 + * @param int $SegmentNumber
852 + *
853 + * @return int
854 + */
855 + public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
856 + $segmentlength = 0;
857 + for ($i = 0; $i < $SegmentNumber; $i++) {
858 + $segmentlength = 0;
859 + foreach ($OggInfoArray['segment_table'] as $key => $value) {
860 + $segmentlength += $value;
861 + if ($value < 255) {
862 + break;
863 + }
864 + }
865 + }
866 + return $segmentlength;
867 + }
868 +
869 + /**
870 + * @param int $nominal_bitrate
871 + *
872 + * @return float
873 + */
874 + public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
875 +
876 + // decrease precision
877 + $nominal_bitrate = $nominal_bitrate / 1000;
878 +
879 + if ($nominal_bitrate < 128) {
880 + // q-1 to q4
881 + $qval = ($nominal_bitrate - 64) / 16;
882 + } elseif ($nominal_bitrate < 256) {
883 + // q4 to q8
884 + $qval = $nominal_bitrate / 32;
885 + } elseif ($nominal_bitrate < 320) {
886 + // q8 to q9
887 + $qval = ($nominal_bitrate + 256) / 64;
888 + } else {
889 + // q9 to q10
890 + $qval = ($nominal_bitrate + 1300) / 180;
891 + }
892 + //return $qval; // 5.031324
893 + //return intval($qval); // 5
894 + return round($qval, 1); // 5 or 4.9
895 + }
896 +
897 + /**
898 + * @param int $colorspace_id
899 + *
900 + * @return string|null
901 + */
902 + public static function TheoraColorSpace($colorspace_id) {
903 + // http://www.theora.org/doc/Theora.pdf (table 6.3)
904 + static $TheoraColorSpaceLookup = array();
905 + if (empty($TheoraColorSpaceLookup)) {
906 + $TheoraColorSpaceLookup[0] = 'Undefined';
907 + $TheoraColorSpaceLookup[1] = 'Rec. 470M';
908 + $TheoraColorSpaceLookup[2] = 'Rec. 470BG';
909 + $TheoraColorSpaceLookup[3] = 'Reserved';
910 + }
911 + return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null);
912 + }
913 +
914 + /**
915 + * @param int $pixelformat_id
916 + *
917 + * @return string|null
918 + */
919 + public static function TheoraPixelFormat($pixelformat_id) {
920 + // http://www.theora.org/doc/Theora.pdf (table 6.4)
921 + static $TheoraPixelFormatLookup = array();
922 + if (empty($TheoraPixelFormatLookup)) {
923 + $TheoraPixelFormatLookup[0] = '4:2:0';
924 + $TheoraPixelFormatLookup[1] = 'Reserved';
925 + $TheoraPixelFormatLookup[2] = '4:2:2';
926 + $TheoraPixelFormatLookup[3] = '4:4:4';
927 + }
928 + return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null);
929 + }
930 +
931 + }
932 +