1 /* (PD) 2001 The Bitzi Corporation
2  * Please see file COPYING or http://bitzi.com/publicdomain
3  * for more info.
4  *
5  * This code is based on vorbis metadata plugin from FreeAmp. EMusic.com
6  * has released this code into the Public Domain.
7  * (Thanks goes to Brett Thomas, VP Engineering Emusic.com)
8  *
9  * $Id: vorbis.c,v 1.10 2004/02/03 01:11:07 mayhemchaos Exp $
10  */
11 #include <stdio.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <assert.h>
15 #include <errno.h>
16 #include <assert.h>
17 
18 #include "vorbis/vorbisfile.h"
19 #include "plugin.h"
20 #include "vorbis.h"
21 
22 /*-------------------------------------------------------------------------*/
23 
24 #ifndef _WIN32
25 #define init_plugin vorbis_init_plugin
26 #endif
27 
28 PluginMethods           *init_plugin(void);
29 static void              vorbis_shutdown_plugin(void);
30 static SupportedFormat  *vorbis_get_supported_formats(void);
31 static const char       *vorbis_get_name(void);
32 static const char       *vorbis_get_version(void);
33 static Attribute        *vorbis_file_analyze(const char *fileName);
34 static void              vorbis_free_attributes(Attribute *attrList);
35 static const char       *vorbis_get_error(void);
36 
37 /* Static internal functions */
38 static char *      convertToISO(const char *utf8);
39 static VorbisInfo *read_vorbis_tag(const char *fileName);
40 static void        delete_vorbis_tag(VorbisInfo *info);
41 
42 /*-------------------------------------------------------------------------*/
43 
44 #define CHUNK          4096
45 #define PLUGIN_VERSION "1.0.0"
46 #define PLUGIN_NAME    "Ogg/Vorbis Metadata"
47 #define MAX_ATTRS      16
48 
49 /*-------------------------------------------------------------------------*/
50 
51 static SupportedFormat formats[] =
52 {
53      { ".ogg", "Ogg/Vorbis" },
54      { NULL,   NULL }
55 };
56 
57 /*-------------------------------------------------------------------------*/
58 
59 static PluginMethods methods =
60 {
61     vorbis_shutdown_plugin,
62     vorbis_get_version,
63     vorbis_get_name,
64     vorbis_get_supported_formats,
65     vorbis_file_analyze,
66     NULL, /* mem_ functions are not supported in this plugin */
67     NULL,
68     NULL,
69     vorbis_free_attributes,
70     vorbis_get_error
71 };
72 
73 static char *errorString = NULL;
74 
75 /*-------------------------------------------------------------------------*/
76 
init_plugin(void)77 PluginMethods *init_plugin(void)
78 {
79     return &methods;
80 }
81 
vorbis_shutdown_plugin(void)82 static void vorbis_shutdown_plugin(void)
83 {
84     if (errorString)
85        free(errorString);
86 }
87 
vorbis_get_version(void)88 static const char *vorbis_get_version(void)
89 {
90     return PLUGIN_VERSION;
91 }
92 
vorbis_get_name(void)93 static const char *vorbis_get_name(void)
94 {
95     return PLUGIN_NAME;
96 }
97 
vorbis_get_supported_formats(void)98 static SupportedFormat *vorbis_get_supported_formats(void)
99 {
100     return formats;
101 }
102 
vorbis_file_analyze(const char * fileName)103 static Attribute *vorbis_file_analyze(const char *fileName)
104 {
105     VorbisInfo *info;
106     Attribute  *attrList;
107     int         i;
108     char        temp[1024];
109 
110     info = read_vorbis_tag(fileName);
111     if (info == NULL)
112        return NULL;
113 
114     attrList = malloc(sizeof(Attribute) * MAX_ATTRS);
115     memset(attrList, 0, sizeof(Attribute) * MAX_ATTRS);
116 
117     i = 0;
118     sprintf(temp, "%d", info->bitrate);
119     attrList[i].key = strdup("tag.vorbis.bitrate");
120     attrList[i++].value = strdup(temp);
121 
122     sprintf(temp, "%d", info->duration);
123     attrList[i].key = strdup("tag.vorbis.duration");
124     attrList[i++].value = strdup(temp);
125 
126     sprintf(temp, "%d", info->samplerate);
127     attrList[i].key = strdup("tag.vorbis.samplerate");
128     attrList[i++].value = strdup(temp);
129 
130     sprintf(temp, "%d", info->channels);
131     attrList[i].key = strdup("tag.vorbis.channels");
132     attrList[i++].value = strdup(temp);
133 
134     if (info->title)
135     {
136         attrList[i].key = strdup("tag.audiotrack.title");
137         attrList[i++].value = strdup(info->title);
138     }
139 
140     if (info->artist)
141     {
142         attrList[i].key = strdup("tag.audiotrack.artist");
143         attrList[i++].value = strdup(info->artist);
144     }
145 
146     if (info->album)
147     {
148         attrList[i].key = strdup("tag.audiotrack.album");
149         attrList[i++].value = strdup(info->album);
150     }
151 
152     if (info->tracknumber)
153     {
154         attrList[i].key = strdup("tag.audiotrack.tracknumber");
155         attrList[i++].value = strdup(info->tracknumber);
156     }
157 
158     if (info->desc)
159     {
160         attrList[i].key = strdup("tag.objective.description");
161         attrList[i++].value = strdup(info->desc);
162     }
163 
164     if (info->genre)
165     {
166         attrList[i].key = strdup("tag.id3genre.genre");
167         attrList[i++].value = strdup(info->genre);
168     }
169 
170     delete_vorbis_tag(info);
171 
172     return attrList;
173 }
174 
vorbis_free_attributes(Attribute * attrList)175 static void vorbis_free_attributes(Attribute *attrList)
176 {
177     int i;
178 
179     for(i = 0; i < MAX_ATTRS; i++)
180     {
181        if (attrList[i].key)
182           free(attrList[i].key);
183        if (attrList[i].value)
184           free(attrList[i].value);
185     }
186 
187     free(attrList);
188 }
189 
vorbis_get_error(void)190 static const char *vorbis_get_error(void)
191 {
192     return errorString;
193 }
194 
delete_vorbis_tag(VorbisInfo * info)195 static void delete_vorbis_tag(VorbisInfo *info)
196 {
197     if (!info)
198        return;
199 
200     if (info->artist)
201        free(info->artist);
202     if (info->album)
203        free(info->album);
204     if (info->title)
205        free(info->title);
206     if (info->genre)
207        free(info->genre);
208     if (info->tracknumber)
209        free(info->tracknumber);
210     if (info->desc)
211        free(info->desc);
212 
213     free(info);
214 }
215 
read_vorbis_tag(const char * fileName)216 static VorbisInfo *read_vorbis_tag(const char *fileName)
217 {
218     char           *temp;
219     FILE           *file;
220     OggVorbis_File  vf;
221     vorbis_comment *comment;
222     vorbis_info    *vi;
223     VorbisInfo     *info;
224     int             ret;
225     ov_callbacks    callbacks;
226 
227     file = fopen(fileName, "rb");
228     if (file == NULL)
229        return NULL;
230 
231     callbacks.read_func = fread;
232     callbacks.seek_func = fseek;
233     callbacks.close_func = fclose;
234     callbacks.tell_func = ftell;
235 
236     memset(&vf, 0, sizeof(vf));
237     ret = ov_open_callbacks(file, &vf, NULL, 0, callbacks);
238     if (ret < 0)
239     {
240        switch(ret)
241        {
242           case OV_EREAD:
243              errorString = strdup("A read from media returned an error.");
244              break;
245           case OV_ENOTVORBIS:
246              errorString = strdup("Bitstream is not Vorbis data.");
247              break;
248           case OV_EVERSION:
249              errorString = strdup("Vorbis version mismatch.");
250              break;
251           case OV_EBADHEADER:
252              errorString = strdup("Invalid Vorbis bitstream header.");
253              break;
254           case OV_EFAULT:
255              errorString = strdup("Internal logic fault; indicates a bug "
256                                   "or heap/stack corruption. ");
257              break;
258           default:
259              errorString = strdup("Unknown error.");
260              break;
261        }
262        fclose(file);
263        return NULL;
264     }
265 
266     info = malloc(sizeof(VorbisInfo));
267     memset(info, 0, sizeof(VorbisInfo));
268 
269     info->bitrate = ov_bitrate(&vf, -1) / 1000;
270     info->duration = (int)(ov_time_total(&vf, 0) * 1000);
271     vi = ov_info(&vf, -1);
272     info->channels = vi->channels;
273     info->samplerate = vi->rate;
274 
275     comment = ov_comment(&vf, -1);
276     if (comment)
277     {
278         temp = vorbis_comment_query(comment, "title", 0);
279         if (temp)
280             info->title = convertToISO(temp);
281 
282         temp = vorbis_comment_query(comment, "artist", 0);
283         if (temp)
284             info->artist = convertToISO(temp);
285 
286         temp = vorbis_comment_query(comment, "album", 0);
287         if (temp)
288             info->album = convertToISO(temp);
289 
290         temp = vorbis_comment_query(comment, "tracknumber", 0);
291         if (temp)
292             info->tracknumber = convertToISO(temp);
293 
294         temp = vorbis_comment_query(comment, "genre", 0);
295         if (temp)
296             info->genre = convertToISO(temp);
297 
298         temp = vorbis_comment_query(comment, "description", 0);
299         if (temp)
300             info->desc = convertToISO(temp);
301     }
302     ov_clear(&vf);
303 
304     return info;
305 }
306 
convertToISO(const char * utf8)307 static char *convertToISO(const char *utf8)
308 {
309    unsigned char *in, *buf;
310    unsigned char *out, *end;
311 
312    in = (unsigned char *)utf8;
313    buf = out = malloc(strlen(utf8) + 1);
314    end = in + strlen(utf8);
315    for(;*in != 0x00 && in <= end; in++, out++)
316    {
317        if (*in < 0x80)
318        {  /* lower 7-bits unchanged */
319           *out = *in;
320        }
321        else
322        if (*in > 0xC3)
323        { /* discard anything above 0xFF */
324           *out = '?';
325        }
326        else
327        if (*in & 0xC0)
328        { /* parse upper 7-bits */
329           if (in >= end)
330             *out = 0;
331           else
332           {
333             *out = (((*in) & 0x1F) << 6) | (0x3F & (*(++in)));
334           }
335        }
336        else
337        {
338           *out = '?';  /* this should never happen */
339        }
340    }
341    *out = 0x00; /* append null */
342 
343    return buf;
344 }
345