ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
Optimfrog.php
Go to the documentation of this file.
1<?php
2
3namespace 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
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}
An exception for terminatinating execution or to throw for unit testing.
GetId3() by James Heinrich info@getid3.org //.
Definition: GetId3Core.php:26
GetId3() by James Heinrich info@getid3.org //.
Definition: BaseHandler.php:26
fseek($bytes, $whence=SEEK_SET)
GetId3() by James Heinrich info@getid3.org //.
Definition: Helper.php:27
static fileextension($filename, $numextensions=1)
Definition: Helper.php:628
static LittleEndian2Int($byteword, $signed=false)
Definition: Helper.php:413
static PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8')
Definition: Helper.php:36
GetId3() by James Heinrich info@getid3.org //.
Definition: Riff.php:43
GetId3() by James Heinrich info@getid3.org //.
Definition: Optimfrog.php:33
static OptimFROGcompressionLookup($CompressionID)
@staticvar array $OptimFROGencoderModeLookup
Definition: Optimfrog.php:457
static OptimFROGencoderNameLookup($EncoderID)
@staticvar array $OptimFROGencoderSystemLookup
Definition: Optimfrog.php:434
static OptimFROGchannelConfigurationLookup($ChannelConfiguration)
@staticvar array $OptimFROGchannelConfigurationLookup
Definition: Optimfrog.php:397
static OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration)
@staticvar array $OptimFROGchannelConfigNumChannelsLookup
Definition: Optimfrog.php:413
static OptimFROGsampleTypeLookup($SampleType)
@staticvar array $OptimFROGsampleTypeLookup
Definition: Optimfrog.php:347
static OptimFROGbitsPerSampleTypeLookup($SampleType)
@staticvar array $OptimFROGbitsPerSampleTypeLookup
Definition: Optimfrog.php:372
static OptimFROGspeedupLookup($CompressionID)
@staticvar array $OptimFROGencoderSpeedupLookup
Definition: Optimfrog.php:487
$info
Definition: example_052.php:80