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