2
/////////////////////////////////////////////////////////////////
3
/// getID3() by James Heinrich <info@getid3.org> //
4
// available at http://getid3.sourceforge.net //
5
// or http://www.getid3.org //
6
/////////////////////////////////////////////////////////////////
7
// See readme.txt for more details //
8
/////////////////////////////////////////////////////////////////
10
// module.audio.ogg.php //
11
// module for analyzing Ogg Vorbis, OggFLAC and Speex files //
12
// dependencies: module.audio.flac.php //
14
/////////////////////////////////////////////////////////////////
16
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
18
class getid3_ogg extends getid3_handler
20
// http://xiph.org/vorbis/doc/Vorbis_I_spec.html
21
public function Analyze() {
22
$info = &$this->getid3->info;
24
$info['fileformat'] = 'ogg';
26
// Warn about illegal tags - only vorbiscomments are allowed
27
if (isset($info['id3v2'])) {
28
$info['warning'][] = 'Illegal ID3v2 tag present.';
30
if (isset($info['id3v1'])) {
31
$info['warning'][] = 'Illegal ID3v1 tag present.';
33
if (isset($info['ape'])) {
34
$info['warning'][] = 'Illegal APE tag present.';
38
// Page 1 - Stream Header
40
$this->fseek($info['avdataoffset']);
42
$oggpageinfo = $this->ParseOggPageHeader();
43
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
45
if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
46
$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?)';
47
unset($info['fileformat']);
52
$filedata = $this->fread($oggpageinfo['page_length']);
55
if (substr($filedata, 0, 4) == 'fLaC') {
57
$info['audio']['dataformat'] = 'flac';
58
$info['audio']['bitrate_mode'] = 'vbr';
59
$info['audio']['lossless'] = true;
61
} elseif (substr($filedata, 1, 6) == 'vorbis') {
63
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
65
} elseif (substr($filedata, 0, 8) == 'Speex ') {
67
// http://www.speex.org/manual/node10.html
69
$info['audio']['dataformat'] = 'speex';
70
$info['mime_type'] = 'audio/speex';
71
$info['audio']['bitrate_mode'] = 'abr';
72
$info['audio']['lossless'] = false;
74
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex '
76
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20);
77
$filedataoffset += 20;
78
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
80
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
82
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
84
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
86
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
88
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
90
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
92
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
94
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
96
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
98
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
100
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
101
$filedataoffset += 4;
102
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
103
$filedataoffset += 4;
105
$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
106
$info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
107
$info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
108
$info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
109
$info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
111
$info['audio']['sample_rate'] = $info['speex']['sample_rate'];
112
$info['audio']['channels'] = $info['speex']['channels'];
113
if ($info['speex']['vbr']) {
114
$info['audio']['bitrate_mode'] = 'vbr';
118
} elseif (substr($filedata, 0, 8) == "fishead\x00") {
120
// Ogg Skeleton version 3.0 Format Specification
121
// http://xiph.org/ogg/doc/skeleton.html
122
$filedataoffset += 8;
123
$info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
124
$filedataoffset += 2;
125
$info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
126
$filedataoffset += 2;
127
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
128
$filedataoffset += 8;
129
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
130
$filedataoffset += 8;
131
$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
132
$filedataoffset += 8;
133
$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
134
$filedataoffset += 8;
135
$info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
136
$filedataoffset += 20;
138
$info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
139
$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
140
$info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
141
$info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc'];
146
$oggpageinfo = $this->ParseOggPageHeader();
147
$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
148
$filedata = $this->fread($oggpageinfo['page_length']);
149
$this->fseek($oggpageinfo['page_end_offset']);
151
if (substr($filedata, 0, 8) == "fisbone\x00") {
154
$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
155
$filedataoffset += 4;
156
$info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
157
$filedataoffset += 4;
158
$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
159
$filedataoffset += 4;
160
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
161
$filedataoffset += 8;
162
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
163
$filedataoffset += 8;
164
$info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
165
$filedataoffset += 8;
166
$info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
167
$filedataoffset += 4;
168
$info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
169
$filedataoffset += 1;
170
$info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3);
171
$filedataoffset += 3;
173
} elseif (substr($filedata, 1, 6) == 'theora') {
175
$info['video']['dataformat'] = 'theora';
176
$info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']';
179
} elseif (substr($filedata, 1, 6) == 'vorbis') {
181
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
184
$info['error'][] = 'unexpected';
187
//} while ($oggpageinfo['page_seqno'] == 0);
188
} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
190
$this->fseek($oggpageinfo['page_start_offset']);
192
$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
197
$info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
199
unset($info['mime_type']);
204
// Page 2 - Comment Header
205
$oggpageinfo = $this->ParseOggPageHeader();
206
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
208
switch ($info['audio']['dataformat']) {
210
$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
211
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
212
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis'
214
$this->ParseVorbisComments();
218
$flac = new getid3_flac($this->getid3);
219
if (!$flac->parseMETAdata()) {
220
$info['error'][] = 'Failed to parse FLAC headers';
227
$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
228
$this->ParseVorbisComments();
233
// Last Page - Number of Samples
234
if (!getid3_lib::intValueSupported($info['avdataend'])) {
236
$info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
240
$this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
241
$LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
242
if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
243
$this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
244
$info['avdataend'] = $this->ftell();
245
$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
246
$info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
247
if ($info['ogg']['samples'] == 0) {
248
$info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
251
if (!empty($info['audio']['sample_rate'])) {
252
$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
258
if (!empty($info['ogg']['bitrate_average'])) {
259
$info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
260
} elseif (!empty($info['ogg']['bitrate_nominal'])) {
261
$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
262
} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
263
$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
265
if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
266
if ($info['audio']['bitrate'] == 0) {
267
$info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
270
$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
273
if (isset($info['ogg']['vendor'])) {
274
$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
277
if ($info['audio']['dataformat'] == 'vorbis') {
279
// Vorbis 1.0 starts with Xiph.Org
280
if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
282
if ($info['audio']['bitrate_mode'] == 'abr') {
284
// Set -b 128 on abr files
285
$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
287
} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
288
// Set -q N on vbr files
289
$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
294
if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
295
$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
303
public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
304
$info = &$this->getid3->info;
305
$info['audio']['dataformat'] = 'vorbis';
306
$info['audio']['lossless'] = false;
308
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
309
$filedataoffset += 1;
310
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
311
$filedataoffset += 6;
312
$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
313
$filedataoffset += 4;
314
$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
315
$filedataoffset += 1;
316
$info['audio']['channels'] = $info['ogg']['numberofchannels'];
317
$info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
318
$filedataoffset += 4;
319
if ($info['ogg']['samplerate'] == 0) {
320
$info['error'][] = 'Corrupt Ogg file: sample rate == zero';
323
$info['audio']['sample_rate'] = $info['ogg']['samplerate'];
324
$info['ogg']['samples'] = 0; // filled in later
325
$info['ogg']['bitrate_average'] = 0; // filled in later
326
$info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
327
$filedataoffset += 4;
328
$info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
329
$filedataoffset += 4;
330
$info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
331
$filedataoffset += 4;
332
$info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
333
$info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
334
$info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
336
$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
337
if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
338
unset($info['ogg']['bitrate_max']);
339
$info['audio']['bitrate_mode'] = 'abr';
341
if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
342
unset($info['ogg']['bitrate_nominal']);
344
if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
345
unset($info['ogg']['bitrate_min']);
346
$info['audio']['bitrate_mode'] = 'abr';
351
public function ParseOggPageHeader() {
352
// http://xiph.org/ogg/vorbis/doc/framing.html
353
$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
355
$filedata = $this->fread($this->getid3->fread_buffer_size());
357
while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
358
if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
359
// should be found before here
362
if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
363
if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
364
// get some more data, unless eof, in which case fail
369
$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
371
$oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
372
$filedataoffset += 1;
373
$oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
374
$filedataoffset += 1;
375
$oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
376
$oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
377
$oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
379
$oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
380
$filedataoffset += 8;
381
$oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
382
$filedataoffset += 4;
383
$oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
384
$filedataoffset += 4;
385
$oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
386
$filedataoffset += 4;
387
$oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
388
$filedataoffset += 1;
389
$oggheader['page_length'] = 0;
390
for ($i = 0; $i < $oggheader['page_segments']; $i++) {
391
$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
392
$filedataoffset += 1;
393
$oggheader['page_length'] += $oggheader['segment_table'][$i];
395
$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
396
$oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length'];
397
$this->fseek($oggheader['header_end_offset']);
402
// http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
403
public function ParseVorbisComments() {
404
$info = &$this->getid3->info;
406
$OriginalOffset = $this->ftell();
407
$commentdataoffset = 0;
408
$VorbisCommentPage = 1;
410
switch ($info['audio']['dataformat']) {
413
$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
414
$this->fseek($CommentStartOffset);
415
$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
416
$commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
418
if ($info['audio']['dataformat'] == 'vorbis') {
419
$commentdataoffset += (strlen('vorbis') + 1);
424
$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
425
$this->fseek($CommentStartOffset);
426
$commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
433
$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
434
$commentdataoffset += 4;
436
$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
437
$commentdataoffset += $VendorSize;
439
$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
440
$commentdataoffset += 4;
441
$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
443
$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
444
$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
445
for ($i = 0; $i < $CommentsCount; $i++) {
447
$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
449
if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
450
if ($oggpageinfo = $this->ParseOggPageHeader()) {
451
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
453
$VorbisCommentPage++;
455
// First, save what we haven't read yet
456
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
458
// Then take that data off the end
459
$commentdata = substr($commentdata, 0, $commentdataoffset);
461
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
462
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
463
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
465
// Finally, stick the unused data back on the end
466
$commentdata .= $AsYetUnusedData;
468
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
469
$commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
473
$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
475
// replace avdataoffset with position just after the last vorbiscomment
476
$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
478
$commentdataoffset += 4;
479
while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
480
if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
481
$info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
485
$VorbisCommentPage++;
487
$oggpageinfo = $this->ParseOggPageHeader();
488
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
490
// First, save what we haven't read yet
491
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
493
// Then take that data off the end
494
$commentdata = substr($commentdata, 0, $commentdataoffset);
496
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
497
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
498
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
500
// Finally, stick the unused data back on the end
501
$commentdata .= $AsYetUnusedData;
503
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
504
if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
505
$info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
508
$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
509
if ($readlength <= 0) {
510
$info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
513
$commentdata .= $this->fread($readlength);
515
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
517
$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
518
$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
519
$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
521
if (!$commentstring) {
524
$info['warning'][] = 'Blank Ogg comment ['.$i.']';
526
} elseif (strstr($commentstring, '=')) {
528
$commentexploded = explode('=', $commentstring, 2);
529
$ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]);
530
$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
532
if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
534
// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
535
// 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.
536
// http://flac.sourceforge.net/format.html#metadata_block_picture
537
$flac = new getid3_flac($this->getid3);
538
$flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
539
$flac->parsePICTURE();
540
$info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
543
} elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
545
$data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
546
$this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
547
/** @todo use 'coverartmime' where available */
548
$imageinfo = getid3_lib::GetDataImageSize($data);
549
if ($imageinfo === false || !isset($imageinfo['mime'])) {
550
$this->warning('COVERART vorbiscomment tag contains invalid image');
554
$ogg = new self($this->getid3);
555
$ogg->setStringMode($data);
556
$info['ogg']['comments']['picture'][] = array(
557
'image_mime' => $imageinfo['mime'],
558
'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
564
$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
570
$info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
573
unset($ThisFileInfo_ogg_comments_raw[$i]);
575
unset($ThisFileInfo_ogg_comments_raw);
578
// Replay Gain Adjustment
579
// http://privatewww.essex.ac.uk/~djmrob/replaygain/
580
if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
581
foreach ($info['ogg']['comments'] as $index => $commentvalue) {
583
case 'rg_audiophile':
584
case 'replaygain_album_gain':
585
$info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
586
unset($info['ogg']['comments'][$index]);
590
case 'replaygain_track_gain':
591
$info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
592
unset($info['ogg']['comments'][$index]);
595
case 'replaygain_album_peak':
596
$info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
597
unset($info['ogg']['comments'][$index]);
601
case 'replaygain_track_peak':
602
$info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
603
unset($info['ogg']['comments'][$index]);
606
case 'replaygain_reference_loudness':
607
$info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
608
unset($info['ogg']['comments'][$index]);
618
$this->fseek($OriginalOffset);
623
public static function SpeexBandModeLookup($mode) {
624
static $SpeexBandModeLookup = array();
625
if (empty($SpeexBandModeLookup)) {
626
$SpeexBandModeLookup[0] = 'narrow';
627
$SpeexBandModeLookup[1] = 'wide';
628
$SpeexBandModeLookup[2] = 'ultra-wide';
630
return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
634
public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
635
for ($i = 0; $i < $SegmentNumber; $i++) {
637
foreach ($OggInfoArray['segment_table'] as $key => $value) {
638
$segmentlength += $value;
644
return $segmentlength;
648
public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
650
// decrease precision
651
$nominal_bitrate = $nominal_bitrate / 1000;
653
if ($nominal_bitrate < 128) {
655
$qval = ($nominal_bitrate - 64) / 16;
656
} elseif ($nominal_bitrate < 256) {
658
$qval = $nominal_bitrate / 32;
659
} elseif ($nominal_bitrate < 320) {
661
$qval = ($nominal_bitrate + 256) / 64;
664
$qval = ($nominal_bitrate + 1300) / 180;
666
//return $qval; // 5.031324
667
//return intval($qval); // 5
668
return round($qval, 1); // 5 or 4.9