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 }