ILIAS  release_5-2 Revision v5.2.25-18-g3f80b82851
GetId3\Module\Audio\Ogg Class Reference

GetId3() by James Heinrich info@.nosp@m.geti.nosp@m.d3.or.nosp@m.g //. More...

+ Inheritance diagram for GetId3\Module\Audio\Ogg:
+ Collaboration diagram for GetId3\Module\Audio\Ogg:

Public Member Functions

 analyze ()
 
 ParseVorbisPageHeader (&$filedata, &$filedataoffset, &$oggpageinfo)
 
 ParseOggPageHeader ()
 
 ParseVorbisComments ()
 
- Public Member Functions inherited from GetId3\Handler\BaseHandler
 __construct (GetId3Core $getid3, $call_module=null)
 
 analyze ()
 Analyze from file pointer. More...
 
 AnalyzeString (&$string)
 Analyze from string instead. More...
 
 saveAttachment (&$ThisFileInfoIndex, $filename, $offset, $length)
 

Static Public Member Functions

static SpeexBandModeLookup ($mode)
 array $SpeexBandModeLookup More...
 
static OggPageSegmentLength ($OggInfoArray, $SegmentNumber=1)
 
static get_quality_from_nominal_bitrate ($nominal_bitrate)
 

Data Fields

 $inline_attachments = true
 

Additional Inherited Members

- Protected Member Functions inherited from GetId3\Handler\BaseHandler
 ftell ()
 
 fread ($bytes)
 
 fseek ($bytes, $whence=SEEK_SET)
 
 feof ()
 
 isDependencyFor ($module)
 
 error ($text)
 
 warning ($text)
 
- Protected Attributes inherited from GetId3\Handler\BaseHandler
 $getid3
 
 $data_string_flag = false
 
 $data_string = ''
 
 $data_string_position = 0
 
 $data_string_length = 0
 

Detailed Description

GetId3() by James Heinrich info@.nosp@m.geti.nosp@m.d3.or.nosp@m.g //.

module for analyzing Ogg Vorbis, OggFLAC and Speex files

Author
James Heinrich info@.nosp@m.geti.nosp@m.d3.or.nosp@m.g http://www.getid3.org

Definition at line 30 of file Ogg.php.

Member Function Documentation

◆ analyze()

GetId3\Module\Audio\Ogg::analyze ( )
Returns
boolean

Definition at line 42 of file Ogg.php.

References $counter, $info, GetId3\Handler\BaseHandler\fread(), GetId3\Handler\BaseHandler\fseek(), GetId3\Handler\BaseHandler\ftell(), GetId3\Lib\Helper\intValueSupported(), GetId3\Lib\Helper\LittleEndian2Int(), GetId3\Module\Audio\Ogg\ParseOggPageHeader(), GetId3\Module\Audio\Ogg\ParseVorbisComments(), GetId3\Module\Audio\Ogg\ParseVorbisPageHeader(), and GetId3\Module\Audio\Ogg\SpeexBandModeLookup().

43  {
44  $info = &$this->getid3->info;
45 
46  $info['fileformat'] = 'ogg';
47 
48  // Warn about illegal tags - only vorbiscomments are allowed
49  if (isset($info['id3v2'])) {
50  $info['warning'][] = 'Illegal ID3v2 tag present.';
51  }
52  if (isset($info['id3v1'])) {
53  $info['warning'][] = 'Illegal ID3v1 tag present.';
54  }
55  if (isset($info['ape'])) {
56  $info['warning'][] = 'Illegal APE tag present.';
57  }
58 
59  // Page 1 - Stream Header
60 
61  $this->fseek($info['avdataoffset']);
62 
63  $oggpageinfo = $this->ParseOggPageHeader();
64  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
65 
66  if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
67  $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?)';
68  unset($info['fileformat']);
69  unset($info['ogg']);
70 
71  return false;
72  }
73 
74  $filedata = $this->fread($oggpageinfo['page_length']);
75  $filedataoffset = 0;
76 
77  if (substr($filedata, 0, 4) == 'fLaC') {
78 
79  $info['audio']['dataformat'] = 'flac';
80  $info['audio']['bitrate_mode'] = 'vbr';
81  $info['audio']['lossless'] = true;
82 
83  } elseif (substr($filedata, 1, 6) == 'vorbis') {
84 
85  $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
86 
87  } elseif (substr($filedata, 0, 8) == 'Speex ') {
88 
89  // http://www.speex.org/manual/node10.html
90 
91  $info['audio']['dataformat'] = 'speex';
92  $info['mime_type'] = 'audio/speex';
93  $info['audio']['bitrate_mode'] = 'abr';
94  $info['audio']['lossless'] = false;
95 
96  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex '
97  $filedataoffset += 8;
98  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20);
99  $filedataoffset += 20;
100  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
101  $filedataoffset += 4;
102  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
103  $filedataoffset += 4;
104  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
105  $filedataoffset += 4;
106  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
107  $filedataoffset += 4;
108  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
109  $filedataoffset += 4;
110  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
111  $filedataoffset += 4;
112  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
113  $filedataoffset += 4;
114  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
115  $filedataoffset += 4;
116  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
117  $filedataoffset += 4;
118  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
119  $filedataoffset += 4;
120  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
121  $filedataoffset += 4;
122  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
123  $filedataoffset += 4;
124  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
125  $filedataoffset += 4;
126 
127  $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
128  $info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
129  $info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
130  $info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
131  $info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
132 
133  $info['audio']['sample_rate'] = $info['speex']['sample_rate'];
134  $info['audio']['channels'] = $info['speex']['channels'];
135  if ($info['speex']['vbr']) {
136  $info['audio']['bitrate_mode'] = 'vbr';
137  }
138 
139  } elseif (substr($filedata, 0, 8) == "fishead\x00") {
140 
141  // Ogg Skeleton version 3.0 Format Specification
142  // http://xiph.org/ogg/doc/skeleton.html
143  $filedataoffset += 8;
144  $info['ogg']['skeleton']['fishead']['raw']['version_major'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
145  $filedataoffset += 2;
146  $info['ogg']['skeleton']['fishead']['raw']['version_minor'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
147  $filedataoffset += 2;
148  $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
149  $filedataoffset += 8;
150  $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
151  $filedataoffset += 8;
152  $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
153  $filedataoffset += 8;
154  $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
155  $filedataoffset += 8;
156  $info['ogg']['skeleton']['fishead']['raw']['utc'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
157  $filedataoffset += 20;
158 
159  $info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
160  $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
161  $info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
162  $info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc'];
163 
164  $counter = 0;
165  do {
166  $oggpageinfo = $this->ParseOggPageHeader();
167  $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
168  $filedata = $this->fread($oggpageinfo['page_length']);
169  $this->fseek($oggpageinfo['page_end_offset']);
170 
171  if (substr($filedata, 0, 8) == "fisbone\x00") {
172 
173  $filedataoffset = 8;
174  $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
175  $filedataoffset += 4;
176  $info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
177  $filedataoffset += 4;
178  $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
179  $filedataoffset += 4;
180  $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
181  $filedataoffset += 8;
182  $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
183  $filedataoffset += 8;
184  $info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
185  $filedataoffset += 8;
186  $info['ogg']['skeleton']['fisbone']['raw']['preroll'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
187  $filedataoffset += 4;
188  $info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
189  $filedataoffset += 1;
190  $info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3);
191  $filedataoffset += 3;
192 
193  } elseif (substr($filedata, 1, 6) == 'theora') {
194 
195  $info['video']['dataformat'] = 'theora';
196  $info['error'][] = 'Ogg Theora not correctly handled in this version of GetId3 ['.$this->getid3->version().']';
197  //break;
198 
199  } elseif (substr($filedata, 1, 6) == 'vorbis') {
200 
201  $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
202 
203  } else {
204  $info['error'][] = 'unexpected';
205  //break;
206  }
207  //} while ($oggpageinfo['page_seqno'] == 0);
208  } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
209 
210  $this->fseek($oggpageinfo['page_start_offset']);
211 
212  $info['error'][] = 'Ogg Skeleton not correctly handled in this version of GetId3 ['.$this->getid3->version().']';
213  //return false;
214 
215  } else {
216 
217  $info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
218  unset($info['ogg']);
219  unset($info['mime_type']);
220 
221  return false;
222 
223  }
224 
225  // Page 2 - Comment Header
226  $oggpageinfo = $this->ParseOggPageHeader();
227  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
228 
229  switch ($info['audio']['dataformat']) {
230  case 'vorbis':
231  $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
232  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = Helper::LittleEndian2Int(substr($filedata, 0, 1));
233  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis'
234 
235  $this->ParseVorbisComments();
236  break;
237 
238  case 'flac':
239  $getid3_flac = new Flac($this->getid3);
240  if (!$getid3_flac->parseMETAdata()) {
241  $info['error'][] = 'Failed to parse FLAC headers';
242 
243  return false;
244  }
245  unset($getid3_flac);
246  break;
247 
248  case 'speex':
249  $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
250  $this->ParseVorbisComments();
251  break;
252 
253  }
254 
255  // Last Page - Number of Samples
256 
257  if (!Helper::intValueSupported($info['avdataend'])) {
258 
259  $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
260 
261  } else {
262 
263  $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
264  $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
265  if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
266  $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
267  $info['avdataend'] = $this->ftell();
268  $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
269  $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
270  if ($info['ogg']['samples'] == 0) {
271  $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
272 
273  return false;
274  }
275  if (!empty($info['audio']['sample_rate'])) {
276  $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
277  }
278  }
279 
280  }
281 
282  if (!empty($info['ogg']['bitrate_average'])) {
283  $info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
284  } elseif (!empty($info['ogg']['bitrate_nominal'])) {
285  $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
286  } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
287  $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
288  }
289  if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
290  if ($info['audio']['bitrate'] == 0) {
291  $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
292 
293  return false;
294  }
295  $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
296  }
297 
298  if (isset($info['ogg']['vendor'])) {
299  $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
300 
301  // Vorbis only
302  if ($info['audio']['dataformat'] == 'vorbis') {
303 
304  // Vorbis 1.0 starts with Xiph.Org
305  if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
306 
307  if ($info['audio']['bitrate_mode'] == 'abr') {
308 
309  // Set -b 128 on abr files
310  $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
311 
312  } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
313  // Set -q N on vbr files
314  $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
315 
316  }
317  }
318 
319  if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
320  $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
321  }
322  }
323  }
324 
325  return true;
326  }
static SpeexBandModeLookup($mode)
array $SpeexBandModeLookup
Definition: Ogg.php:714
ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo)
Definition: Ogg.php:328
fseek($bytes, $whence=SEEK_SET)
$counter
$info
Definition: example_052.php:80
static LittleEndian2Int($byteword, $signed=false)
Definition: Helper.php:413
static intValueSupported($num)
null $hasINT64
Definition: Helper.php:130
+ Here is the call graph for this function:

◆ get_quality_from_nominal_bitrate()

static GetId3\Module\Audio\Ogg::get_quality_from_nominal_bitrate (   $nominal_bitrate)
static
Parameters
type$nominal_bitrate
Returns
type

Definition at line 752 of file Ogg.php.

753  {
754  // decrease precision
755  $nominal_bitrate = $nominal_bitrate / 1000;
756 
757  if ($nominal_bitrate < 128) {
758  // q-1 to q4
759  $qval = ($nominal_bitrate - 64) / 16;
760  } elseif ($nominal_bitrate < 256) {
761  // q4 to q8
762  $qval = $nominal_bitrate / 32;
763  } elseif ($nominal_bitrate < 320) {
764  // q8 to q9
765  $qval = ($nominal_bitrate + 256) / 64;
766  } else {
767  // q9 to q10
768  $qval = ($nominal_bitrate + 1300) / 180;
769  }
770  //return $qval; // 5.031324
771  //return intval($qval); // 5
772  return round($qval, 1); // 5 or 4.9
773  }

◆ OggPageSegmentLength()

static GetId3\Module\Audio\Ogg::OggPageSegmentLength (   $OggInfoArray,
  $SegmentNumber = 1 
)
static
Parameters
type$OggInfoArray
type$SegmentNumber
Returns
type

Definition at line 732 of file Ogg.php.

Referenced by GetId3\Module\Audio\Ogg\ParseVorbisComments().

733  {
734  for ($i = 0; $i < $SegmentNumber; $i++) {
735  $segmentlength = 0;
736  foreach ($OggInfoArray['segment_table'] as $key => $value) {
737  $segmentlength += $value;
738  if ($value < 255) {
739  break;
740  }
741  }
742  }
743 
744  return $segmentlength;
745  }
+ Here is the caller graph for this function:

◆ ParseOggPageHeader()

GetId3\Module\Audio\Ogg::ParseOggPageHeader ( )
Returns
boolean http://xiph.org/ogg/vorbis/doc/framing.html

Definition at line 384 of file Ogg.php.

References GetId3\Handler\BaseHandler\feof(), GetId3\Handler\BaseHandler\fread(), GetId3\Handler\BaseHandler\fseek(), GetId3\Handler\BaseHandler\ftell(), and GetId3\Lib\Helper\LittleEndian2Int().

Referenced by GetId3\Module\Audio\Ogg\analyze(), and GetId3\Module\Audio\Ogg\ParseVorbisComments().

385  {
386  $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
387 
388  $filedata = $this->fread($this->getid3->fread_buffer_size());
389  $filedataoffset = 0;
390  while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
391  if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
392  // should be found before here
393  return false;
394  }
395  if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
396  if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
397  // get some more data, unless eof, in which case fail
398  return false;
399  }
400  }
401  }
402  $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
403 
404  $oggheader['stream_structver'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
405  $filedataoffset += 1;
406  $oggheader['flags_raw'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
407  $filedataoffset += 1;
408  $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
409  $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
410  $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
411 
412  $oggheader['pcm_abs_position'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
413  $filedataoffset += 8;
414  $oggheader['stream_serialno'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
415  $filedataoffset += 4;
416  $oggheader['page_seqno'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
417  $filedataoffset += 4;
418  $oggheader['page_checksum'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
419  $filedataoffset += 4;
420  $oggheader['page_segments'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
421  $filedataoffset += 1;
422  $oggheader['page_length'] = 0;
423  for ($i = 0; $i < $oggheader['page_segments']; $i++) {
424  $oggheader['segment_table'][$i] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
425  $filedataoffset += 1;
426  $oggheader['page_length'] += $oggheader['segment_table'][$i];
427  }
428  $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
429  $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length'];
430  $this->fseek($oggheader['header_end_offset']);
431 
432  return $oggheader;
433  }
fseek($bytes, $whence=SEEK_SET)
static LittleEndian2Int($byteword, $signed=false)
Definition: Helper.php:413
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ ParseVorbisComments()

GetId3\Module\Audio\Ogg::ParseVorbisComments ( )
Returns
boolean

Definition at line 439 of file Ogg.php.

References $info, array, GetId3\Handler\BaseHandler\fread(), GetId3\Handler\BaseHandler\fseek(), GetId3\Handler\BaseHandler\ftell(), GetId3\Lib\Helper\GetDataImageSize(), GetId3\Lib\Helper\LittleEndian2Int(), GetId3\Module\Audio\Ogg\OggPageSegmentLength(), and GetId3\Module\Audio\Ogg\ParseOggPageHeader().

Referenced by GetId3\Module\Audio\Ogg\analyze().

440  {
441  $info = &$this->getid3->info;
442 
443  $OriginalOffset = $this->ftell();
444  $commentdataoffset = 0;
445  $VorbisCommentPage = 1;
446 
447  switch ($info['audio']['dataformat']) {
448  case 'vorbis':
449  case 'speex':
450  $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
451  $this->fseek($CommentStartOffset);
452  $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
453  $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
454 
455  if ($info['audio']['dataformat'] == 'vorbis') {
456  $commentdataoffset += (strlen('vorbis') + 1);
457  }
458  break;
459 
460  case 'flac':
461  $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
462  $this->fseek($CommentStartOffset);
463  $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
464  break;
465 
466  default:
467  return false;
468  }
469 
470  $VendorSize = Helper::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
471  $commentdataoffset += 4;
472 
473  $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
474  $commentdataoffset += $VendorSize;
475 
476  $CommentsCount = Helper::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
477  $commentdataoffset += 4;
478  $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
479 
480  $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
481  $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
482  for ($i = 0; $i < $CommentsCount; $i++) {
483 
484  $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
485 
486  if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
487  if ($oggpageinfo = $this->ParseOggPageHeader()) {
488  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
489 
490  $VorbisCommentPage++;
491 
492  // First, save what we haven't read yet
493  $AsYetUnusedData = substr($commentdata, $commentdataoffset);
494 
495  // Then take that data off the end
496  $commentdata = substr($commentdata, 0, $commentdataoffset);
497 
498  // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
499  $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
500  $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
501 
502  // Finally, stick the unused data back on the end
503  $commentdata .= $AsYetUnusedData;
504 
505  //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
506  $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
507  }
508 
509  }
510  $ThisFileInfo_ogg_comments_raw[$i]['size'] = Helper::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
511 
512  // replace avdataoffset with position just after the last vorbiscomment
513  $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
514 
515  $commentdataoffset += 4;
516  while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
517  if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
518  $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
519  break 2;
520  }
521 
522  $VorbisCommentPage++;
523 
524  $oggpageinfo = $this->ParseOggPageHeader();
525  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
526 
527  // First, save what we haven't read yet
528  $AsYetUnusedData = substr($commentdata, $commentdataoffset);
529 
530  // Then take that data off the end
531  $commentdata = substr($commentdata, 0, $commentdataoffset);
532 
533  // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
534  $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
535  $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
536 
537  // Finally, stick the unused data back on the end
538  $commentdata .= $AsYetUnusedData;
539 
540  //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
541  if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
542  $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
543  break;
544  }
545  $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
546  if ($readlength <= 0) {
547  $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
548  break;
549  }
550  $commentdata .= $this->fread($readlength);
551 
552  //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
553  }
554  $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
555  $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
556  $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
557 
558  if (!$commentstring) {
559 
560  // no comment?
561  $info['warning'][] = 'Blank Ogg comment ['.$i.']';
562 
563  } elseif (strstr($commentstring, '=')) {
564 
565  $commentexploded = explode('=', $commentstring, 2);
566  $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]);
567  $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
568  $ThisFileInfo_ogg_comments_raw[$i]['data'] = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
569  $ThisFileInfo_ogg_comments_raw[$i]['data_length'] = strlen($ThisFileInfo_ogg_comments_raw[$i]['data']);
570 
571  if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
572  // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
573  // 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.
574  // http://flac.sourceforge.net/format.html#metadata_block_picture
575  $getid3_temp = new GetId3Core();
576  $getid3_flac = new Flac($getid3_temp);
577  $getid3_flac->data_string = $ThisFileInfo_ogg_comments_raw[$i]['data'];
578  $getid3_flac->data_string_flag = true;
579  if ($getid3_flac->parsePICTURE()) {
580  if (!empty($getid3_temp->info['flac']['PICTURE'])) {
581  foreach ($getid3_temp->info['flac']['PICTURE'] as $key => $value) {
582  $ThisFileInfo_ogg_comments_raw[$i]['data'] = $value['data'];
583  $ThisFileInfo_ogg_comments_raw[$i]['data_length'] = strlen($value['data']);
584  $ThisFileInfo_ogg_comments_raw[$i]['image_mime'] = $value['image_mime'];
585  $ThisFileInfo_ogg_comments_raw[$i]['width'] = $value['width'];
586  $ThisFileInfo_ogg_comments_raw[$i]['height'] = $value['height'];
587  $ThisFileInfo_ogg_comments_raw[$i]['type'] = $value['type'];
588  $ThisFileInfo_ogg_comments_raw[$i]['typeid'] = $value['typeid'];
589  $ThisFileInfo_ogg_comments_raw[$i]['color_depth'] = $value['color_depth'];
590  $ThisFileInfo_ogg_comments_raw[$i]['colors_indexed'] = $value['colors_indexed'];
591  }
592  }
593  } else {
594  $info['warning'][] = 'Failed to GetId3_flac.parsePICTURE()';
595  }
596  unset($getid3_flac, $getid3_temp);
597  }
598 
599  if (preg_match('#^(BM|GIF|\xFF\xD8\xFF|\x89\x50\x4E\x47\x0D\x0A\x1A\x0A|II\x2A\x00|MM\x00\x2A)#s', $ThisFileInfo_ogg_comments_raw[$i]['data'])) {
600  $imageinfo = array();
601  $imagechunkcheck = Helper::GetDataImageSize($ThisFileInfo_ogg_comments_raw[$i]['data'], $imageinfo);
602  unset($imageinfo);
603  if (!empty($imagechunkcheck)) {
604  $ThisFileInfo_ogg_comments_raw[$i]['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
605  if ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] && ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] != 'application/octet-stream')) {
606  unset($ThisFileInfo_ogg_comments_raw[$i]['value']);
607  }
608  }
609  }
610 
611  if (isset($ThisFileInfo_ogg_comments_raw[$i]['value'])) {
612  unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
613  $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
614  } else {
615  do {
616  if ($this->inline_attachments === false) {
617  // skip entirely
618  unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
619  break;
620  }
621  if ($this->inline_attachments === true) {
622  // great
623  } elseif (is_int($this->inline_attachments)) {
624  if ($this->inline_attachments < $ThisFileInfo_ogg_comments_raw[$i]['data_length']) {
625  // too big, skip
626  $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' is too large to process inline ('.number_format($ThisFileInfo_ogg_comments_raw[$i]['data_length']).' bytes)';
627  unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
628  break;
629  }
630  } elseif (is_string($this->inline_attachments)) {
631  $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
632  if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
633  // cannot write, skip
634  $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
635  unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
636  break;
637  }
638  }
639  // if we get this far, must be OK
640  if (is_string($this->inline_attachments)) {
641  $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$ThisFileInfo_ogg_comments_raw[$i]['offset'];
642  if (!file_exists($destination_filename) || is_writable($destination_filename)) {
643  file_put_contents($destination_filename, $ThisFileInfo_ogg_comments_raw[$i]['data']);
644  } else {
645  $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
646  }
647  $ThisFileInfo_ogg_comments_raw[$i]['data_filename'] = $destination_filename;
648  unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
649  } else {
650  $info['ogg']['comments']['picture'][] = array('data'=>$ThisFileInfo_ogg_comments_raw[$i]['data'], 'image_mime'=>$ThisFileInfo_ogg_comments_raw[$i]['image_mime']);
651  }
652  } while (false);
653 
654  }
655 
656  } else {
657 
658  $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
659 
660  }
661  }
662 
663  // Replay Gain Adjustment
664  // http://privatewww.essex.ac.uk/~djmrob/replaygain/
665  if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
666  foreach ($info['ogg']['comments'] as $index => $commentvalue) {
667  switch ($index) {
668  case 'rg_audiophile':
669  case 'replaygain_album_gain':
670  $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
671  unset($info['ogg']['comments'][$index]);
672  break;
673 
674  case 'rg_radio':
675  case 'replaygain_track_gain':
676  $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
677  unset($info['ogg']['comments'][$index]);
678  break;
679 
680  case 'replaygain_album_peak':
681  $info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
682  unset($info['ogg']['comments'][$index]);
683  break;
684 
685  case 'rg_peak':
686  case 'replaygain_track_peak':
687  $info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
688  unset($info['ogg']['comments'][$index]);
689  break;
690 
691  case 'replaygain_reference_loudness':
692  $info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
693  unset($info['ogg']['comments'][$index]);
694  break;
695 
696  default:
697  // do nothing
698  break;
699  }
700  }
701  }
702 
703  $this->fseek($OriginalOffset);
704 
705  return true;
706  }
static GetDataImageSize($imgData, &$imageinfo)
string $tempdir
Definition: Helper.php:1622
fseek($bytes, $whence=SEEK_SET)
$info
Definition: example_052.php:80
static OggPageSegmentLength($OggInfoArray, $SegmentNumber=1)
Definition: Ogg.php:732
Create styles array
The data for the language used.
static LittleEndian2Int($byteword, $signed=false)
Definition: Helper.php:413
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ ParseVorbisPageHeader()

GetId3\Module\Audio\Ogg::ParseVorbisPageHeader ( $filedata,
$filedataoffset,
$oggpageinfo 
)

Definition at line 328 of file Ogg.php.

References $info, and GetId3\Lib\Helper\LittleEndian2Int().

Referenced by GetId3\Module\Audio\Ogg\analyze().

329  {
330  $info = &$this->getid3->info;
331  $info['audio']['dataformat'] = 'vorbis';
332  $info['audio']['lossless'] = false;
333 
334  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
335  $filedataoffset += 1;
336  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
337  $filedataoffset += 6;
338  $info['ogg']['bitstreamversion'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
339  $filedataoffset += 4;
340  $info['ogg']['numberofchannels'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
341  $filedataoffset += 1;
342  $info['audio']['channels'] = $info['ogg']['numberofchannels'];
343  $info['ogg']['samplerate'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
344  $filedataoffset += 4;
345  if ($info['ogg']['samplerate'] == 0) {
346  $info['error'][] = 'Corrupt Ogg file: sample rate == zero';
347 
348  return false;
349  }
350  $info['audio']['sample_rate'] = $info['ogg']['samplerate'];
351  $info['ogg']['samples'] = 0; // filled in later
352  $info['ogg']['bitrate_average'] = 0; // filled in later
353  $info['ogg']['bitrate_max'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
354  $filedataoffset += 4;
355  $info['ogg']['bitrate_nominal'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
356  $filedataoffset += 4;
357  $info['ogg']['bitrate_min'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
358  $filedataoffset += 4;
359  $info['ogg']['blocksize_small'] = pow(2, Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
360  $info['ogg']['blocksize_large'] = pow(2, (Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
361  $info['ogg']['stop_bit'] = Helper::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
362 
363  $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
364  if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
365  unset($info['ogg']['bitrate_max']);
366  $info['audio']['bitrate_mode'] = 'abr';
367  }
368  if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
369  unset($info['ogg']['bitrate_nominal']);
370  }
371  if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
372  unset($info['ogg']['bitrate_min']);
373  $info['audio']['bitrate_mode'] = 'abr';
374  }
375 
376  return true;
377  }
$info
Definition: example_052.php:80
static LittleEndian2Int($byteword, $signed=false)
Definition: Helper.php:413
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ SpeexBandModeLookup()

static GetId3\Module\Audio\Ogg::SpeexBandModeLookup (   $mode)
static

array $SpeexBandModeLookup

Parameters
type$mode
Returns
type

Definition at line 714 of file Ogg.php.

References array.

Referenced by GetId3\Module\Audio\Ogg\analyze().

715  {
716  static $SpeexBandModeLookup = array();
717  if (empty($SpeexBandModeLookup)) {
718  $SpeexBandModeLookup[0] = 'narrow';
719  $SpeexBandModeLookup[1] = 'wide';
720  $SpeexBandModeLookup[2] = 'ultra-wide';
721  }
722 
723  return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
724  }
Create styles array
The data for the language used.
+ Here is the caller graph for this function:

Field Documentation

◆ $inline_attachments

GetId3\Module\Audio\Ogg::$inline_attachments = true

Definition at line 36 of file Ogg.php.


The documentation for this class was generated from the following file: