1 /* EasyTAG - tag editor for audio files
2 * Copyright (C) 2014 David King <amigadave@amigadave.com>
3 * Copyright (C) 2002 Artur Polaczynski (Ar't) <artii@o2.pl>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 51
17 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 /*
21 Some portions of code or/and ideas come from
22 winamp plugins, xmms plugins, mppdec decoder
23 thanks:
24 -Frank Klemm <Frank.Klemm@uni-jena.de>
25 -Andree Buschmann <Andree.Buschmann@web.de>
26 -Thomas Juerges <thomas.juerges@astro.ruhr-uni-bochum.de>
27 */
28
29 #include <errno.h>
30 #include <glib/gstdio.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include "info_mpc.h"
35 #include "is_tag.h"
36
37 #define MPC_HEADER_LENGTH 16
38
39 /*
40 *.MPC,*.MP+,*.MPP
41 */
42 /* Profile is 0...15, where 7...13 is used. */
43 static const char *
profile_stringify(unsigned int profile)44 profile_stringify (unsigned int profile)
45 {
46 static const char na[] = "n.a.";
47 static const char *Names[] = {
48 na, "Experimental", na, na,
49 na, na, na, "Telephone",
50 "Thumb", "Radio", "Standard", "Xtreme",
51 "Insane", "BrainDead", "BrainDead+", "BrainDead++"
52 };
53
54 return profile >=
55 sizeof (Names) / sizeof (*Names) ? na : Names[profile];
56 }
57
58 /*
59 * info_mpc_read:
60 * @file: file from which to read a header
61 * @stream_info: stream information to fill
62 * @error: a #Gerror, or %NULL
63 *
64 * Read header from the given MusePack @file.
65 *
66 * Returns: %TRUE on success, %FALSE and with @error set on failure
67 */
68 gboolean
info_mpc_read(GFile * file,StreamInfoMpc * stream_info,GError ** error)69 info_mpc_read (GFile *file,
70 StreamInfoMpc *stream_info,
71 GError **error)
72 {
73 GFileInfo *info;
74 GFileInputStream *istream;
75 guint32 header_buffer[MPC_HEADER_LENGTH];
76 gsize bytes_read;
77 gsize id3_size;
78
79 info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
80 G_FILE_QUERY_INFO_NONE, NULL, error);
81
82 if (!info)
83 {
84 return FALSE;
85 }
86
87 stream_info->FileSize = g_file_info_get_size (info);
88 g_object_unref (info);
89
90 {
91 gchar *path;
92 FILE *fp;
93
94 path = g_file_get_path (file);
95 fp = g_fopen (path, "rb");
96 g_free (path);
97
98 if (!fp)
99 {
100 /* TODO: Add specific error domain and message. */
101 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s",
102 g_strerror (EINVAL));
103 return FALSE;
104 }
105
106 /* Skip id3v2. */
107 /* FIXME: is_id3v2 (and is_id3v1 and is_ape) should accept an istream
108 * or GFile. */
109 id3_size = is_id3v2 (fp);
110
111 fseek (fp, 0, SEEK_END);
112 stream_info->FileSize = ftell (fp);
113
114 /* Stream size. */
115 stream_info->ByteLength = stream_info->FileSize - is_id3v1 (fp)
116 - is_ape (fp) - id3_size;
117
118 fclose (fp);
119 }
120
121 istream = g_file_read (file, NULL, error);
122
123 if (!istream)
124 {
125 return FALSE;
126 }
127
128 if (!g_seekable_seek (G_SEEKABLE (istream), id3_size, G_SEEK_SET, NULL,
129 error))
130 {
131 return FALSE;
132 }
133
134 /* Read 16 guint32. */
135 if (!g_input_stream_read_all (G_INPUT_STREAM (istream), header_buffer,
136 MPC_HEADER_LENGTH * 4, &bytes_read, NULL,
137 error))
138 {
139 g_debug ("Only %" G_GSIZE_FORMAT "bytes out of 16 bytes of data were "
140 "read", bytes_read);
141 return FALSE;
142 }
143
144 /* FIXME: Read 4 bytes, take as a uint32, then byteswap if necessary. (The
145 * official Musepack decoder expects the user(!) to request the
146 * byteswap.) */
147 if (memcmp (header_buffer, "MP+", 3) != 0)
148 {
149 /* TODO: Add specific error domain and message. */
150 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s",
151 g_strerror (EINVAL));
152 return FALSE;
153 }
154
155 stream_info->StreamVersion = header_buffer[0] >> 24;
156
157 if (stream_info->StreamVersion >= 7)
158 {
159 const long samplefreqs[4] = { 44100, 48000, 37800, 32000 };
160
161 // read the file-header (SV7 and above)
162 stream_info->Bitrate = 0;
163 stream_info->Frames = header_buffer[1];
164 stream_info->SampleFreq = samplefreqs[(header_buffer[2] >> 16) & 0x0003];
165 stream_info->MaxBand = (header_buffer[2] >> 24) & 0x003F;
166 stream_info->MS = (header_buffer[2] >> 30) & 0x0001;
167 stream_info->Profile = (header_buffer[2] << 8) >> 28;
168 stream_info->IS = (header_buffer[2] >> 31) & 0x0001;
169 stream_info->BlockSize = 1;
170
171 stream_info->EncoderVersion = (header_buffer[6] >> 24) & 0x00FF;
172 stream_info->Channels = 2;
173 // gain
174 stream_info->EstPeakTitle = header_buffer[2] & 0xFFFF; // read the ReplayGain data
175 stream_info->GainTitle = (header_buffer[3] >> 16) & 0xFFFF;
176 stream_info->PeakTitle = header_buffer[3] & 0xFFFF;
177 stream_info->GainAlbum = (header_buffer[4] >> 16) & 0xFFFF;
178 stream_info->PeakAlbum = header_buffer[4] & 0xFFFF;
179 // gaples
180 stream_info->IsTrueGapless = (header_buffer[5] >> 31) & 0x0001; // true gapless: used?
181 stream_info->LastFrameSamples = (header_buffer[5] >> 20) & 0x07FF; // true gapless: valid samples for last frame
182
183 if (stream_info->EncoderVersion == 0)
184 {
185 sprintf (stream_info->Encoder, "<= 1.05"); // Buschmann 1.7.x, Klemm <= 1.05
186 }
187 else
188 {
189 switch (stream_info->EncoderVersion % 10)
190 {
191 case 0:
192 sprintf (stream_info->Encoder, "%u.%u",
193 stream_info->EncoderVersion / 100,
194 stream_info->EncoderVersion / 10 % 10);
195 break;
196 case 2:
197 case 4:
198 case 6:
199 case 8:
200 sprintf (stream_info->Encoder, "%u.%02u Beta",
201 stream_info->EncoderVersion / 100,
202 stream_info->EncoderVersion % 100);
203 break;
204 default:
205 sprintf (stream_info->Encoder, "%u.%02u Alpha",
206 stream_info->EncoderVersion / 100,
207 stream_info->EncoderVersion % 100);
208 break;
209 }
210 }
211 // estimation, exact value needs too much time
212 stream_info->Bitrate = (long) (stream_info->ByteLength) * 8. * stream_info->SampleFreq / (1152 * stream_info->Frames - 576);
213
214 }
215 else
216 {
217 // read the file-header (SV6 and below)
218 stream_info->Bitrate = ((header_buffer[0] >> 23) & 0x01FF) * 1000; // read the file-header (SV6 and below)
219 stream_info->MS = (header_buffer[0] >> 21) & 0x0001;
220 stream_info->IS = (header_buffer[0] >> 22) & 0x0001;
221 stream_info->StreamVersion = (header_buffer[0] >> 11) & 0x03FF;
222 stream_info->MaxBand = (header_buffer[0] >> 6) & 0x001F;
223 stream_info->BlockSize = (header_buffer[0]) & 0x003F;
224
225 stream_info->Profile = 0;
226 //gain
227 stream_info->GainTitle = 0; // not supported
228 stream_info->PeakTitle = 0;
229 stream_info->GainAlbum = 0;
230 stream_info->PeakAlbum = 0;
231 //gaples
232 stream_info->LastFrameSamples = 0;
233 stream_info->IsTrueGapless = 0;
234
235 if (stream_info->StreamVersion >= 5)
236 {
237 stream_info->Frames = header_buffer[1]; // 32 bit
238 }
239 else
240 {
241 stream_info->Frames = (header_buffer[1] >> 16); // 16 bit
242 }
243
244 stream_info->EncoderVersion = 0;
245 stream_info->Encoder[0] = '\0';
246 #if 0
247 if (Info->StreamVersion == 7)
248 return ERROR_CODE_SV7BETA; // are there any unsupported parameters used?
249 if (Info->Bitrate != 0)
250 return ERROR_CODE_CBR;
251 if (Info->IS != 0)
252 return ERROR_CODE_IS;
253 if (Info->BlockSize != 1)
254 return ERROR_CODE_BLOCKSIZE;
255 #endif
256 if (stream_info->StreamVersion < 6) // Bugfix: last frame was invalid for up to SV5
257 {
258 stream_info->Frames -= 1;
259 }
260
261 stream_info->SampleFreq = 44100; // AB: used by all files up to SV7
262 stream_info->Channels = 2;
263 }
264
265 stream_info->ProfileName = profile_stringify (stream_info->Profile);
266
267 stream_info->Duration = (int) (stream_info->Frames * 1152
268 / (stream_info->SampleFreq / 1000.0));
269
270 return TRUE;
271 }
272