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.nsv.php // 12// module for analyzing Nullsoft NSV files // 13// dependencies: NONE // 14// /// 15///////////////////////////////////////////////////////////////// 16 17if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers 18 exit; 19} 20 21class getid3_nsv extends getid3_handler 22{ 23 /** 24 * @return bool 25 */ 26 public function Analyze() { 27 $info = &$this->getid3->info; 28 29 $this->fseek($info['avdataoffset']); 30 $NSVheader = $this->fread(4); 31 32 switch ($NSVheader) { 33 case 'NSVs': 34 if ($this->getNSVsHeaderFilepointer(0)) { 35 $info['fileformat'] = 'nsv'; 36 $info['audio']['dataformat'] = 'nsv'; 37 $info['video']['dataformat'] = 'nsv'; 38 $info['audio']['lossless'] = false; 39 $info['video']['lossless'] = false; 40 } 41 break; 42 43 case 'NSVf': 44 if ($this->getNSVfHeaderFilepointer(0)) { 45 $info['fileformat'] = 'nsv'; 46 $info['audio']['dataformat'] = 'nsv'; 47 $info['video']['dataformat'] = 'nsv'; 48 $info['audio']['lossless'] = false; 49 $info['video']['lossless'] = false; 50 $this->getNSVsHeaderFilepointer($info['nsv']['NSVf']['header_length']); 51 } 52 break; 53 54 default: 55 $this->error('Expecting "NSVs" or "NSVf" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($NSVheader).'"'); 56 return false; 57 } 58 59 if (!isset($info['nsv']['NSVf'])) { 60 $this->warning('NSVf header not present - cannot calculate playtime or bitrate'); 61 } 62 63 return true; 64 } 65 66 /** 67 * @param int $fileoffset 68 * 69 * @return bool 70 */ 71 public function getNSVsHeaderFilepointer($fileoffset) { 72 $info = &$this->getid3->info; 73 $this->fseek($fileoffset); 74 $NSVsheader = $this->fread(28); 75 $offset = 0; 76 77 $info['nsv']['NSVs']['identifier'] = substr($NSVsheader, $offset, 4); 78 $offset += 4; 79 80 if ($info['nsv']['NSVs']['identifier'] != 'NSVs') { 81 $this->error('expected "NSVs" at offset ('.$fileoffset.'), found "'.$info['nsv']['NSVs']['identifier'].'" instead'); 82 unset($info['nsv']['NSVs']); 83 return false; 84 } 85 86 $info['nsv']['NSVs']['offset'] = $fileoffset; 87 88 $info['nsv']['NSVs']['video_codec'] = substr($NSVsheader, $offset, 4); 89 $offset += 4; 90 $info['nsv']['NSVs']['audio_codec'] = substr($NSVsheader, $offset, 4); 91 $offset += 4; 92 $info['nsv']['NSVs']['resolution_x'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); 93 $offset += 2; 94 $info['nsv']['NSVs']['resolution_y'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); 95 $offset += 2; 96 97 $info['nsv']['NSVs']['framerate_index'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 98 $offset += 1; 99 //$info['nsv']['NSVs']['unknown1b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 100 $offset += 1; 101 //$info['nsv']['NSVs']['unknown1c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 102 $offset += 1; 103 //$info['nsv']['NSVs']['unknown1d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 104 $offset += 1; 105 //$info['nsv']['NSVs']['unknown2a'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 106 $offset += 1; 107 //$info['nsv']['NSVs']['unknown2b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 108 $offset += 1; 109 //$info['nsv']['NSVs']['unknown2c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 110 $offset += 1; 111 //$info['nsv']['NSVs']['unknown2d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 112 $offset += 1; 113 114 switch ($info['nsv']['NSVs']['audio_codec']) { 115 case 'PCM ': 116 $info['nsv']['NSVs']['bits_channel'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 117 $offset += 1; 118 $info['nsv']['NSVs']['channels'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); 119 $offset += 1; 120 $info['nsv']['NSVs']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); 121 $offset += 2; 122 123 $info['audio']['sample_rate'] = $info['nsv']['NSVs']['sample_rate']; 124 break; 125 126 case 'MP3 ': 127 case 'NONE': 128 default: 129 //$info['nsv']['NSVs']['unknown3'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 4)); 130 $offset += 4; 131 break; 132 } 133 134 $info['video']['resolution_x'] = $info['nsv']['NSVs']['resolution_x']; 135 $info['video']['resolution_y'] = $info['nsv']['NSVs']['resolution_y']; 136 $info['nsv']['NSVs']['frame_rate'] = $this->NSVframerateLookup($info['nsv']['NSVs']['framerate_index']); 137 $info['video']['frame_rate'] = $info['nsv']['NSVs']['frame_rate']; 138 $info['video']['bits_per_sample'] = 24; 139 $info['video']['pixel_aspect_ratio'] = (float) 1; 140 141 return true; 142 } 143 144 /** 145 * @param int $fileoffset 146 * @param bool $getTOCoffsets 147 * 148 * @return bool 149 */ 150 public function getNSVfHeaderFilepointer($fileoffset, $getTOCoffsets=false) { 151 $info = &$this->getid3->info; 152 $this->fseek($fileoffset); 153 $NSVfheader = $this->fread(28); 154 $offset = 0; 155 156 $info['nsv']['NSVf']['identifier'] = substr($NSVfheader, $offset, 4); 157 $offset += 4; 158 159 if ($info['nsv']['NSVf']['identifier'] != 'NSVf') { 160 $this->error('expected "NSVf" at offset ('.$fileoffset.'), found "'.$info['nsv']['NSVf']['identifier'].'" instead'); 161 unset($info['nsv']['NSVf']); 162 return false; 163 } 164 165 $info['nsv']['NSVs']['offset'] = $fileoffset; 166 167 $info['nsv']['NSVf']['header_length'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); 168 $offset += 4; 169 $info['nsv']['NSVf']['file_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); 170 $offset += 4; 171 172 if ($info['nsv']['NSVf']['file_size'] > $info['avdataend']) { 173 $this->warning('truncated file - NSVf header indicates '.$info['nsv']['NSVf']['file_size'].' bytes, file actually '.$info['avdataend'].' bytes'); 174 } 175 176 $info['nsv']['NSVf']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); 177 $offset += 4; 178 $info['nsv']['NSVf']['meta_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); 179 $offset += 4; 180 $info['nsv']['NSVf']['TOC_entries_1'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); 181 $offset += 4; 182 $info['nsv']['NSVf']['TOC_entries_2'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); 183 $offset += 4; 184 185 if ($info['nsv']['NSVf']['playtime_ms'] == 0) { 186 $this->error('Corrupt NSV file: NSVf.playtime_ms == zero'); 187 return false; 188 } 189 190 $NSVfheader .= $this->fread($info['nsv']['NSVf']['meta_size'] + (4 * $info['nsv']['NSVf']['TOC_entries_1']) + (4 * $info['nsv']['NSVf']['TOC_entries_2'])); 191 $NSVfheaderlength = strlen($NSVfheader); 192 $info['nsv']['NSVf']['metadata'] = substr($NSVfheader, $offset, $info['nsv']['NSVf']['meta_size']); 193 $offset += $info['nsv']['NSVf']['meta_size']; 194 195 if ($getTOCoffsets) { 196 $TOCcounter = 0; 197 while ($TOCcounter < $info['nsv']['NSVf']['TOC_entries_1']) { 198 if ($TOCcounter < $info['nsv']['NSVf']['TOC_entries_1']) { 199 $info['nsv']['NSVf']['TOC_1'][$TOCcounter] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); 200 $offset += 4; 201 $TOCcounter++; 202 } 203 } 204 } 205 206 if (trim($info['nsv']['NSVf']['metadata']) != '') { 207 $info['nsv']['NSVf']['metadata'] = str_replace('`', "\x01", $info['nsv']['NSVf']['metadata']); 208 $CommentPairArray = explode("\x01".' ', $info['nsv']['NSVf']['metadata']); 209 foreach ($CommentPairArray as $CommentPair) { 210 if (strstr($CommentPair, '='."\x01")) { 211 list($key, $value) = explode('='."\x01", $CommentPair, 2); 212 $info['nsv']['comments'][strtolower($key)][] = trim(str_replace("\x01", '', $value)); 213 } 214 } 215 } 216 217 $info['playtime_seconds'] = $info['nsv']['NSVf']['playtime_ms'] / 1000; 218 $info['bitrate'] = ($info['nsv']['NSVf']['file_size'] * 8) / $info['playtime_seconds']; 219 220 return true; 221 } 222 223 /** 224 * @param int $framerateindex 225 * 226 * @return float|false 227 */ 228 public static function NSVframerateLookup($framerateindex) { 229 if ($framerateindex <= 127) { 230 return (float) $framerateindex; 231 } 232 static $NSVframerateLookup = array(); 233 if (empty($NSVframerateLookup)) { 234 $NSVframerateLookup[129] = 29.970; 235 $NSVframerateLookup[131] = 23.976; 236 $NSVframerateLookup[133] = 14.985; 237 $NSVframerateLookup[197] = 59.940; 238 $NSVframerateLookup[199] = 47.952; 239 } 240 return (isset($NSVframerateLookup[$framerateindex]) ? $NSVframerateLookup[$framerateindex] : false); 241 } 242 243} 244