1 /*
2 * t_fttaglib.c
3 *
4 * a taglib tagutil backend, using TagLib.
5 */
6 #include <limits.h>
7
8 /* TagLib headers */
9 #include "tag_c.h"
10
11 #include "t_config.h"
12 #include "t_backend.h"
13
14
15 static const char libid[] = "TagLib";
16
17
18 struct t_fttaglib_data {
19 const char *libid;
20 TagLib_File *file;
21 TagLib_Tag *tag;
22 };
23
24 struct t_backend *t_fttaglib_backend(void);
25
26 static void *t_fttaglib_init(const char *path);
27 static struct t_taglist *t_fttaglib_read(void *opaque);
28 static int t_fttaglib_write(void *opaque, const struct t_taglist *tlist);
29 static void t_fttaglib_clear(void *opaque);
30
31
32 struct t_backend *
t_fttaglib_backend(void)33 t_fttaglib_backend(void)
34 {
35
36 static struct t_backend b = {
37 .libid = libid,
38 .desc = "various file format but limited set of tags",
39 .init = t_fttaglib_init,
40 .read = t_fttaglib_read,
41 .write = t_fttaglib_write,
42 .clear = t_fttaglib_clear,
43 };
44
45 /* TagLib specific init */
46
47 /*
48 * FIXME: UTF-8 is a default in TagLib's C API, so this is useless.
49 * However it is unclear when we should switch to Latin1, in particular
50 * when we can't get a locale. This OK for now since most backend assume
51 * an UTF-8 (which should be fixed).
52 */
53 char *lc_all, *dot;
54 lc_all = getenv("LC_ALL");
55 if (lc_all != NULL) {
56 dot = strchr(lc_all, '.');
57 if (dot != NULL && strcmp(dot + 1, "UTF-8") == 0)
58 taglib_set_strings_unicode(1);
59 }
60 taglib_set_string_management_enabled(0);
61
62 return (&b);
63 }
64
65 static void *
t_fttaglib_init(const char * path)66 t_fttaglib_init(const char *path)
67 {
68 TagLib_File *f;
69 struct t_fttaglib_data *data;
70
71 assert(path != NULL);
72
73 data = calloc(1, sizeof(struct t_fttaglib_data));
74 if (data == NULL)
75 return (NULL);
76 data->libid = libid;
77
78 f = taglib_file_new(path);
79 if (f == NULL || !taglib_file_is_valid(f)) {
80 free(data);
81 return (NULL);
82 }
83
84 data->file = f;
85 data->tag = taglib_file_tag(f);
86
87 return (data);
88 }
89
90 static struct t_taglist *
t_fttaglib_read(void * opaque)91 t_fttaglib_read(void *opaque)
92 {
93 unsigned int uintval;
94 char buf[5], *val = NULL;
95 struct t_fttaglib_data *data;
96 struct t_taglist *tlist = NULL;
97
98 assert(opaque != NULL);
99 data = opaque;
100 assert(data->libid == libid);
101
102 if ((tlist = t_taglist_new()) == NULL)
103 return (NULL);
104
105 /*
106 * There is a lot of duplication around here but it's ok because it's
107 * very dumb code.
108 */
109
110 if ((val = taglib_tag_title(data->tag)) == NULL)
111 goto error_label;
112 if (strlen(val) > 0 && t_taglist_insert(tlist, "title", val) == -1)
113 goto error_label;
114 taglib_free(val);
115 val = NULL;
116
117 if ((val = taglib_tag_artist(data->tag)) == NULL)
118 goto error_label;
119 if (strlen(val) > 0 && t_taglist_insert(tlist, "artist", val) == -1)
120 goto error_label;
121 taglib_free(val);
122 val = NULL;
123
124 uintval = taglib_tag_year(data->tag);
125 if (uintval > 0 && uintval < 10000) {
126 if (sprintf(buf, "%04u", uintval) < 0)
127 goto error_label;
128 if (t_taglist_insert(tlist, "year", buf) == -1)
129 goto error_label;
130 }
131
132 if ((val = taglib_tag_album(data->tag)) == NULL)
133 goto error_label;
134 if (strlen(val) > 0 && t_taglist_insert(tlist, "album", val) == -1)
135 goto error_label;
136 taglib_free(val);
137 val = NULL;
138
139 uintval = taglib_tag_track(data->tag);
140 if (uintval > 0 && uintval < 10000) {
141 if (sprintf(buf, "%02u", uintval) < 0)
142 goto error_label;
143 if (t_taglist_insert(tlist, "track", buf) == -1)
144 goto error_label;
145 }
146
147 if ((val = taglib_tag_genre(data->tag)) == NULL)
148 goto error_label;
149 if (strlen(val) > 0 && t_taglist_insert(tlist, "genre", val) == -1)
150 goto error_label;
151 taglib_free(val);
152 val = NULL;
153
154 if ((val = taglib_tag_comment(data->tag)) == NULL)
155 goto error_label;
156 if (strlen(val) > 0 && t_taglist_insert(tlist, "comment", val) == -1)
157 goto error_label;
158 taglib_free(val);
159 val = NULL;
160
161 return (tlist);
162 error_label:
163 /* NOTE: the documentation does not state if NULL is a valid argument
164 for tablib_free() */
165 if (val != NULL)
166 taglib_free(val);
167 t_taglist_delete(tlist);
168 return (NULL);
169 }
170
171 static int
t_fttaglib_write(void * opaque,const struct t_taglist * tlist)172 t_fttaglib_write(void *opaque, const struct t_taglist *tlist)
173 {
174 struct t_fttaglib_data *data;
175 struct t_tag *t;
176 char *endptr;
177 unsigned long ulongval;
178
179 assert(opaque != NULL);
180 data = opaque;
181 assert(data->libid == libid);
182
183 /* clear all the tags */
184 taglib_tag_set_title(data->tag, "");
185 taglib_tag_set_artist(data->tag, "");
186 taglib_tag_set_year(data->tag, 0);
187 taglib_tag_set_album(data->tag, "");
188 taglib_tag_set_track(data->tag, 0);
189 taglib_tag_set_genre(data->tag, "");
190 taglib_tag_set_comment(data->tag, "");
191
192 /* load the tlist */
193 TAILQ_FOREACH(t, tlist->tags, entries) {
194 if (t_tag_keycmp(t->key, "title") == 0)
195 taglib_tag_set_title(data->tag, t->val);
196 else if (t_tag_keycmp(t->key, "artist") == 0)
197 taglib_tag_set_artist(data->tag, t->val);
198 else if (t_tag_keycmp(t->key, "year") == 0) {
199 ulongval = strtoul(t->val, &endptr, 10);
200 if (endptr == t->val || *endptr != '\0') {
201 warnx("invalid unsigned int argument for %s: %s",
202 t->key, t->val);
203 } else if (ulongval > UINT_MAX) {
204 warnx("invalid unsigned int argument for %s: %s (too large)",
205 t->key, t->val);
206 } else
207 taglib_tag_set_year(data->tag, (unsigned int)ulongval);
208 } else if (t_tag_keycmp(t->key, "album") == 0)
209 taglib_tag_set_album(data->tag, t->val);
210 else if (t_tag_keycmp(t->key, "track") == 0) {
211 ulongval = strtoul(t->val, &endptr, 10);
212 if (endptr == t->val || *endptr != '\0') {
213 warnx("invalid unsigned int argument for %s: %s",
214 t->key, t->val);
215 } else if (ulongval > UINT_MAX) {
216 warnx("invalid unsigned int argument for %s: %s (too large)",
217 t->key, t->val);
218 } else
219 taglib_tag_set_track(data->tag, (unsigned int)ulongval);
220 } else if (t_tag_keycmp(t->key, "genre") == 0)
221 taglib_tag_set_genre(data->tag, t->val);
222 else if (t_tag_keycmp(t->key, "comment") == 0)
223 taglib_tag_set_comment(data->tag, t->val);
224 else
225 warnx("unsupported tag for TagLib backend: %s", t->key);
226 }
227
228 if (!taglib_file_save(data->file))
229 return (-1);
230 return (0);
231 }
232
233 static void
t_fttaglib_clear(void * opaque)234 t_fttaglib_clear(void *opaque)
235 {
236 struct t_fttaglib_data *data;
237
238 assert(opaque != NULL);
239 data = opaque;
240 assert(data->libid == libid);
241
242 taglib_file_free(data->file);
243 free(data);
244 }
245