1<?php 2 3///////////////////////////////////////////////////////////////// 4/// getID3() by James Heinrich <info@getid3.org> // 5// available at https://github.com/JamesHeinrich/getID3 // 6// or https://www.getid3.org // 7// or http://getid3.sourceforge.net // 8// see readme.txt for more details // 9///////////////////////////////////////////////////////////////// 10// // 11// module.audio.la.php // 12// module for analyzing BONK audio files // 13// dependencies: module.tag.id3v2.php (optional) // 14// /// 15///////////////////////////////////////////////////////////////// 16 17if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers 18 exit; 19} 20 21class getid3_bonk extends getid3_handler 22{ 23 /** 24 * @return bool 25 */ 26 public function Analyze() { 27 $info = &$this->getid3->info; 28 29 // shortcut 30 $info['bonk'] = array(); 31 $thisfile_bonk = &$info['bonk']; 32 33 $thisfile_bonk['dataoffset'] = $info['avdataoffset']; 34 $thisfile_bonk['dataend'] = $info['avdataend']; 35 36 if (!getid3_lib::intValueSupported($thisfile_bonk['dataend'])) { 37 38 $this->warning('Unable to parse BONK file from end (v0.6+ preferred method) because PHP filesystem functions only support up to '.round(PHP_INT_MAX / 1073741824).'GB'); 39 40 } else { 41 42 // scan-from-end method, for v0.6 and higher 43 $this->fseek($thisfile_bonk['dataend'] - 8); 44 $PossibleBonkTag = $this->fread(8); 45 while ($this->BonkIsValidTagName(substr($PossibleBonkTag, 4, 4), true)) { 46 $BonkTagSize = getid3_lib::LittleEndian2Int(substr($PossibleBonkTag, 0, 4)); 47 $this->fseek(0 - $BonkTagSize, SEEK_CUR); 48 $BonkTagOffset = $this->ftell(); 49 $TagHeaderTest = $this->fread(5); 50 if (($TagHeaderTest[0] != "\x00") || (substr($PossibleBonkTag, 4, 4) != strtolower(substr($PossibleBonkTag, 4, 4)))) { 51 $this->error('Expecting "'.getid3_lib::PrintHexBytes("\x00".strtoupper(substr($PossibleBonkTag, 4, 4))).'" at offset '.$BonkTagOffset.', found "'.getid3_lib::PrintHexBytes($TagHeaderTest).'"'); 52 return false; 53 } 54 $BonkTagName = substr($TagHeaderTest, 1, 4); 55 56 $thisfile_bonk[$BonkTagName]['size'] = $BonkTagSize; 57 $thisfile_bonk[$BonkTagName]['offset'] = $BonkTagOffset; 58 $this->HandleBonkTags($BonkTagName); 59 $NextTagEndOffset = $BonkTagOffset - 8; 60 if ($NextTagEndOffset < $thisfile_bonk['dataoffset']) { 61 if (empty($info['audio']['encoder'])) { 62 $info['audio']['encoder'] = 'Extended BONK v0.9+'; 63 } 64 return true; 65 } 66 $this->fseek($NextTagEndOffset); 67 $PossibleBonkTag = $this->fread(8); 68 } 69 70 } 71 72 // seek-from-beginning method for v0.4 and v0.5 73 if (empty($thisfile_bonk['BONK'])) { 74 $this->fseek($thisfile_bonk['dataoffset']); 75 do { 76 $TagHeaderTest = $this->fread(5); 77 switch ($TagHeaderTest) { 78 case "\x00".'BONK': 79 if (empty($info['audio']['encoder'])) { 80 $info['audio']['encoder'] = 'BONK v0.4'; 81 } 82 break; 83 84 case "\x00".'INFO': 85 $info['audio']['encoder'] = 'Extended BONK v0.5'; 86 break; 87 88 default: 89 break 2; 90 } 91 $BonkTagName = substr($TagHeaderTest, 1, 4); 92 $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset']; 93 $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset']; 94 $this->HandleBonkTags($BonkTagName); 95 96 } while (true); 97 } 98 99 // parse META block for v0.6 - v0.8 100 if (empty($thisfile_bonk['INFO']) && isset($thisfile_bonk['META']['tags']['info'])) { 101 $this->fseek($thisfile_bonk['META']['tags']['info']); 102 $TagHeaderTest = $this->fread(5); 103 if ($TagHeaderTest == "\x00".'INFO') { 104 $info['audio']['encoder'] = 'Extended BONK v0.6 - v0.8'; 105 106 $BonkTagName = substr($TagHeaderTest, 1, 4); 107 $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset']; 108 $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset']; 109 $this->HandleBonkTags($BonkTagName); 110 } 111 } 112 113 if (empty($info['audio']['encoder'])) { 114 $info['audio']['encoder'] = 'Extended BONK v0.9+'; 115 } 116 if (empty($thisfile_bonk['BONK'])) { 117 unset($info['bonk']); 118 } 119 return true; 120 121 } 122 123 /** 124 * @param string $BonkTagName 125 */ 126 public function HandleBonkTags($BonkTagName) { 127 $info = &$this->getid3->info; 128 switch ($BonkTagName) { 129 case 'BONK': 130 // shortcut 131 $thisfile_bonk_BONK = &$info['bonk']['BONK']; 132 133 $BonkData = "\x00".'BONK'.$this->fread(17); 134 $thisfile_bonk_BONK['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); 135 $thisfile_bonk_BONK['number_samples'] = getid3_lib::LittleEndian2Int(substr($BonkData, 6, 4)); 136 $thisfile_bonk_BONK['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BonkData, 10, 4)); 137 138 $thisfile_bonk_BONK['channels'] = getid3_lib::LittleEndian2Int(substr($BonkData, 14, 1)); 139 $thisfile_bonk_BONK['lossless'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 15, 1)); 140 $thisfile_bonk_BONK['joint_stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 16, 1)); 141 $thisfile_bonk_BONK['number_taps'] = getid3_lib::LittleEndian2Int(substr($BonkData, 17, 2)); 142 $thisfile_bonk_BONK['downsampling_ratio'] = getid3_lib::LittleEndian2Int(substr($BonkData, 19, 1)); 143 $thisfile_bonk_BONK['samples_per_packet'] = getid3_lib::LittleEndian2Int(substr($BonkData, 20, 2)); 144 145 $info['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17; 146 $info['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size']; 147 148 $info['fileformat'] = 'bonk'; 149 $info['audio']['dataformat'] = 'bonk'; 150 $info['audio']['bitrate_mode'] = 'vbr'; // assumed 151 $info['audio']['channels'] = $thisfile_bonk_BONK['channels']; 152 $info['audio']['sample_rate'] = $thisfile_bonk_BONK['sample_rate']; 153 $info['audio']['channelmode'] = ($thisfile_bonk_BONK['joint_stereo'] ? 'joint stereo' : 'stereo'); 154 $info['audio']['lossless'] = $thisfile_bonk_BONK['lossless']; 155 $info['audio']['codec'] = 'bonk'; 156 157 $info['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']); 158 if ($info['playtime_seconds'] > 0) { 159 $info['audio']['bitrate'] = (($info['bonk']['dataend'] - $info['bonk']['dataoffset']) * 8) / $info['playtime_seconds']; 160 } 161 break; 162 163 case 'INFO': 164 // shortcut 165 $thisfile_bonk_INFO = &$info['bonk']['INFO']; 166 167 $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int($this->fread(1)); 168 $thisfile_bonk_INFO['entries_count'] = 0; 169 $NextInfoDataPair = $this->fread(5); 170 if (!$this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { 171 while (!feof($this->getid3->fp)) { 172 //$CurrentSeekInfo['offset'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 0, 4)); 173 //$CurrentSeekInfo['nextbit'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 4, 1)); 174 //$thisfile_bonk_INFO[] = $CurrentSeekInfo; 175 176 $NextInfoDataPair = $this->fread(5); 177 if ($this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { 178 $this->fseek(-5, SEEK_CUR); 179 break; 180 } 181 $thisfile_bonk_INFO['entries_count']++; 182 } 183 } 184 break; 185 186 case 'META': 187 $BonkData = "\x00".'META'.$this->fread($info['bonk']['META']['size'] - 5); 188 $info['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); 189 190 $MetaTagEntries = floor(((strlen($BonkData) - 8) - 6) / 8); // BonkData - xxxxmeta - ØMETA 191 $offset = 6; 192 for ($i = 0; $i < $MetaTagEntries; $i++) { 193 $MetaEntryTagName = substr($BonkData, $offset, 4); 194 $offset += 4; 195 $MetaEntryTagOffset = getid3_lib::LittleEndian2Int(substr($BonkData, $offset, 4)); 196 $offset += 4; 197 $info['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset; 198 } 199 break; 200 201 case ' ID3': 202 $info['audio']['encoder'] = 'Extended BONK v0.9+'; 203 204 // ID3v2 checking is optional 205 if (class_exists('getid3_id3v2')) { 206 $getid3_temp = new getID3(); 207 $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp); 208 $getid3_id3v2 = new getid3_id3v2($getid3_temp); 209 $getid3_id3v2->StartingOffset = $info['bonk'][' ID3']['offset'] + 2; 210 $info['bonk'][' ID3']['valid'] = $getid3_id3v2->Analyze(); 211 if ($info['bonk'][' ID3']['valid']) { 212 $info['id3v2'] = $getid3_temp->info['id3v2']; 213 } 214 unset($getid3_temp, $getid3_id3v2); 215 } 216 break; 217 218 default: 219 $this->warning('Unexpected Bonk tag "'.$BonkTagName.'" at offset '.$info['bonk'][$BonkTagName]['offset']); 220 break; 221 222 } 223 } 224 225 /** 226 * @param string $PossibleBonkTag 227 * @param bool $ignorecase 228 * 229 * @return bool 230 */ 231 public static function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) { 232 static $BonkIsValidTagName = array('BONK', 'INFO', ' ID3', 'META'); 233 foreach ($BonkIsValidTagName as $validtagname) { 234 if ($validtagname == $PossibleBonkTag) { 235 return true; 236 } elseif ($ignorecase && (strtolower($validtagname) == strtolower($PossibleBonkTag))) { 237 return true; 238 } 239 } 240 return false; 241 } 242 243} 244