ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
Optimfrog.php
Go to the documentation of this file.
1 <?php
2 
3 namespace GetId3\Module\Audio;
4 
9 
12 // available at http://getid3.sourceforge.net //
13 // or http://www.getid3.org //
15 // See readme.txt for more details //
17 // //
18 // module.audio.optimfrog.php //
19 // module for analyzing OptimFROG audio files //
20 // dependencies: module.audio.riff.php //
21 // ///
23 
32 class Optimfrog extends BaseHandler
33 {
34 
39  public function analyze()
40  {
41  $info = &$this->getid3->info;
42 
43  $info['fileformat'] = 'ofr';
44  $info['audio']['dataformat'] = 'ofr';
45  $info['audio']['bitrate_mode'] = 'vbr';
46  $info['audio']['lossless'] = true;
47 
48  fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
49  $OFRheader = fread($this->getid3->fp, 8);
50  if (substr($OFRheader, 0, 5) == '*RIFF') {
51  return $this->ParseOptimFROGheader42();
52 
53  } elseif (substr($OFRheader, 0, 3) == 'OFR') {
54  return $this->ParseOptimFROGheader45();
55 
56  }
57 
58  $info['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$info['avdataoffset'].', found "'.Helper::PrintHexBytes($OFRheader).'"';
59  unset($info['fileformat']);
60 
61  return false;
62  }
63 
68  public function ParseOptimFROGheader42()
69  {
70  // for fileformat of v4.21 and older
71 
72  $info = &$this->getid3->info;
73  fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
74  $OptimFROGheaderData = fread($this->getid3->fp, 45);
75  $info['avdataoffset'] = 45;
76 
77  $OptimFROGencoderVersion_raw = Helper::LittleEndian2Int(substr($OptimFROGheaderData, 0, 1));
78  $OptimFROGencoderVersion_major = floor($OptimFROGencoderVersion_raw / 10);
79  $OptimFROGencoderVersion_minor = $OptimFROGencoderVersion_raw - ($OptimFROGencoderVersion_major * 10);
80  $RIFFdata = substr($OptimFROGheaderData, 1, 44);
81  $OrignalRIFFheaderSize = Helper::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8;
82  $OrignalRIFFdataSize = Helper::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44;
83 
84  if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) {
85  $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
86  fseek($this->getid3->fp, $info['avdataend'], SEEK_SET);
87  $RIFFdata .= fread($this->getid3->fp, $OrignalRIFFheaderSize - $OrignalRIFFdataSize);
88  }
89 
90  // move the data chunk after all other chunks (if any)
91  // so that the RIFF parser doesn't see EOF when trying
92  // to skip over the data chunk
93  $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
94 
95  $getid3_temp = new GetId3Core();
96  $getid3_temp->openfile($this->getid3->filename);
97  $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
98  $getid3_temp->info['avdataend'] = $info['avdataend'];
99  $getid3_riff = new Riff($getid3_temp);
100  $getid3_riff->ParseRIFFdata($RIFFdata);
101  $info['riff'] = $getid3_temp->info['riff'];
102 
103  $info['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor;
104  $info['audio']['channels'] = $info['riff']['audio'][0]['channels'];
105  $info['audio']['sample_rate'] = $info['riff']['audio'][0]['sample_rate'];
106  $info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample'];
107  $info['playtime_seconds'] = $OrignalRIFFdataSize / ($info['audio']['channels'] * $info['audio']['sample_rate'] * ($info['audio']['bits_per_sample'] / 8));
108  $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
109 
110  unset($getid3_riff, $getid3_temp, $RIFFdata);
111 
112  return true;
113  }
114 
119  public function ParseOptimFROGheader45()
120  {
121  // for fileformat of v4.50a and higher
122 
123  $info = &$this->getid3->info;
124  $RIFFdata = '';
125  fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
126  while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < $info['avdataend'])) {
127  $BlockOffset = ftell($this->getid3->fp);
128  $BlockData = fread($this->getid3->fp, 8);
129  $offset = 8;
130  $BlockName = substr($BlockData, 0, 4);
131  $BlockSize = Helper::LittleEndian2Int(substr($BlockData, 4, 4));
132 
133  if ($BlockName == 'OFRX') {
134  $BlockName = 'OFR ';
135  }
136  if (!isset($info['ofr'][$BlockName])) {
137  $info['ofr'][$BlockName] = array();
138  }
139  $thisfile_ofr_thisblock = &$info['ofr'][$BlockName];
140 
141  switch ($BlockName) {
142  case 'OFR ':
143 
144  // shortcut
145  $thisfile_ofr_thisblock['offset'] = $BlockOffset;
146  $thisfile_ofr_thisblock['size'] = $BlockSize;
147 
148  $info['audio']['encoder'] = 'OptimFROG 4.50 alpha';
149  switch ($BlockSize) {
150  case 12:
151  case 15:
152  // good
153  break;
154 
155  default:
156  $info['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)';
157  break;
158  }
159  $BlockData .= fread($this->getid3->fp, $BlockSize);
160 
161  $thisfile_ofr_thisblock['total_samples'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 6));
162  $offset += 6;
163  $thisfile_ofr_thisblock['raw']['sample_type'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 1));
164  $thisfile_ofr_thisblock['sample_type'] = $this->OptimFROGsampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']);
165  $offset += 1;
166  $thisfile_ofr_thisblock['channel_config'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 1));
167  $thisfile_ofr_thisblock['channels'] = $thisfile_ofr_thisblock['channel_config'];
168  $offset += 1;
169  $thisfile_ofr_thisblock['sample_rate'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 4));
170  $offset += 4;
171 
172  if ($BlockSize > 12) {
173 
174  // OFR 4.504b or higher
175  $thisfile_ofr_thisblock['channels'] = $this->OptimFROGchannelConfigNumChannelsLookup($thisfile_ofr_thisblock['channel_config']);
176  $thisfile_ofr_thisblock['raw']['encoder_id'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 2));
177  $thisfile_ofr_thisblock['encoder'] = $this->OptimFROGencoderNameLookup($thisfile_ofr_thisblock['raw']['encoder_id']);
178  $offset += 2;
179  $thisfile_ofr_thisblock['raw']['compression'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 1));
180  $thisfile_ofr_thisblock['compression'] = $this->OptimFROGcompressionLookup($thisfile_ofr_thisblock['raw']['compression']);
181  $thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']);
182  $offset += 1;
183 
184  $info['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder'];
185  $info['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression'];
186 
187  if ((($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xF0) >> 4) == 7) { // v4.507
188  if (strtolower(Helper::fileextension($info['filename'])) == 'ofs') {
189  // OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference
190  // between lossless and lossy other than the file extension.
191  $info['audio']['dataformat'] = 'ofs';
192  $info['audio']['lossless'] = true;
193  }
194  }
195 
196  }
197 
198  $info['audio']['channels'] = $thisfile_ofr_thisblock['channels'];
199  $info['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate'];
200  $info['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']);
201  break;
202 
203 
204  case 'COMP':
205  // unlike other block types, there CAN be multiple COMP blocks
206 
207  $COMPdata['offset'] = $BlockOffset;
208  $COMPdata['size'] = $BlockSize;
209 
210  if ($info['avdataoffset'] == 0) {
211  $info['avdataoffset'] = $BlockOffset;
212  }
213 
214  // Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data
215  $BlockData .= fread($this->getid3->fp, 14);
216  fseek($this->getid3->fp, $BlockSize - 14, SEEK_CUR);
217 
218  $COMPdata['crc_32'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 4));
219  $offset += 4;
220  $COMPdata['sample_count'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 4));
221  $offset += 4;
222  $COMPdata['raw']['sample_type'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 1));
223  $COMPdata['sample_type'] = $this->OptimFROGsampleTypeLookup($COMPdata['raw']['sample_type']);
224  $offset += 1;
225  $COMPdata['raw']['channel_configuration'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 1));
226  $COMPdata['channel_configuration'] = $this->OptimFROGchannelConfigurationLookup($COMPdata['raw']['channel_configuration']);
227  $offset += 1;
228  $COMPdata['raw']['algorithm_id'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 2));
229  //$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']);
230  $offset += 2;
231 
232  if ($info['ofr']['OFR ']['size'] > 12) {
233 
234  // OFR 4.504b or higher
235  $COMPdata['raw']['encoder_id'] = Helper::LittleEndian2Int(substr($BlockData, $offset, 2));
236  $COMPdata['encoder'] = $this->OptimFROGencoderNameLookup($COMPdata['raw']['encoder_id']);
237  $offset += 2;
238 
239  }
240 
241  if ($COMPdata['crc_32'] == 0x454E4F4E) {
242  // ASCII value of 'NONE' - placeholder value in v4.50a
243  $COMPdata['crc_32'] = false;
244  }
245 
246  $thisfile_ofr_thisblock[] = $COMPdata;
247  break;
248 
249  case 'HEAD':
250  $thisfile_ofr_thisblock['offset'] = $BlockOffset;
251  $thisfile_ofr_thisblock['size'] = $BlockSize;
252 
253  $RIFFdata .= fread($this->getid3->fp, $BlockSize);
254  break;
255 
256  case 'TAIL':
257  $thisfile_ofr_thisblock['offset'] = $BlockOffset;
258  $thisfile_ofr_thisblock['size'] = $BlockSize;
259 
260  if ($BlockSize > 0) {
261  $RIFFdata .= fread($this->getid3->fp, $BlockSize);
262  }
263  break;
264 
265  case 'RECV':
266  // block contains no useful meta data - simply note and skip
267 
268  $thisfile_ofr_thisblock['offset'] = $BlockOffset;
269  $thisfile_ofr_thisblock['size'] = $BlockSize;
270 
271  fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
272  break;
273 
274 
275  case 'APET':
276  // APEtag v2
277 
278  $thisfile_ofr_thisblock['offset'] = $BlockOffset;
279  $thisfile_ofr_thisblock['size'] = $BlockSize;
280  $info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.$this->getid3->version().') of GetId3Core()';
281 
282  fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
283  break;
284 
285 
286  case 'MD5 ':
287  // APEtag v2
288 
289  $thisfile_ofr_thisblock['offset'] = $BlockOffset;
290  $thisfile_ofr_thisblock['size'] = $BlockSize;
291 
292  if ($BlockSize == 16) {
293 
294  $thisfile_ofr_thisblock['md5_binary'] = fread($this->getid3->fp, $BlockSize);
295  $thisfile_ofr_thisblock['md5_string'] = Helper::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false);
296  $info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string'];
297 
298  } else {
299 
300  $info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead';
301  fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
302 
303  }
304  break;
305 
306 
307  default:
308  $thisfile_ofr_thisblock['offset'] = $BlockOffset;
309  $thisfile_ofr_thisblock['size'] = $BlockSize;
310 
311  $info['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset'];
312  fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
313  break;
314  }
315  }
316  if (isset($info['ofr']['TAIL']['offset'])) {
317  $info['avdataend'] = $info['ofr']['TAIL']['offset'];
318  }
319 
320  $info['playtime_seconds'] = (float) $info['ofr']['OFR ']['total_samples'] / ($info['audio']['channels'] * $info['audio']['sample_rate']);
321  $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
322 
323  // move the data chunk after all other chunks (if any)
324  // so that the RIFF parser doesn't see EOF when trying
325  // to skip over the data chunk
326  $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
327 
328  $getid3_temp = new GetId3Core();
329  $getid3_temp->openfile($this->getid3->filename);
330  $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
331  $getid3_temp->info['avdataend'] = $info['avdataend'];
332  $getid3_riff = new Riff($getid3_temp);
333  $getid3_riff->ParseRIFFdata($RIFFdata);
334  $info['riff'] = $getid3_temp->info['riff'];
335 
336  unset($getid3_riff, $getid3_temp, $RIFFdata);
337 
338  return true;
339  }
340 
347  public static function OptimFROGsampleTypeLookup($SampleType)
348  {
349  static $OptimFROGsampleTypeLookup = array(
350  0 => 'unsigned int (8-bit)',
351  1 => 'signed int (8-bit)',
352  2 => 'unsigned int (16-bit)',
353  3 => 'signed int (16-bit)',
354  4 => 'unsigned int (24-bit)',
355  5 => 'signed int (24-bit)',
356  6 => 'unsigned int (32-bit)',
357  7 => 'signed int (32-bit)',
358  8 => 'float 0.24 (32-bit)',
359  9 => 'float 16.8 (32-bit)',
360  10 => 'float 24.0 (32-bit)'
361  );
362 
363  return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false);
364  }
365 
372  public static function OptimFROGbitsPerSampleTypeLookup($SampleType)
373  {
374  static $OptimFROGbitsPerSampleTypeLookup = array(
375  0 => 8,
376  1 => 8,
377  2 => 16,
378  3 => 16,
379  4 => 24,
380  5 => 24,
381  6 => 32,
382  7 => 32,
383  8 => 32,
384  9 => 32,
385  10 => 32
386  );
387 
388  return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false);
389  }
390 
397  public static function OptimFROGchannelConfigurationLookup($ChannelConfiguration)
398  {
399  static $OptimFROGchannelConfigurationLookup = array(
400  0 => 'mono',
401  1 => 'stereo'
402  );
403 
404  return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false);
405  }
406 
413  public static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration)
414  {
415  static $OptimFROGchannelConfigNumChannelsLookup = array(
416  0 => 1,
417  1 => 2
418  );
419 
420  return (isset($OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration] : false);
421  }
422 
423  // static function OptimFROGalgorithmNameLookup($AlgorithID) {
424  // static $OptimFROGalgorithmNameLookup = array();
425  // return (isset($OptimFROGalgorithmNameLookup[$AlgorithID]) ? $OptimFROGalgorithmNameLookup[$AlgorithID] : false);
426  // }
427 
434  public static function OptimFROGencoderNameLookup($EncoderID)
435  {
436  // version = (encoderID >> 4) + 4500
437  // system = encoderID & 0xF
438 
439  $EncoderVersion = number_format(((($EncoderID & 0xF0) >> 4) + 4500) / 1000, 3);
440  $EncoderSystemID = ($EncoderID & 0x0F);
441 
442  static $OptimFROGencoderSystemLookup = array(
443  0x00 => 'Windows console',
444  0x01 => 'Linux console',
445  0x0F => 'unknown'
446  );
447 
448  return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')';
449  }
450 
457  public static function OptimFROGcompressionLookup($CompressionID)
458  {
459  // mode = compression >> 3
460  // speedup = compression & 0x07
461 
462  $CompressionModeID = ($CompressionID & 0xF8) >> 3;
463  //$CompressionSpeedupID = ($CompressionID & 0x07);
464 
465  static $OptimFROGencoderModeLookup = array(
466  0x00 => 'fast',
467  0x01 => 'normal',
468  0x02 => 'high',
469  0x03 => 'extra', // extranew (some versions)
470  0x04 => 'best', // bestnew (some versions)
471  0x05 => 'ultra',
472  0x06 => 'insane',
473  0x07 => 'highnew',
474  0x08 => 'extranew',
475  0x09 => 'bestnew'
476  );
477 
478  return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')');
479  }
480 
487  public static function OptimFROGspeedupLookup($CompressionID)
488  {
489  // mode = compression >> 3
490  // speedup = compression & 0x07
491 
492  //$CompressionModeID = ($CompressionID & 0xF8) >> 3;
493  $CompressionSpeedupID = ($CompressionID & 0x07);
494 
495  static $OptimFROGencoderSpeedupLookup = array(
496  0x00 => '1x',
497  0x01 => '2x',
498  0x02 => '4x'
499  );
500 
501  return (isset($OptimFROGencoderSpeedupLookup[$CompressionSpeedupID]) ? $OptimFROGencoderSpeedupLookup[$CompressionSpeedupID] : 'undefined mode (0x'.dechex($CompressionSpeedupID));
502  }
503 }
static PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8')
Definition: Helper.php:36
GetId3() by James Heinrich info@getid3.org //.
Definition: BaseHandler.php:25
static OptimFROGencoderNameLookup($EncoderID)
array $OptimFROGencoderSystemLookup
Definition: Optimfrog.php:434
static OptimFROGbitsPerSampleTypeLookup($SampleType)
array $OptimFROGbitsPerSampleTypeLookup
Definition: Optimfrog.php:372
static fileextension($filename, $numextensions=1)
Definition: Helper.php:628
GetId3() by James Heinrich info@getid3.org //.
Definition: Optimfrog.php:32
fseek($bytes, $whence=SEEK_SET)
$info
Definition: example_052.php:80
GetId3() by James Heinrich info@getid3.org //.
Definition: GetId3Core.php:25
static OptimFROGchannelConfigurationLookup($ChannelConfiguration)
array $OptimFROGchannelConfigurationLookup
Definition: Optimfrog.php:397
Create styles array
The data for the language used.
static OptimFROGspeedupLookup($CompressionID)
array $OptimFROGencoderSpeedupLookup
Definition: Optimfrog.php:487
static OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration)
array $OptimFROGchannelConfigNumChannelsLookup
Definition: Optimfrog.php:413
static LittleEndian2Int($byteword, $signed=false)
Definition: Helper.php:413
static OptimFROGcompressionLookup($CompressionID)
array $OptimFROGencoderModeLookup
Definition: Optimfrog.php:457
GetId3() by James Heinrich info@getid3.org //.
Definition: Riff.php:42
static OptimFROGsampleTypeLookup($SampleType)
array $OptimFROGsampleTypeLookup
Definition: Optimfrog.php:347