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 #include <errno.h>
21 #include <glib/gstdio.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include "info_mac.h"
26 #include "is_tag.h"
27 
28 #define MAC_FORMAT_FLAG_8_BIT                 1    // 8-bit wave
29 #define MAC_FORMAT_FLAG_CRC                   2    // new CRC32 error detection
30 #define MAC_FORMAT_FLAG_HAS_PEAK_LEVEL        4    // u-long Peak_Level after the header
31 #define MAC_FORMAT_FLAG_24_BIT                8    // 24-bit wave
32 #define MAC_FORMAT_FLAG_HAS_SEEK_ELEMENTS    16    // number of seek elements after the peak level
33 #define MAC_FORMAT_FLAG_CREATE_WAV_HEADER    32    // wave header not stored
34 
35 #define MAC_FORMAT_HEADER_LENGTH 16
36 
37 struct macHeader {
38     char             id[4];               // should equal 'MAC '
39     unsigned short   ver;                 // version number * 1000 (3.81 = 3810)
40     unsigned short   compLevel;           // the compression level
41     unsigned short   formatFlags;         // any format flags (for future use)
42     unsigned short   channels;            // the number of channels (1 or 2)
43     unsigned long    sampleRate;          // the sample rate (typically 44100)
44     unsigned long    headerBytesWAV;      // the bytes after the MAC header that compose the WAV header
45     unsigned long    terminatingBytesWAV; // the bytes after that raw data (for extended info)
46     unsigned long    totalFrames;         // the number of frames in the file
47     unsigned long    finalFrameBlocks;    // the number of samples in the final frame
48     unsigned long    peakLevel;
49     unsigned short   seekElements;
50 };
51 
52 
53 // local prototypes
54 static int
55 monkey_samples_per_frame(unsigned int versionid, unsigned int compressionlevel);
56 static const char *
57 monkey_stringify(unsigned int profile);
58 
59 static const char *
monkey_stringify(unsigned int profile)60 monkey_stringify(unsigned int profile)
61 {
62         static const char na[] = "unknown";
63         static const char *Names[] = {
64                 na, "Fast", "Normal", "High", "Extra-High", "Insane"
65         };
66         unsigned int profile2 = profile/1000;
67 
68         return (profile2 >= sizeof (Names) / sizeof (*Names)) ? na : Names[(profile2)];
69 }
70 
71 
72 static int
monkey_samples_per_frame(unsigned int versionid,unsigned int compressionlevel)73 monkey_samples_per_frame(unsigned int versionid, unsigned int compressionlevel)
74 {
75     if (versionid >= 3950) {
76         return 294912; // 73728 * 4
77     } else if (versionid >= 3900) {
78         return 73728;
79     } else if ((versionid >= 3800) && (compressionlevel == COMPRESSION_LEVEL_EXTRA_HIGH)) {
80         return 73728;
81     } else {
82         return 9216;
83     }
84 }
85 
86 /*
87  * info_mac_read:
88  * @file: file from which to read a header
89  * @stream_info: stream information to fill
90  * @error: a #GError, or NULL
91  *
92  * Read the header information from a Monkey's Audio file.
93  *
94  * Returns: %TRUE on success, or %FALSE and with @error set on failure
95 */
96 gboolean
info_mac_read(GFile * file,StreamInfoMac * stream_info,GError ** error)97 info_mac_read (GFile *file,
98                StreamInfoMac *stream_info,
99                GError **error)
100 {
101     GFileInfo *info;
102     GFileInputStream *istream;
103     guint8 header_buffer[MAC_FORMAT_HEADER_LENGTH];
104     gsize bytes_read;
105     gsize size_id3;
106     struct macHeader *header;
107 
108     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
109 
110     info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
111                               G_FILE_QUERY_INFO_NONE, NULL, error);
112 
113     if (!info)
114     {
115         return FALSE;
116     }
117 
118     stream_info->FileSize = g_file_info_get_size (info);
119     g_object_unref (info);
120 
121     istream = g_file_read (file, NULL, error);
122 
123     if (!istream)
124     {
125         return FALSE;
126     }
127 
128     /* FIXME: is_id3v2() should accept an istream or a GFile. */
129     {
130         gchar *path;
131         FILE *fp;
132 
133         path = g_file_get_path (file);
134         fp = g_fopen (path, "rb");
135 
136         if (!fp)
137         {
138             /* TODO: Add specific error domain and message. */
139             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s",
140                          g_strerror (EINVAL));
141             g_free (path);
142             return FALSE;
143         }
144 
145         size_id3 = is_id3v2 (fp);
146         fclose (fp);
147         g_free (path);
148     }
149 
150     if (!g_seekable_seek (G_SEEKABLE (istream), size_id3, G_SEEK_SET, NULL,
151                           error))
152     {
153         return FALSE;
154     }
155 
156     if (!g_input_stream_read_all (G_INPUT_STREAM (istream), header_buffer,
157                                   MAC_FORMAT_HEADER_LENGTH, &bytes_read, NULL,
158                                   error))
159     {
160         g_debug ("Only %" G_GSIZE_FORMAT " bytes out of 16 bytes of data were "
161                  "read", bytes_read);
162         return FALSE;
163     }
164 
165     if (memcmp (header_buffer, "MAC", 3) != 0)
166     {
167         /* TODO: Add specific error domain and message. */
168         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s",
169                      g_strerror (EINVAL));
170         return FALSE; // no monkeyAudio file
171     }
172 
173     header = (struct macHeader *) header_buffer;
174 
175     stream_info->Version = stream_info->EncoderVersion = header->ver;
176     stream_info->Channels = header->channels;
177     stream_info->SampleFreq = header->sampleRate;
178     stream_info->Flags = header->formatFlags;
179     stream_info->SamplesPerFrame = monkey_samples_per_frame(header->ver, header->compLevel);
180     stream_info->BitsPerSample = (header->formatFlags & MAC_FORMAT_FLAG_8_BIT)
181                                   ? 8 : ((header->formatFlags & MAC_FORMAT_FLAG_24_BIT) ? 24 : 16);
182 
183     stream_info->PeakLevel = header->peakLevel;
184 //    Info->PeakRatio       = Info->PakLevel / pow(2, Info->bitsPerSample - 1);
185     stream_info->Frames = header->totalFrames;
186     stream_info->Samples = (stream_info->Frames - 1)
187                            * stream_info->SamplesPerFrame
188                            + header->finalFrameBlocks;
189 
190     stream_info->Duration = stream_info -> SampleFreq > 0 ?
191                             ((float)stream_info->Samples
192                              / stream_info->SampleFreq) * 1000 : 0;
193 
194     stream_info->Compresion = header->compLevel;
195     stream_info->CompresionName = monkey_stringify (stream_info->Compresion);
196 
197     stream_info->UncompresedSize = stream_info->Samples
198                                    * stream_info->Channels
199                                    * (stream_info->BitsPerSample / 8);
200 
201     stream_info->CompresionRatio = (stream_info->UncompresedSize
202                                     + header->headerBytesWAV) > 0
203                                     ? stream_info->FileSize
204                                       / (float) (stream_info->UncompresedSize
205                                                  + header->headerBytesWAV) : 0.;
206 
207     stream_info->Bitrate = stream_info->Duration > 0
208                            ? (((stream_info->Samples * stream_info->Channels
209                                 * stream_info->BitsPerSample)
210                                / (float) stream_info->Duration)
211                               * stream_info->CompresionRatio) * 1000 : 0;
212 
213     stream_info->PeakRatio = stream_info->ByteLength = 0;
214 
215     return TRUE;
216 }
217