File indexing completed on 2024-05-12 17:26:00
0001 <?php 0002 ///////////////////////////////////////////////////////////////// 0003 /// getID3() by James Heinrich <info@getid3.org> // 0004 // available at http://getid3.sourceforge.net // 0005 // or http://www.getid3.org // 0006 // also https://github.com/JamesHeinrich/getID3 // 0007 ///////////////////////////////////////////////////////////////// 0008 // See readme.txt for more details // 0009 ///////////////////////////////////////////////////////////////// 0010 // // 0011 // module.audio.optimfrog.php // 0012 // module for analyzing OptimFROG audio files // 0013 // dependencies: module.audio.riff.php // 0014 // /// 0015 ///////////////////////////////////////////////////////////////// 0016 0017 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); 0018 0019 class getid3_optimfrog extends getid3_handler 0020 { 0021 0022 public function Analyze() { 0023 $info = &$this->getid3->info; 0024 0025 $info['fileformat'] = 'ofr'; 0026 $info['audio']['dataformat'] = 'ofr'; 0027 $info['audio']['bitrate_mode'] = 'vbr'; 0028 $info['audio']['lossless'] = true; 0029 0030 $this->fseek($info['avdataoffset']); 0031 $OFRheader = $this->fread(8); 0032 if (substr($OFRheader, 0, 5) == '*RIFF') { 0033 0034 return $this->ParseOptimFROGheader42(); 0035 0036 } elseif (substr($OFRheader, 0, 3) == 'OFR') { 0037 0038 return $this->ParseOptimFROGheader45(); 0039 0040 } 0041 0042 $info['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($OFRheader).'"'; 0043 unset($info['fileformat']); 0044 return false; 0045 } 0046 0047 0048 public function ParseOptimFROGheader42() { 0049 // for fileformat of v4.21 and older 0050 0051 $info = &$this->getid3->info; 0052 $this->fseek($info['avdataoffset']); 0053 $OptimFROGheaderData = $this->fread(45); 0054 $info['avdataoffset'] = 45; 0055 0056 $OptimFROGencoderVersion_raw = getid3_lib::LittleEndian2Int(substr($OptimFROGheaderData, 0, 1)); 0057 $OptimFROGencoderVersion_major = floor($OptimFROGencoderVersion_raw / 10); 0058 $OptimFROGencoderVersion_minor = $OptimFROGencoderVersion_raw - ($OptimFROGencoderVersion_major * 10); 0059 $RIFFdata = substr($OptimFROGheaderData, 1, 44); 0060 $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; 0061 $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; 0062 0063 if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { 0064 $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); 0065 $this->fseek($info['avdataend']); 0066 $RIFFdata .= $this->fread($OrignalRIFFheaderSize - $OrignalRIFFdataSize); 0067 } 0068 0069 // move the data chunk after all other chunks (if any) 0070 // so that the RIFF parser doesn't see EOF when trying 0071 // to skip over the data chunk 0072 $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); 0073 0074 $getid3_temp = new getID3(); 0075 $getid3_temp->openfile($this->getid3->filename); 0076 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 0077 $getid3_temp->info['avdataend'] = $info['avdataend']; 0078 $getid3_riff = new getid3_riff($getid3_temp); 0079 $getid3_riff->ParseRIFFdata($RIFFdata); 0080 $info['riff'] = $getid3_temp->info['riff']; 0081 0082 $info['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor; 0083 $info['audio']['channels'] = $info['riff']['audio'][0]['channels']; 0084 $info['audio']['sample_rate'] = $info['riff']['audio'][0]['sample_rate']; 0085 $info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample']; 0086 $info['playtime_seconds'] = $OrignalRIFFdataSize / ($info['audio']['channels'] * $info['audio']['sample_rate'] * ($info['audio']['bits_per_sample'] / 8)); 0087 $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 0088 0089 unset($getid3_riff, $getid3_temp, $RIFFdata); 0090 0091 return true; 0092 } 0093 0094 0095 public function ParseOptimFROGheader45() { 0096 // for fileformat of v4.50a and higher 0097 0098 $info = &$this->getid3->info; 0099 $RIFFdata = ''; 0100 $this->fseek($info['avdataoffset']); 0101 while (!feof($this->getid3->fp) && ($this->ftell() < $info['avdataend'])) { 0102 $BlockOffset = $this->ftell(); 0103 $BlockData = $this->fread(8); 0104 $offset = 8; 0105 $BlockName = substr($BlockData, 0, 4); 0106 $BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4)); 0107 0108 if ($BlockName == 'OFRX') { 0109 $BlockName = 'OFR '; 0110 } 0111 if (!isset($info['ofr'][$BlockName])) { 0112 $info['ofr'][$BlockName] = array(); 0113 } 0114 $thisfile_ofr_thisblock = &$info['ofr'][$BlockName]; 0115 0116 switch ($BlockName) { 0117 case 'OFR ': 0118 0119 // shortcut 0120 $thisfile_ofr_thisblock['offset'] = $BlockOffset; 0121 $thisfile_ofr_thisblock['size'] = $BlockSize; 0122 0123 $info['audio']['encoder'] = 'OptimFROG 4.50 alpha'; 0124 switch ($BlockSize) { 0125 case 12: 0126 case 15: 0127 // good 0128 break; 0129 0130 default: 0131 $info['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)'; 0132 break; 0133 } 0134 $BlockData .= $this->fread($BlockSize); 0135 0136 $thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6)); 0137 $offset += 6; 0138 $thisfile_ofr_thisblock['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); 0139 $thisfile_ofr_thisblock['sample_type'] = $this->OptimFROGsampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); 0140 $offset += 1; 0141 $thisfile_ofr_thisblock['channel_config'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); 0142 $thisfile_ofr_thisblock['channels'] = $thisfile_ofr_thisblock['channel_config']; 0143 $offset += 1; 0144 $thisfile_ofr_thisblock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); 0145 $offset += 4; 0146 0147 if ($BlockSize > 12) { 0148 0149 // OFR 4.504b or higher 0150 $thisfile_ofr_thisblock['channels'] = $this->OptimFROGchannelConfigNumChannelsLookup($thisfile_ofr_thisblock['channel_config']); 0151 $thisfile_ofr_thisblock['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); 0152 $thisfile_ofr_thisblock['encoder'] = $this->OptimFROGencoderNameLookup($thisfile_ofr_thisblock['raw']['encoder_id']); 0153 $offset += 2; 0154 $thisfile_ofr_thisblock['raw']['compression'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); 0155 $thisfile_ofr_thisblock['compression'] = $this->OptimFROGcompressionLookup($thisfile_ofr_thisblock['raw']['compression']); 0156 $thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']); 0157 $offset += 1; 0158 0159 $info['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder']; 0160 $info['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression']; 0161 0162 if ((($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xF0) >> 4) == 7) { // v4.507 0163 if (strtolower(getid3_lib::fileextension($info['filename'])) == 'ofs') { 0164 // OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference 0165 // between lossless and lossy other than the file extension. 0166 $info['audio']['dataformat'] = 'ofs'; 0167 $info['audio']['lossless'] = true; 0168 } 0169 } 0170 0171 } 0172 0173 $info['audio']['channels'] = $thisfile_ofr_thisblock['channels']; 0174 $info['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate']; 0175 $info['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); 0176 break; 0177 0178 0179 case 'COMP': 0180 // unlike other block types, there CAN be multiple COMP blocks 0181 0182 $COMPdata['offset'] = $BlockOffset; 0183 $COMPdata['size'] = $BlockSize; 0184 0185 if ($info['avdataoffset'] == 0) { 0186 $info['avdataoffset'] = $BlockOffset; 0187 } 0188 0189 // Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data 0190 $BlockData .= $this->fread(14); 0191 $this->fseek($BlockSize - 14, SEEK_CUR); 0192 0193 $COMPdata['crc_32'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); 0194 $offset += 4; 0195 $COMPdata['sample_count'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); 0196 $offset += 4; 0197 $COMPdata['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); 0198 $COMPdata['sample_type'] = $this->OptimFROGsampleTypeLookup($COMPdata['raw']['sample_type']); 0199 $offset += 1; 0200 $COMPdata['raw']['channel_configuration'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); 0201 $COMPdata['channel_configuration'] = $this->OptimFROGchannelConfigurationLookup($COMPdata['raw']['channel_configuration']); 0202 $offset += 1; 0203 $COMPdata['raw']['algorithm_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); 0204 //$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']); 0205 $offset += 2; 0206 0207 if ($info['ofr']['OFR ']['size'] > 12) { 0208 0209 // OFR 4.504b or higher 0210 $COMPdata['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); 0211 $COMPdata['encoder'] = $this->OptimFROGencoderNameLookup($COMPdata['raw']['encoder_id']); 0212 $offset += 2; 0213 0214 } 0215 0216 if ($COMPdata['crc_32'] == 0x454E4F4E) { 0217 // ASCII value of 'NONE' - placeholder value in v4.50a 0218 $COMPdata['crc_32'] = false; 0219 } 0220 0221 $thisfile_ofr_thisblock[] = $COMPdata; 0222 break; 0223 0224 case 'HEAD': 0225 $thisfile_ofr_thisblock['offset'] = $BlockOffset; 0226 $thisfile_ofr_thisblock['size'] = $BlockSize; 0227 0228 $RIFFdata .= $this->fread($BlockSize); 0229 break; 0230 0231 case 'TAIL': 0232 $thisfile_ofr_thisblock['offset'] = $BlockOffset; 0233 $thisfile_ofr_thisblock['size'] = $BlockSize; 0234 0235 if ($BlockSize > 0) { 0236 $RIFFdata .= $this->fread($BlockSize); 0237 } 0238 break; 0239 0240 case 'RECV': 0241 // block contains no useful meta data - simply note and skip 0242 0243 $thisfile_ofr_thisblock['offset'] = $BlockOffset; 0244 $thisfile_ofr_thisblock['size'] = $BlockSize; 0245 0246 $this->fseek($BlockSize, SEEK_CUR); 0247 break; 0248 0249 0250 case 'APET': 0251 // APEtag v2 0252 0253 $thisfile_ofr_thisblock['offset'] = $BlockOffset; 0254 $thisfile_ofr_thisblock['size'] = $BlockSize; 0255 $info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.$this->getid3->version().') of getID3()'; 0256 0257 $this->fseek($BlockSize, SEEK_CUR); 0258 break; 0259 0260 0261 case 'MD5 ': 0262 // APEtag v2 0263 0264 $thisfile_ofr_thisblock['offset'] = $BlockOffset; 0265 $thisfile_ofr_thisblock['size'] = $BlockSize; 0266 0267 if ($BlockSize == 16) { 0268 0269 $thisfile_ofr_thisblock['md5_binary'] = $this->fread($BlockSize); 0270 $thisfile_ofr_thisblock['md5_string'] = getid3_lib::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false); 0271 $info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; 0272 0273 } else { 0274 0275 $info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead'; 0276 $this->fseek($BlockSize, SEEK_CUR); 0277 0278 } 0279 break; 0280 0281 0282 default: 0283 $thisfile_ofr_thisblock['offset'] = $BlockOffset; 0284 $thisfile_ofr_thisblock['size'] = $BlockSize; 0285 0286 $info['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset']; 0287 $this->fseek($BlockSize, SEEK_CUR); 0288 break; 0289 } 0290 } 0291 if (isset($info['ofr']['TAIL']['offset'])) { 0292 $info['avdataend'] = $info['ofr']['TAIL']['offset']; 0293 } 0294 0295 $info['playtime_seconds'] = (float) $info['ofr']['OFR ']['total_samples'] / ($info['audio']['channels'] * $info['audio']['sample_rate']); 0296 $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 0297 0298 // move the data chunk after all other chunks (if any) 0299 // so that the RIFF parser doesn't see EOF when trying 0300 // to skip over the data chunk 0301 $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); 0302 0303 $getid3_temp = new getID3(); 0304 $getid3_temp->openfile($this->getid3->filename); 0305 $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; 0306 $getid3_temp->info['avdataend'] = $info['avdataend']; 0307 $getid3_riff = new getid3_riff($getid3_temp); 0308 $getid3_riff->ParseRIFFdata($RIFFdata); 0309 $info['riff'] = $getid3_temp->info['riff']; 0310 0311 unset($getid3_riff, $getid3_temp, $RIFFdata); 0312 0313 return true; 0314 } 0315 0316 0317 public static function OptimFROGsampleTypeLookup($SampleType) { 0318 static $OptimFROGsampleTypeLookup = array( 0319 0 => 'unsigned int (8-bit)', 0320 1 => 'signed int (8-bit)', 0321 2 => 'unsigned int (16-bit)', 0322 3 => 'signed int (16-bit)', 0323 4 => 'unsigned int (24-bit)', 0324 5 => 'signed int (24-bit)', 0325 6 => 'unsigned int (32-bit)', 0326 7 => 'signed int (32-bit)', 0327 8 => 'float 0.24 (32-bit)', 0328 9 => 'float 16.8 (32-bit)', 0329 10 => 'float 24.0 (32-bit)' 0330 ); 0331 return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false); 0332 } 0333 0334 public static function OptimFROGbitsPerSampleTypeLookup($SampleType) { 0335 static $OptimFROGbitsPerSampleTypeLookup = array( 0336 0 => 8, 0337 1 => 8, 0338 2 => 16, 0339 3 => 16, 0340 4 => 24, 0341 5 => 24, 0342 6 => 32, 0343 7 => 32, 0344 8 => 32, 0345 9 => 32, 0346 10 => 32 0347 ); 0348 return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false); 0349 } 0350 0351 public static function OptimFROGchannelConfigurationLookup($ChannelConfiguration) { 0352 static $OptimFROGchannelConfigurationLookup = array( 0353 0 => 'mono', 0354 1 => 'stereo' 0355 ); 0356 return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false); 0357 } 0358 0359 public static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) { 0360 static $OptimFROGchannelConfigNumChannelsLookup = array( 0361 0 => 1, 0362 1 => 2 0363 ); 0364 return (isset($OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration] : false); 0365 } 0366 0367 0368 0369 // static function OptimFROGalgorithmNameLookup($AlgorithID) { 0370 // static $OptimFROGalgorithmNameLookup = array(); 0371 // return (isset($OptimFROGalgorithmNameLookup[$AlgorithID]) ? $OptimFROGalgorithmNameLookup[$AlgorithID] : false); 0372 // } 0373 0374 0375 public static function OptimFROGencoderNameLookup($EncoderID) { 0376 // version = (encoderID >> 4) + 4500 0377 // system = encoderID & 0xF 0378 0379 $EncoderVersion = number_format(((($EncoderID & 0xF0) >> 4) + 4500) / 1000, 3); 0380 $EncoderSystemID = ($EncoderID & 0x0F); 0381 0382 static $OptimFROGencoderSystemLookup = array( 0383 0x00 => 'Windows console', 0384 0x01 => 'Linux console', 0385 0x0F => 'unknown' 0386 ); 0387 return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')'; 0388 } 0389 0390 public static function OptimFROGcompressionLookup($CompressionID) { 0391 // mode = compression >> 3 0392 // speedup = compression & 0x07 0393 0394 $CompressionModeID = ($CompressionID & 0xF8) >> 3; 0395 //$CompressionSpeedupID = ($CompressionID & 0x07); 0396 0397 static $OptimFROGencoderModeLookup = array( 0398 0x00 => 'fast', 0399 0x01 => 'normal', 0400 0x02 => 'high', 0401 0x03 => 'extra', // extranew (some versions) 0402 0x04 => 'best', // bestnew (some versions) 0403 0x05 => 'ultra', 0404 0x06 => 'insane', 0405 0x07 => 'highnew', 0406 0x08 => 'extranew', 0407 0x09 => 'bestnew' 0408 ); 0409 return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')'); 0410 } 0411 0412 public static function OptimFROGspeedupLookup($CompressionID) { 0413 // mode = compression >> 3 0414 // speedup = compression & 0x07 0415 0416 //$CompressionModeID = ($CompressionID & 0xF8) >> 3; 0417 $CompressionSpeedupID = ($CompressionID & 0x07); 0418 0419 static $OptimFROGencoderSpeedupLookup = array( 0420 0x00 => '1x', 0421 0x01 => '2x', 0422 0x02 => '4x' 0423 ); 0424 return (isset($OptimFROGencoderSpeedupLookup[$CompressionSpeedupID]) ? $OptimFROGencoderSpeedupLookup[$CompressionSpeedupID] : 'undefined mode (0x'.dechex($CompressionSpeedupID)); 0425 } 0426 0427 }