1 | <?php |
---|
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 | ///////////////////////////////////////////////////////////////// |
---|
15 | |
---|
16 | |
---|
17 | class getid3_mpc |
---|
18 | { |
---|
19 | |
---|
20 | function getid3_mpc(&$fd, &$ThisFileInfo) { |
---|
21 | $ThisFileInfo['mpc']['header'] = array(); |
---|
22 | $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; |
---|
23 | |
---|
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; |
---|
29 | |
---|
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'])) { |
---|
34 | |
---|
35 | // this is SV8 |
---|
36 | return $this->ParseMPCsv8($fd, $ThisFileInfo); |
---|
37 | |
---|
38 | } elseif (ereg('^MP\+', $ThisFileInfo['mpc']['header']['preamble'])) { |
---|
39 | |
---|
40 | // this is SV7 |
---|
41 | return $this->ParseMPCsv7($fd, $ThisFileInfo); |
---|
42 | |
---|
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)) { |
---|
44 | |
---|
45 | // this is SV4 - SV6, handle seperately |
---|
46 | return $this->ParseMPCsv6($fd, $ThisFileInfo); |
---|
47 | |
---|
48 | } else { |
---|
49 | |
---|
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; |
---|
54 | |
---|
55 | } |
---|
56 | return false; |
---|
57 | } |
---|
58 | |
---|
59 | |
---|
60 | function ParseMPCsv8(&$fd, &$ThisFileInfo) { |
---|
61 | // this is SV8 |
---|
62 | // http://trac.musepack.net/trac/wiki/SV8Specification |
---|
63 | |
---|
64 | $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; |
---|
65 | |
---|
66 | $keyNameSize = 2; |
---|
67 | $maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10" |
---|
68 | |
---|
69 | $offset = ftell($fd); |
---|
70 | while ($offset < $ThisFileInfo['avdataend']) { |
---|
71 | $thisPacket = array(); |
---|
72 | $thisPacket['offset'] = $offset; |
---|
73 | $packet_offset = 0; |
---|
74 | |
---|
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']; |
---|
93 | |
---|
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; |
---|
104 | |
---|
105 | $packetLength = 0; |
---|
106 | $thisPacket['sample_count'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); |
---|
107 | $packet_offset += $packetLength; |
---|
108 | |
---|
109 | $packetLength = 0; |
---|
110 | $thisPacket['beginning_silence'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); |
---|
111 | $packet_offset += $packetLength; |
---|
112 | |
---|
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']); |
---|
121 | |
---|
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']; |
---|
126 | |
---|
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; |
---|
132 | |
---|
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; |
---|
148 | |
---|
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; |
---|
154 | |
---|
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']; |
---|
173 | |
---|
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; |
---|
179 | |
---|
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; |
---|
185 | |
---|
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; |
---|
192 | |
---|
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 | } |
---|
499 | } |
---|
500 | |
---|
501 | |
---|
502 | ?> |
---|