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