public function ParseOptimFROGheader45() { // for fileformat of v4.50a and higher $info =& $this->getid3->info; $RIFFdata = ''; $this->fseek($info['avdataoffset']); while (!feof($this->getid3->fp) && $this->ftell() < $info['avdataend']) { $BlockOffset = $this->ftell(); $BlockData = $this->fread(8); $offset = 8; $BlockName = substr($BlockData, 0, 4); $BlockSize = Utils::LittleEndian2Int(substr($BlockData, 4, 4)); if ($BlockName == 'OFRX') { $BlockName = 'OFR '; } if (!isset($info['ofr'][$BlockName])) { $info['ofr'][$BlockName] = array(); } $thisfile_ofr_thisblock =& $info['ofr'][$BlockName]; switch ($BlockName) { case 'OFR ': // shortcut $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $info['audio']['encoder'] = 'OptimFROG 4.50 alpha'; switch ($BlockSize) { case 12: case 15: // good break; default: $info['warning'][] = '"' . $BlockName . '" contains more data than expected (expected 12 or 15 bytes, found ' . $BlockSize . ' bytes)'; break; } $BlockData .= $this->fread($BlockSize); $thisfile_ofr_thisblock['total_samples'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 6)); $offset += 6; $thisfile_ofr_thisblock['raw']['sample_type'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 1)); $thisfile_ofr_thisblock['sample_type'] = $this->OptimFROGsampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); $offset += 1; $thisfile_ofr_thisblock['channel_config'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 1)); $thisfile_ofr_thisblock['channels'] = $thisfile_ofr_thisblock['channel_config']; $offset += 1; $thisfile_ofr_thisblock['sample_rate'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 4)); $offset += 4; if ($BlockSize > 12) { // OFR 4.504b or higher $thisfile_ofr_thisblock['channels'] = $this->OptimFROGchannelConfigNumChannelsLookup($thisfile_ofr_thisblock['channel_config']); $thisfile_ofr_thisblock['raw']['encoder_id'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 2)); $thisfile_ofr_thisblock['encoder'] = $this->OptimFROGencoderNameLookup($thisfile_ofr_thisblock['raw']['encoder_id']); $offset += 2; $thisfile_ofr_thisblock['raw']['compression'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 1)); $thisfile_ofr_thisblock['compression'] = $this->OptimFROGcompressionLookup($thisfile_ofr_thisblock['raw']['compression']); $thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']); $offset += 1; $info['audio']['encoder'] = 'OptimFROG ' . $thisfile_ofr_thisblock['encoder']; $info['audio']['encoder_options'] = '--mode ' . $thisfile_ofr_thisblock['compression']; if (($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xf0) >> 4 == 7) { // v4.507 if (strtolower(Utils::fileextension($info['filename'])) == 'ofs') { // OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference // between lossless and lossy other than the file extension. $info['audio']['dataformat'] = 'ofs'; $info['audio']['lossless'] = true; } } } $info['audio']['channels'] = $thisfile_ofr_thisblock['channels']; $info['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate']; $info['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); break; case 'COMP': // unlike other block types, there CAN be multiple COMP blocks $COMPdata['offset'] = $BlockOffset; $COMPdata['size'] = $BlockSize; if ($info['avdataoffset'] == 0) { $info['avdataoffset'] = $BlockOffset; } // Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data $BlockData .= $this->fread(14); $this->fseek($BlockSize - 14, SEEK_CUR); $COMPdata['crc_32'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 4)); $offset += 4; $COMPdata['sample_count'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 4)); $offset += 4; $COMPdata['raw']['sample_type'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 1)); $COMPdata['sample_type'] = $this->OptimFROGsampleTypeLookup($COMPdata['raw']['sample_type']); $offset += 1; $COMPdata['raw']['channel_configuration'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 1)); $COMPdata['channel_configuration'] = $this->OptimFROGchannelConfigurationLookup($COMPdata['raw']['channel_configuration']); $offset += 1; $COMPdata['raw']['algorithm_id'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 2)); //$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']); $offset += 2; if ($info['ofr']['OFR ']['size'] > 12) { // OFR 4.504b or higher $COMPdata['raw']['encoder_id'] = Utils::LittleEndian2Int(substr($BlockData, $offset, 2)); $COMPdata['encoder'] = $this->OptimFROGencoderNameLookup($COMPdata['raw']['encoder_id']); $offset += 2; } if ($COMPdata['crc_32'] == 0x454e4f4e) { // ASCII value of 'NONE' - placeholder value in v4.50a $COMPdata['crc_32'] = false; } $thisfile_ofr_thisblock[] = $COMPdata; break; case 'HEAD': $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $RIFFdata .= $this->fread($BlockSize); break; case 'TAIL': $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; if ($BlockSize > 0) { $RIFFdata .= $this->fread($BlockSize); } break; case 'RECV': // block contains no useful meta data - simply note and skip $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $this->fseek($BlockSize, SEEK_CUR); break; case 'APET': // APEtag v2 $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version (' . $this->getid3->version() . ') of getID3()'; $this->fseek($BlockSize, SEEK_CUR); break; case 'MD5 ': // APEtag v2 $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; if ($BlockSize == 16) { $thisfile_ofr_thisblock['md5_binary'] = $this->fread($BlockSize); $thisfile_ofr_thisblock['md5_string'] = Utils::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false); $info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; } else { $info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found ' . $BlockSize . ' instead'; $this->fseek($BlockSize, SEEK_CUR); } break; default: $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; $info['warning'][] = 'Unhandled OptimFROG block type "' . $BlockName . '" at offset ' . $thisfile_ofr_thisblock['offset']; $this->fseek($BlockSize, SEEK_CUR); break; } } if (isset($info['ofr']['TAIL']['offset'])) { $info['avdataend'] = $info['ofr']['TAIL']['offset']; } $info['playtime_seconds'] = (double) $info['ofr']['OFR ']['total_samples'] / ($info['audio']['channels'] * $info['audio']['sample_rate']); $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; // move the data chunk after all other chunks (if any) // so that the RIFF parser doesn't see EOF when trying // to skip over the data chunk $RIFFdata = substr($RIFFdata, 0, 36) . substr($RIFFdata, 44) . substr($RIFFdata, 36, 8); $getid3_temp = new GetID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; $getid3_temp->info['avdataend'] = $info['avdataend']; $getid3_riff = new Riff($getid3_temp); $getid3_riff->ParseRIFFdata($RIFFdata); $info['riff'] = $getid3_temp->info['riff']; unset($getid3_riff, $getid3_temp, $RIFFdata); return true; }