[3318] | 1 | <?php |
---|
[3544] | 2 | ///////////////////////////////////////////////////////////////// |
---|
| 3 | /// getID3() by James Heinrich <info@getid3.org> // |
---|
| 4 | // available at http://getid3.sourceforge.net // |
---|
| 5 | // or http://www.getid3.org // |
---|
| 6 | ///////////////////////////////////////////////////////////////// |
---|
| 7 | // See readme.txt for more details // |
---|
| 8 | ///////////////////////////////////////////////////////////////// |
---|
| 9 | // // |
---|
| 10 | // module.audio.mpc.php // |
---|
| 11 | // module for analyzing Musepack/MPEG+ Audio files // |
---|
| 12 | // dependencies: NONE // |
---|
| 13 | // /// |
---|
| 14 | ///////////////////////////////////////////////////////////////// |
---|
[3318] | 15 | |
---|
[3544] | 16 | |
---|
| 17 | class getid3_mpc |
---|
[3318] | 18 | { |
---|
| 19 | |
---|
[3544] | 20 | function getid3_mpc(&$fd, &$ThisFileInfo) { |
---|
| 21 | $ThisFileInfo['mpc']['header'] = array(); |
---|
| 22 | $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; |
---|
[3318] | 23 | |
---|
[3544] | 24 | $ThisFileInfo['fileformat'] = 'mpc'; |
---|
| 25 | $ThisFileInfo['audio']['dataformat'] = 'mpc'; |
---|
| 26 | $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; |
---|
| 27 | $ThisFileInfo['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only |
---|
| 28 | $ThisFileInfo['audio']['lossless'] = false; |
---|
[3318] | 29 | |
---|
[3544] | 30 | fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); |
---|
| 31 | $MPCheaderData = fread($fd, 4); |
---|
| 32 | $ThisFileInfo['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6) |
---|
| 33 | if (ereg('^MPCK', $ThisFileInfo['mpc']['header']['preamble'])) { |
---|
[3318] | 34 | |
---|
[3544] | 35 | // this is SV8 |
---|
| 36 | return $this->ParseMPCsv8($fd, $ThisFileInfo); |
---|
[3318] | 37 | |
---|
[3544] | 38 | } elseif (ereg('^MP\+', $ThisFileInfo['mpc']['header']['preamble'])) { |
---|
[3318] | 39 | |
---|
[3544] | 40 | // this is SV7 |
---|
| 41 | return $this->ParseMPCsv7($fd, $ThisFileInfo); |
---|
[3318] | 42 | |
---|
[3544] | 43 | } elseif (preg_match('/^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]/s', $MPCheaderData)) { |
---|
[3318] | 44 | |
---|
[3544] | 45 | // this is SV4 - SV6, handle seperately |
---|
| 46 | return $this->ParseMPCsv6($fd, $ThisFileInfo); |
---|
[3318] | 47 | |
---|
[3544] | 48 | } else { |
---|
[3318] | 49 | |
---|
[3544] | 50 | $ThisFileInfo['error'][] = 'Expecting "MP+" or "MPCK" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($MPCheaderData, 0, 4).'"'; |
---|
| 51 | unset($ThisFileInfo['fileformat']); |
---|
| 52 | unset($ThisFileInfo['mpc']); |
---|
| 53 | return false; |
---|
[3318] | 54 | |
---|
[3544] | 55 | } |
---|
| 56 | return false; |
---|
| 57 | } |
---|
[3318] | 58 | |
---|
| 59 | |
---|
[3544] | 60 | function ParseMPCsv8(&$fd, &$ThisFileInfo) { |
---|
| 61 | // this is SV8 |
---|
| 62 | // http://trac.musepack.net/trac/wiki/SV8Specification |
---|
[3318] | 63 | |
---|
[3544] | 64 | $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; |
---|
[3318] | 65 | |
---|
[3544] | 66 | $keyNameSize = 2; |
---|
| 67 | $maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10" |
---|
[3318] | 68 | |
---|
[3544] | 69 | $offset = ftell($fd); |
---|
| 70 | while ($offset < $ThisFileInfo['avdataend']) { |
---|
| 71 | $thisPacket = array(); |
---|
| 72 | $thisPacket['offset'] = $offset; |
---|
| 73 | $packet_offset = 0; |
---|
[3318] | 74 | |
---|
[3544] | 75 | // Size is a variable-size field, could be 1-4 bytes (possibly more?) |
---|
| 76 | // read enough data in and figure out the exact size later |
---|
| 77 | $MPCheaderData = fread($fd, $keyNameSize + $maxHandledPacketLength); |
---|
| 78 | $packet_offset += $keyNameSize; |
---|
| 79 | $thisPacket['key'] = substr($MPCheaderData, 0, $keyNameSize); |
---|
| 80 | $thisPacket['key_name'] = $this->MPCsv8PacketName($thisPacket['key']); |
---|
| 81 | if ($thisPacket['key'] == $thisPacket['key_name']) { |
---|
| 82 | $ThisFileInfo['error'][] = 'Found unexpected key value "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; |
---|
| 83 | return false; |
---|
| 84 | } |
---|
| 85 | $packetLength = 0; |
---|
| 86 | $thisPacket['packet_size'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $keyNameSize), $packetLength); // includes keyname and packet_size field |
---|
| 87 | if ($thisPacket['packet_size'] === false) { |
---|
| 88 | $ThisFileInfo['error'][] = 'Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize); |
---|
| 89 | return false; |
---|
| 90 | } |
---|
| 91 | $packet_offset += $packetLength; |
---|
| 92 | $offset += $thisPacket['packet_size']; |
---|
[3318] | 93 | |
---|
[3544] | 94 | switch ($thisPacket['key']) { |
---|
| 95 | case 'SH': // Stream Header |
---|
| 96 | $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; |
---|
| 97 | if ($moreBytesToRead > 0) { |
---|
| 98 | $MPCheaderData .= fread($fd, $moreBytesToRead); |
---|
| 99 | } |
---|
| 100 | $thisPacket['crc'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4)); |
---|
| 101 | $packet_offset += 4; |
---|
| 102 | $thisPacket['stream_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); |
---|
| 103 | $packet_offset += 1; |
---|
[3318] | 104 | |
---|
[3544] | 105 | $packetLength = 0; |
---|
| 106 | $thisPacket['sample_count'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); |
---|
| 107 | $packet_offset += $packetLength; |
---|
[3318] | 108 | |
---|
[3544] | 109 | $packetLength = 0; |
---|
| 110 | $thisPacket['beginning_silence'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); |
---|
| 111 | $packet_offset += $packetLength; |
---|
[3318] | 112 | |
---|
[3544] | 113 | $otherUsefulData = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); |
---|
| 114 | $packet_offset += 2; |
---|
| 115 | $thisPacket['sample_frequency_raw'] = (($otherUsefulData & 0xE000) >> 13); |
---|
| 116 | $thisPacket['max_bands_used'] = (($otherUsefulData & 0x1F00) >> 8); |
---|
| 117 | $thisPacket['channels'] = (($otherUsefulData & 0x00F0) >> 4) + 1; |
---|
| 118 | $thisPacket['ms_used'] = (bool) (($otherUsefulData & 0x0008) >> 3); |
---|
| 119 | $thisPacket['audio_block_frames'] = (($otherUsefulData & 0x0007) >> 0); |
---|
| 120 | $thisPacket['sample_frequency'] = $this->MPCfrequencyLookup($thisPacket['sample_frequency_raw']); |
---|
[3318] | 121 | |
---|
[3544] | 122 | $thisfile_mpc_header['mid_side_stereo'] = $thisPacket['ms_used']; |
---|
| 123 | $thisfile_mpc_header['sample_rate'] = $thisPacket['sample_frequency']; |
---|
| 124 | $thisfile_mpc_header['samples'] = $thisPacket['sample_count']; |
---|
| 125 | $thisfile_mpc_header['stream_version_major'] = $thisPacket['stream_version']; |
---|
[3318] | 126 | |
---|
[3544] | 127 | $ThisFileInfo['audio']['channels'] = $thisPacket['channels']; |
---|
| 128 | $ThisFileInfo['audio']['sample_rate'] = $thisPacket['sample_frequency']; |
---|
| 129 | $ThisFileInfo['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency']; |
---|
| 130 | $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; |
---|
| 131 | break; |
---|
[3318] | 132 | |
---|
[3544] | 133 | case 'RG': // Replay Gain |
---|
| 134 | $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; |
---|
| 135 | if ($moreBytesToRead > 0) { |
---|
| 136 | $MPCheaderData .= fread($fd, $moreBytesToRead); |
---|
| 137 | } |
---|
| 138 | $thisPacket['replaygain_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); |
---|
| 139 | $packet_offset += 1; |
---|
| 140 | $thisPacket['replaygain_title_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); |
---|
| 141 | $packet_offset += 2; |
---|
| 142 | $thisPacket['replaygain_title_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); |
---|
| 143 | $packet_offset += 2; |
---|
| 144 | $thisPacket['replaygain_album_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); |
---|
| 145 | $packet_offset += 2; |
---|
| 146 | $thisPacket['replaygain_album_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); |
---|
| 147 | $packet_offset += 2; |
---|
[3318] | 148 | |
---|
[3544] | 149 | if ($thisPacket['replaygain_title_gain']) { $ThisFileInfo['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; } |
---|
| 150 | if ($thisPacket['replaygain_title_peak']) { $ThisFileInfo['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; } |
---|
| 151 | if ($thisPacket['replaygain_album_gain']) { $ThisFileInfo['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; } |
---|
| 152 | if ($thisPacket['replaygain_album_peak']) { $ThisFileInfo['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; } |
---|
| 153 | break; |
---|
[3318] | 154 | |
---|
[3544] | 155 | case 'EI': // Encoder Info |
---|
| 156 | $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; |
---|
| 157 | if ($moreBytesToRead > 0) { |
---|
| 158 | $MPCheaderData .= fread($fd, $moreBytesToRead); |
---|
| 159 | } |
---|
| 160 | $profile_pns = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); |
---|
| 161 | $packet_offset += 1; |
---|
| 162 | $quality_int = (($profile_pns & 0xF0) >> 4); |
---|
| 163 | $quality_dec = (($profile_pns & 0x0E) >> 3); |
---|
| 164 | $thisPacket['quality'] = (float) $quality_int + ($quality_dec / 8); |
---|
| 165 | $thisPacket['pns_tool'] = (bool) (($profile_pns & 0x01) >> 0); |
---|
| 166 | $thisPacket['version_major'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); |
---|
| 167 | $packet_offset += 1; |
---|
| 168 | $thisPacket['version_minor'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); |
---|
| 169 | $packet_offset += 1; |
---|
| 170 | $thisPacket['version_build'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); |
---|
| 171 | $packet_offset += 1; |
---|
| 172 | $thisPacket['version'] = $thisPacket['version_major'].'.'.$thisPacket['version_minor'].'.'.$thisPacket['version_build']; |
---|
[3318] | 173 | |
---|
[3544] | 174 | $ThisFileInfo['audio']['encoder'] = 'MPC v'.$thisPacket['version'].' ('.(($thisPacket['version_minor'] % 2) ? 'unstable' : 'stable').')'; |
---|
| 175 | $thisfile_mpc_header['encoder_version'] = $ThisFileInfo['audio']['encoder']; |
---|
| 176 | //$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] / 1.5875); // values can range from 0.000 to 15.875, mapped to qualities of 0.0 to 10.0 |
---|
| 177 | $thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] - 5); // values can range from 0.000 to 15.875, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0 |
---|
| 178 | break; |
---|
[3318] | 179 | |
---|
[3544] | 180 | case 'SO': // Seek Table Offset |
---|
| 181 | $packetLength = 0; |
---|
| 182 | $thisPacket['seek_table_offset'] = $thisPacket['offset'] + $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); |
---|
| 183 | $packet_offset += $packetLength; |
---|
| 184 | break; |
---|
[3318] | 185 | |
---|
[3544] | 186 | case 'ST': // Seek Table |
---|
| 187 | case 'SE': // Stream End |
---|
| 188 | case 'AP': // Audio Data |
---|
| 189 | // nothing useful here, just skip this packet |
---|
| 190 | $thisPacket = array(); |
---|
| 191 | break; |
---|
[3318] | 192 | |
---|
[3544] | 193 | default: |
---|
| 194 | $ThisFileInfo['error'][] = 'Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']; |
---|
| 195 | return false; |
---|
| 196 | break; |
---|
| 197 | } |
---|
| 198 | if (!empty($thisPacket)) { |
---|
| 199 | $ThisFileInfo['mpc']['packets'][] = $thisPacket; |
---|
| 200 | } |
---|
| 201 | fseek($fd, $offset); |
---|
| 202 | } |
---|
| 203 | $thisfile_mpc_header['size'] = $offset; |
---|
| 204 | return true; |
---|
| 205 | } |
---|
| 206 | |
---|
| 207 | function ParseMPCsv7(&$fd, &$ThisFileInfo) { |
---|
| 208 | // this is SV7 |
---|
| 209 | // http://www.uni-jena.de/~pfk/mpp/sv8/header.html |
---|
| 210 | $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; |
---|
| 211 | $offset = 0; |
---|
| 212 | |
---|
| 213 | $thisfile_mpc_header['size'] = 28; |
---|
| 214 | $MPCheaderData = $ThisFileInfo['mpc']['header']['preamble']; |
---|
| 215 | $MPCheaderData .= fread($fd, $thisfile_mpc_header['size'] - strlen($ThisFileInfo['mpc']['header']['preamble'])); |
---|
| 216 | $offset = strlen('MP+'); |
---|
| 217 | |
---|
| 218 | $StreamVersionByte = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1)); |
---|
| 219 | $offset += 1; |
---|
| 220 | $thisfile_mpc_header['stream_version_major'] = ($StreamVersionByte & 0x0F) >> 0; |
---|
| 221 | $thisfile_mpc_header['stream_version_minor'] = ($StreamVersionByte & 0xF0) >> 4; // should always be 0, subversions no longer exist in SV8 |
---|
| 222 | $thisfile_mpc_header['frame_count'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); |
---|
| 223 | $offset += 4; |
---|
| 224 | |
---|
| 225 | if ($thisfile_mpc_header['stream_version_major'] != 7) { |
---|
| 226 | $ThisFileInfo['error'][] = 'Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')'; |
---|
| 227 | return false; |
---|
| 228 | } |
---|
| 229 | |
---|
| 230 | $FlagsDWORD1 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); |
---|
| 231 | $offset += 4; |
---|
| 232 | $thisfile_mpc_header['intensity_stereo'] = (bool) (($FlagsDWORD1 & 0x80000000) >> 31); |
---|
| 233 | $thisfile_mpc_header['mid_side_stereo'] = (bool) (($FlagsDWORD1 & 0x40000000) >> 30); |
---|
| 234 | $thisfile_mpc_header['max_subband'] = ($FlagsDWORD1 & 0x3F000000) >> 24; |
---|
| 235 | $thisfile_mpc_header['raw']['profile'] = ($FlagsDWORD1 & 0x00F00000) >> 20; |
---|
| 236 | $thisfile_mpc_header['begin_loud'] = (bool) (($FlagsDWORD1 & 0x00080000) >> 19); |
---|
| 237 | $thisfile_mpc_header['end_loud'] = (bool) (($FlagsDWORD1 & 0x00040000) >> 18); |
---|
| 238 | $thisfile_mpc_header['raw']['sample_rate'] = ($FlagsDWORD1 & 0x00030000) >> 16; |
---|
| 239 | $thisfile_mpc_header['max_level'] = ($FlagsDWORD1 & 0x0000FFFF); |
---|
| 240 | |
---|
| 241 | $thisfile_mpc_header['raw']['title_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2)); |
---|
| 242 | $offset += 2; |
---|
| 243 | $thisfile_mpc_header['raw']['title_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true); |
---|
| 244 | $offset += 2; |
---|
| 245 | |
---|
| 246 | $thisfile_mpc_header['raw']['album_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2)); |
---|
| 247 | $offset += 2; |
---|
| 248 | $thisfile_mpc_header['raw']['album_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true); |
---|
| 249 | $offset += 2; |
---|
| 250 | |
---|
| 251 | $FlagsDWORD2 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); |
---|
| 252 | $offset += 4; |
---|
| 253 | $thisfile_mpc_header['true_gapless'] = (bool) (($FlagsDWORD2 & 0x80000000) >> 31); |
---|
| 254 | $thisfile_mpc_header['last_frame_length'] = ($FlagsDWORD2 & 0x7FF00000) >> 20; |
---|
| 255 | |
---|
| 256 | |
---|
| 257 | $thisfile_mpc_header['raw']['not_sure_what'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 3)); |
---|
| 258 | $offset += 3; |
---|
| 259 | $thisfile_mpc_header['raw']['encoder_version'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1)); |
---|
| 260 | $offset += 1; |
---|
| 261 | |
---|
| 262 | $thisfile_mpc_header['profile'] = $this->MPCprofileNameLookup($thisfile_mpc_header['raw']['profile']); |
---|
| 263 | $thisfile_mpc_header['sample_rate'] = $this->MPCfrequencyLookup($thisfile_mpc_header['raw']['sample_rate']); |
---|
| 264 | if ($thisfile_mpc_header['sample_rate'] == 0) { |
---|
| 265 | $ThisFileInfo['error'][] = 'Corrupt MPC file: frequency == zero'; |
---|
| 266 | return false; |
---|
| 267 | } |
---|
| 268 | $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; |
---|
| 269 | $thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $ThisFileInfo['audio']['channels']; |
---|
| 270 | |
---|
| 271 | $ThisFileInfo['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $ThisFileInfo['audio']['channels']) / $ThisFileInfo['audio']['sample_rate']; |
---|
| 272 | if ($ThisFileInfo['playtime_seconds'] == 0) { |
---|
| 273 | $ThisFileInfo['error'][] = 'Corrupt MPC file: playtime_seconds == zero'; |
---|
| 274 | return false; |
---|
| 275 | } |
---|
| 276 | |
---|
| 277 | // add size of file header to avdataoffset - calc bitrate correctly + MD5 data |
---|
| 278 | $ThisFileInfo['avdataoffset'] += $thisfile_mpc_header['size']; |
---|
| 279 | |
---|
| 280 | $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; |
---|
| 281 | |
---|
| 282 | $thisfile_mpc_header['title_peak'] = $thisfile_mpc_header['raw']['title_peak']; |
---|
| 283 | $thisfile_mpc_header['title_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['title_peak']); |
---|
| 284 | if ($thisfile_mpc_header['raw']['title_gain'] < 0) { |
---|
| 285 | $thisfile_mpc_header['title_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['title_gain']) / -100; |
---|
| 286 | } else { |
---|
| 287 | $thisfile_mpc_header['title_gain_db'] = (float) $thisfile_mpc_header['raw']['title_gain'] / 100; |
---|
| 288 | } |
---|
| 289 | |
---|
| 290 | $thisfile_mpc_header['album_peak'] = $thisfile_mpc_header['raw']['album_peak']; |
---|
| 291 | $thisfile_mpc_header['album_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['album_peak']); |
---|
| 292 | if ($thisfile_mpc_header['raw']['album_gain'] < 0) { |
---|
| 293 | $thisfile_mpc_header['album_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['album_gain']) / -100; |
---|
| 294 | } else { |
---|
| 295 | $thisfile_mpc_header['album_gain_db'] = (float) $thisfile_mpc_header['raw']['album_gain'] / 100;; |
---|
| 296 | } |
---|
| 297 | $thisfile_mpc_header['encoder_version'] = $this->MPCencoderVersionLookup($thisfile_mpc_header['raw']['encoder_version']); |
---|
| 298 | |
---|
| 299 | $ThisFileInfo['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db']; |
---|
| 300 | $ThisFileInfo['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db']; |
---|
| 301 | |
---|
| 302 | if ($thisfile_mpc_header['title_peak'] > 0) { |
---|
| 303 | $ThisFileInfo['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak']; |
---|
| 304 | } elseif (round($thisfile_mpc_header['max_level'] * 1.18) > 0) { |
---|
| 305 | $ThisFileInfo['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c |
---|
| 306 | } |
---|
| 307 | if ($thisfile_mpc_header['album_peak'] > 0) { |
---|
| 308 | $ThisFileInfo['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak']; |
---|
| 309 | } |
---|
| 310 | |
---|
| 311 | //$ThisFileInfo['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'].'.'.$thisfile_mpc_header['stream_version_minor'].', '.$thisfile_mpc_header['encoder_version']; |
---|
| 312 | $ThisFileInfo['audio']['encoder'] = $thisfile_mpc_header['encoder_version']; |
---|
| 313 | $ThisFileInfo['audio']['encoder_options'] = $thisfile_mpc_header['profile']; |
---|
| 314 | $thisfile_mpc_header['quality'] = (float) ($thisfile_mpc_header['raw']['profile'] - 5); // values can range from 0 to 15, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0 |
---|
| 315 | |
---|
| 316 | return true; |
---|
| 317 | } |
---|
| 318 | |
---|
| 319 | function ParseMPCsv6(&$fd, &$ThisFileInfo) { |
---|
| 320 | // this is SV4 - SV6 |
---|
| 321 | $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; |
---|
| 322 | $offset = 0; |
---|
| 323 | |
---|
| 324 | $thisfile_mpc_header['size'] = 8; |
---|
| 325 | fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); |
---|
| 326 | $MPCheaderData = fread($fd, $thisfile_mpc_header['size']); |
---|
| 327 | |
---|
| 328 | // add size of file header to avdataoffset - calc bitrate correctly + MD5 data |
---|
| 329 | $ThisFileInfo['avdataoffset'] += $thisfile_mpc_header['size']; |
---|
| 330 | |
---|
| 331 | // Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :) |
---|
| 332 | $HeaderDWORD[0] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 0, 4)); |
---|
| 333 | $HeaderDWORD[1] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 4, 4)); |
---|
| 334 | |
---|
| 335 | |
---|
| 336 | // DDDD DDDD CCCC CCCC BBBB BBBB AAAA AAAA |
---|
| 337 | // aaaa aaaa abcd dddd dddd deee eeff ffff |
---|
| 338 | // |
---|
| 339 | // a = bitrate = anything |
---|
| 340 | // b = IS = anything |
---|
| 341 | // c = MS = anything |
---|
| 342 | // d = streamversion = 0000000004 or 0000000005 or 0000000006 |
---|
| 343 | // e = maxband = anything |
---|
| 344 | // f = blocksize = 000001 for SV5+, anything(?) for SV4 |
---|
| 345 | |
---|
| 346 | $thisfile_mpc_header['target_bitrate'] = (($HeaderDWORD[0] & 0xFF800000) >> 23); |
---|
| 347 | $thisfile_mpc_header['intensity_stereo'] = (bool) (($HeaderDWORD[0] & 0x00400000) >> 22); |
---|
| 348 | $thisfile_mpc_header['mid_side_stereo'] = (bool) (($HeaderDWORD[0] & 0x00200000) >> 21); |
---|
| 349 | $thisfile_mpc_header['stream_version_major'] = ($HeaderDWORD[0] & 0x001FF800) >> 11; |
---|
| 350 | $thisfile_mpc_header['stream_version_minor'] = 0; // no sub-version numbers before SV7 |
---|
| 351 | $thisfile_mpc_header['max_band'] = ($HeaderDWORD[0] & 0x000007C0) >> 6; // related to lowpass frequency, not sure how it translates exactly |
---|
| 352 | $thisfile_mpc_header['block_size'] = ($HeaderDWORD[0] & 0x0000003F); |
---|
| 353 | |
---|
| 354 | switch ($thisfile_mpc_header['stream_version_major']) { |
---|
| 355 | case 4: |
---|
| 356 | $thisfile_mpc_header['frame_count'] = ($HeaderDWORD[1] >> 16); |
---|
| 357 | break; |
---|
| 358 | |
---|
| 359 | case 5: |
---|
| 360 | case 6: |
---|
| 361 | $thisfile_mpc_header['frame_count'] = $HeaderDWORD[1]; |
---|
| 362 | break; |
---|
| 363 | |
---|
| 364 | default: |
---|
| 365 | $ThisFileInfo['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_version_major'].' instead'; |
---|
| 366 | unset($ThisFileInfo['mpc']); |
---|
| 367 | return false; |
---|
| 368 | break; |
---|
| 369 | } |
---|
| 370 | |
---|
| 371 | if (($thisfile_mpc_header['stream_version_major'] > 4) && ($thisfile_mpc_header['block_size'] != 1)) { |
---|
| 372 | $ThisFileInfo['warning'][] = 'Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size']; |
---|
| 373 | } |
---|
| 374 | |
---|
| 375 | $thisfile_mpc_header['sample_rate'] = 44100; // AB: used by all files up to SV7 |
---|
| 376 | $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; |
---|
| 377 | $thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $ThisFileInfo['audio']['channels']; |
---|
| 378 | |
---|
| 379 | if ($thisfile_mpc_header['target_bitrate'] == 0) { |
---|
| 380 | $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; |
---|
| 381 | } else { |
---|
| 382 | $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; |
---|
| 383 | } |
---|
| 384 | |
---|
| 385 | $ThisFileInfo['mpc']['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152; |
---|
| 386 | $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpc']['bitrate']; |
---|
| 387 | $ThisFileInfo['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major']; |
---|
| 388 | |
---|
| 389 | return true; |
---|
| 390 | } |
---|
| 391 | |
---|
| 392 | |
---|
| 393 | function MPCprofileNameLookup($profileid) { |
---|
| 394 | static $MPCprofileNameLookup = array( |
---|
| 395 | 0 => 'no profile', |
---|
| 396 | 1 => 'Experimental', |
---|
| 397 | 2 => 'unused', |
---|
| 398 | 3 => 'unused', |
---|
| 399 | 4 => 'unused', |
---|
| 400 | 5 => 'below Telephone (q = 0.0)', |
---|
| 401 | 6 => 'below Telephone (q = 1.0)', |
---|
| 402 | 7 => 'Telephone (q = 2.0)', |
---|
| 403 | 8 => 'Thumb (q = 3.0)', |
---|
| 404 | 9 => 'Radio (q = 4.0)', |
---|
| 405 | 10 => 'Standard (q = 5.0)', |
---|
| 406 | 11 => 'Extreme (q = 6.0)', |
---|
| 407 | 12 => 'Insane (q = 7.0)', |
---|
| 408 | 13 => 'BrainDead (q = 8.0)', |
---|
| 409 | 14 => 'above BrainDead (q = 9.0)', |
---|
| 410 | 15 => 'above BrainDead (q = 10.0)' |
---|
| 411 | ); |
---|
| 412 | return (isset($MPCprofileNameLookup[$profileid]) ? $MPCprofileNameLookup[$profileid] : 'invalid'); |
---|
| 413 | } |
---|
| 414 | |
---|
| 415 | function MPCfrequencyLookup($frequencyid) { |
---|
| 416 | static $MPCfrequencyLookup = array( |
---|
| 417 | 0 => 44100, |
---|
| 418 | 1 => 48000, |
---|
| 419 | 2 => 37800, |
---|
| 420 | 3 => 32000 |
---|
| 421 | ); |
---|
| 422 | return (isset($MPCfrequencyLookup[$frequencyid]) ? $MPCfrequencyLookup[$frequencyid] : 'invalid'); |
---|
| 423 | } |
---|
| 424 | |
---|
| 425 | function MPCpeakDBLookup($intvalue) { |
---|
| 426 | if ($intvalue > 0) { |
---|
| 427 | return ((log10($intvalue) / log10(2)) - 15) * 6; |
---|
| 428 | } |
---|
| 429 | return false; |
---|
| 430 | } |
---|
| 431 | |
---|
| 432 | function MPCencoderVersionLookup($encoderversion) { |
---|
| 433 | //Encoder version * 100 (106 = 1.06) |
---|
| 434 | //EncoderVersion % 10 == 0 Release (1.0) |
---|
| 435 | //EncoderVersion % 2 == 0 Beta (1.06) |
---|
| 436 | //EncoderVersion % 2 == 1 Alpha (1.05a...z) |
---|
| 437 | |
---|
| 438 | if ($encoderversion == 0) { |
---|
| 439 | // very old version, not known exactly which |
---|
| 440 | return 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05'; |
---|
| 441 | } |
---|
| 442 | |
---|
| 443 | if (($encoderversion % 10) == 0) { |
---|
| 444 | |
---|
| 445 | // release version |
---|
| 446 | return number_format($encoderversion / 100, 2); |
---|
| 447 | |
---|
| 448 | } elseif (($encoderversion % 2) == 0) { |
---|
| 449 | |
---|
| 450 | // beta version |
---|
| 451 | return number_format($encoderversion / 100, 2).' beta'; |
---|
| 452 | |
---|
| 453 | } |
---|
| 454 | |
---|
| 455 | // alpha version |
---|
| 456 | return number_format($encoderversion / 100, 2).' alpha'; |
---|
| 457 | } |
---|
| 458 | |
---|
| 459 | function SV8variableLengthInteger($data, &$packetLength, $maxHandledPacketLength=9) { |
---|
| 460 | $packet_size = 0; |
---|
| 461 | for ($packetLength = 1; $packetLength <= $maxHandledPacketLength; $packetLength++) { |
---|
| 462 | // variable-length size field: |
---|
| 463 | // bits, big-endian |
---|
| 464 | // 0xxx xxxx - value 0 to 2^7-1 |
---|
| 465 | // 1xxx xxxx 0xxx xxxx - value 0 to 2^14-1 |
---|
| 466 | // 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^21-1 |
---|
| 467 | // 1xxx xxxx 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^28-1 |
---|
| 468 | // ... |
---|
| 469 | $thisbyte = ord(substr($data, ($packetLength - 1), 1)); |
---|
| 470 | // look through bytes until find a byte with MSB==0 |
---|
| 471 | $packet_size = ($packet_size << 7); |
---|
| 472 | $packet_size = ($packet_size | ($thisbyte & 0x7F)); |
---|
| 473 | if (($thisbyte & 0x80) === 0) { |
---|
| 474 | break; |
---|
| 475 | } |
---|
| 476 | if ($packetLength >= $maxHandledPacketLength) { |
---|
| 477 | return false; |
---|
| 478 | } |
---|
| 479 | } |
---|
| 480 | return $packet_size; |
---|
| 481 | } |
---|
| 482 | |
---|
| 483 | function MPCsv8PacketName($packetKey) { |
---|
| 484 | static $MPCsv8PacketName = array(); |
---|
| 485 | if (empty($MPCsv8PacketName)) { |
---|
| 486 | $MPCsv8PacketName = array( |
---|
| 487 | 'AP' => 'Audio Packet', |
---|
| 488 | 'CT' => 'Chapter Tag', |
---|
| 489 | 'EI' => 'Encoder Info', |
---|
| 490 | 'RG' => 'Replay Gain', |
---|
| 491 | 'SE' => 'Stream End', |
---|
| 492 | 'SH' => 'Stream Header', |
---|
| 493 | 'SO' => 'Seek Table Offset', |
---|
| 494 | 'ST' => 'Seek Table', |
---|
| 495 | ); |
---|
| 496 | } |
---|
| 497 | return (isset($MPCsv8PacketName[$packetKey]) ? $MPCsv8PacketName[$packetKey] : $packetKey); |
---|
| 498 | } |
---|
[3318] | 499 | } |
---|
| 500 | |
---|
| 501 | |
---|
| 502 | ?> |
---|