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