configure.ac: Move OggVorbis Encoder to Encoder Plugins.
[mpd-mk.git] / src / tag.c
blobaa96a468f448e1d83b0171774bcd98a63f74b084
1 /*
2 * Copyright (C) 2003-2010 The Music Player Daemon Project
3 * http://www.musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "config.h"
21 #include "tag.h"
22 #include "tag_internal.h"
23 #include "tag_pool.h"
24 #include "conf.h"
25 #include "song.h"
27 #include <glib.h>
28 #include <assert.h>
29 #include <stdio.h>
30 #include <stdlib.h>
32 /**
33 * Maximum number of items managed in the bulk list; if it is
34 * exceeded, we switch back to "normal" reallocation.
36 #define BULK_MAX 64
38 static struct {
39 #ifndef NDEBUG
40 bool busy;
41 #endif
42 struct tag_item *items[BULK_MAX];
43 } bulk;
45 const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
46 [TAG_ARTIST] = "Artist",
47 [TAG_ARTIST_SORT] = "ArtistSort",
48 [TAG_ALBUM] = "Album",
49 [TAG_ALBUM_ARTIST] = "AlbumArtist",
50 [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
51 [TAG_TITLE] = "Title",
52 [TAG_TRACK] = "Track",
53 [TAG_NAME] = "Name",
54 [TAG_GENRE] = "Genre",
55 [TAG_DATE] = "Date",
56 [TAG_COMPOSER] = "Composer",
57 [TAG_PERFORMER] = "Performer",
58 [TAG_COMMENT] = "Comment",
59 [TAG_DISC] = "Disc",
61 /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */
62 [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID",
63 [TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID",
64 [TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID",
65 [TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID",
68 bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
70 enum tag_type
71 tag_name_parse(const char *name)
73 assert(name != NULL);
75 for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
76 assert(tag_item_names[i] != NULL);
78 if (strcmp(name, tag_item_names[i]) == 0)
79 return (enum tag_type)i;
82 return TAG_NUM_OF_ITEM_TYPES;
85 enum tag_type
86 tag_name_parse_i(const char *name)
88 assert(name != NULL);
90 for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
91 assert(tag_item_names[i] != NULL);
93 if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0)
94 return (enum tag_type)i;
97 return TAG_NUM_OF_ITEM_TYPES;
100 static size_t items_size(const struct tag *tag)
102 return tag->num_items * sizeof(struct tag_item *);
105 void tag_lib_init(void)
107 const char *value;
108 int quit = 0;
109 char *temp;
110 char *s;
111 char *c;
112 enum tag_type type;
114 /* parse the "metadata_to_use" config parameter below */
116 /* ignore comments by default */
117 ignore_tag_items[TAG_COMMENT] = true;
119 value = config_get_string(CONF_METADATA_TO_USE, NULL);
120 if (value == NULL)
121 return;
123 memset(ignore_tag_items, true, TAG_NUM_OF_ITEM_TYPES);
125 if (0 == g_ascii_strcasecmp(value, "none"))
126 return;
128 temp = c = s = g_strdup(value);
129 while (!quit) {
130 if (*s == ',' || *s == '\0') {
131 if (*s == '\0')
132 quit = 1;
133 *s = '\0';
135 c = g_strstrip(c);
136 if (*c == 0)
137 continue;
139 type = tag_name_parse_i(c);
140 if (type == TAG_NUM_OF_ITEM_TYPES)
141 g_error("error parsing metadata item \"%s\"",
144 ignore_tag_items[type] = false;
146 s++;
147 c = s;
149 s++;
152 g_free(temp);
155 struct tag *tag_new(void)
157 struct tag *ret = g_new(struct tag, 1);
158 ret->items = NULL;
159 ret->time = -1;
160 ret->num_items = 0;
161 return ret;
164 static void tag_delete_item(struct tag *tag, unsigned idx)
166 assert(idx < tag->num_items);
167 tag->num_items--;
169 g_mutex_lock(tag_pool_lock);
170 tag_pool_put_item(tag->items[idx]);
171 g_mutex_unlock(tag_pool_lock);
173 if (tag->num_items - idx > 0) {
174 memmove(tag->items + idx, tag->items + idx + 1,
175 (tag->num_items - idx) * sizeof(tag->items[0]));
178 if (tag->num_items > 0) {
179 tag->items = g_realloc(tag->items, items_size(tag));
180 } else {
181 g_free(tag->items);
182 tag->items = NULL;
186 void tag_clear_items_by_type(struct tag *tag, enum tag_type type)
188 for (unsigned i = 0; i < tag->num_items; i++) {
189 if (tag->items[i]->type == type) {
190 tag_delete_item(tag, i);
191 /* decrement since when just deleted this node */
192 i--;
197 void tag_free(struct tag *tag)
199 int i;
201 assert(tag != NULL);
203 g_mutex_lock(tag_pool_lock);
204 for (i = tag->num_items; --i >= 0; )
205 tag_pool_put_item(tag->items[i]);
206 g_mutex_unlock(tag_pool_lock);
208 if (tag->items == bulk.items) {
209 #ifndef NDEBUG
210 assert(bulk.busy);
211 bulk.busy = false;
212 #endif
213 } else
214 g_free(tag->items);
216 g_free(tag);
219 struct tag *tag_dup(const struct tag *tag)
221 struct tag *ret;
223 if (!tag)
224 return NULL;
226 ret = tag_new();
227 ret->time = tag->time;
228 ret->num_items = tag->num_items;
229 ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL;
231 g_mutex_lock(tag_pool_lock);
232 for (unsigned i = 0; i < tag->num_items; i++)
233 ret->items[i] = tag_pool_dup_item(tag->items[i]);
234 g_mutex_unlock(tag_pool_lock);
236 return ret;
239 struct tag *
240 tag_merge(const struct tag *base, const struct tag *add)
242 struct tag *ret;
243 unsigned n;
245 assert(base != NULL);
246 assert(add != NULL);
248 /* allocate new tag object */
250 ret = tag_new();
251 ret->time = add->time > 0 ? add->time : base->time;
252 ret->num_items = base->num_items + add->num_items;
253 ret->items = ret->num_items > 0 ? g_malloc(items_size(ret)) : NULL;
255 g_mutex_lock(tag_pool_lock);
257 /* copy all items from "add" */
259 for (unsigned i = 0; i < add->num_items; ++i)
260 ret->items[i] = tag_pool_dup_item(add->items[i]);
262 n = add->num_items;
264 /* copy additional items from "base" */
266 for (unsigned i = 0; i < base->num_items; ++i)
267 if (!tag_has_type(add, base->items[i]->type))
268 ret->items[n++] = tag_pool_dup_item(base->items[i]);
270 g_mutex_unlock(tag_pool_lock);
272 assert(n <= ret->num_items);
274 if (n < ret->num_items) {
275 /* some tags were not copied - shrink ret->items */
276 assert(n > 0);
278 ret->num_items = n;
279 ret->items = g_realloc(ret->items, items_size(ret));
282 return ret;
285 struct tag *
286 tag_merge_replace(struct tag *base, struct tag *add)
288 if (add == NULL)
289 return base;
291 if (base == NULL)
292 return add;
294 struct tag *tag = tag_merge(base, add);
295 tag_free(base);
296 tag_free(add);
298 return tag;
301 const char *
302 tag_get_value(const struct tag *tag, enum tag_type type)
304 assert(tag != NULL);
305 assert(type < TAG_NUM_OF_ITEM_TYPES);
307 for (unsigned i = 0; i < tag->num_items; i++)
308 if (tag->items[i]->type == type)
309 return tag->items[i]->value;
311 return NULL;
314 bool tag_has_type(const struct tag *tag, enum tag_type type)
316 return tag_get_value(tag, type) != NULL;
319 bool tag_equal(const struct tag *tag1, const struct tag *tag2)
321 if (tag1 == NULL && tag2 == NULL)
322 return true;
323 else if (!tag1 || !tag2)
324 return false;
326 if (tag1->time != tag2->time)
327 return false;
329 if (tag1->num_items != tag2->num_items)
330 return false;
332 for (unsigned i = 0; i < tag1->num_items; i++) {
333 if (tag1->items[i]->type != tag2->items[i]->type)
334 return false;
335 if (strcmp(tag1->items[i]->value, tag2->items[i]->value)) {
336 return false;
340 return true;
344 * Replace invalid sequences with the question mark.
346 static char *
347 patch_utf8(const char *src, size_t length, const gchar *end)
349 /* duplicate the string, and replace invalid bytes in that
350 buffer */
351 char *dest = g_strdup(src);
353 do {
354 dest[end - src] = '?';
355 } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end));
357 return dest;
360 static char *
361 fix_utf8(const char *str, size_t length)
363 const gchar *end;
364 char *temp;
365 gsize written;
367 assert(str != NULL);
369 /* check if the string is already valid UTF-8 */
370 if (g_utf8_validate(str, length, &end))
371 return NULL;
373 /* no, it's not - try to import it from ISO-Latin-1 */
374 temp = g_convert(str, length, "utf-8", "iso-8859-1",
375 NULL, &written, NULL);
376 if (temp != NULL)
377 /* success! */
378 return temp;
380 /* no, still broken - there's no medication, just patch
381 invalid sequences */
382 return patch_utf8(str, length, end);
385 void tag_begin_add(struct tag *tag)
387 assert(!bulk.busy);
388 assert(tag != NULL);
389 assert(tag->items == NULL);
390 assert(tag->num_items == 0);
392 #ifndef NDEBUG
393 bulk.busy = true;
394 #endif
395 tag->items = bulk.items;
398 void tag_end_add(struct tag *tag)
400 if (tag->items == bulk.items) {
401 assert(tag->num_items <= BULK_MAX);
403 if (tag->num_items > 0) {
404 /* copy the tag items from the bulk list over
405 to a new list (which fits exactly) */
406 tag->items = g_malloc(items_size(tag));
407 memcpy(tag->items, bulk.items, items_size(tag));
408 } else
409 tag->items = NULL;
412 #ifndef NDEBUG
413 bulk.busy = false;
414 #endif
417 static bool
418 char_is_non_printable(unsigned char ch)
420 return ch < 0x20;
423 static const char *
424 find_non_printable(const char *p, size_t length)
426 for (size_t i = 0; i < length; ++i)
427 if (char_is_non_printable(p[i]))
428 return p + i;
430 return NULL;
434 * Clears all non-printable characters, convert them to space.
435 * Returns NULL if nothing needs to be cleared.
437 static char *
438 clear_non_printable(const char *p, size_t length)
440 const char *first = find_non_printable(p, length);
441 char *dest;
443 if (first == NULL)
444 return NULL;
446 dest = g_strndup(p, length);
448 for (size_t i = first - p; i < length; ++i)
449 if (char_is_non_printable(dest[i]))
450 dest[i] = ' ';
452 return dest;
455 static char *
456 fix_tag_value(const char *p, size_t length)
458 char *utf8, *cleared;
460 utf8 = fix_utf8(p, length);
461 if (utf8 != NULL) {
462 p = utf8;
463 length = strlen(p);
466 cleared = clear_non_printable(p, length);
467 if (cleared == NULL)
468 cleared = utf8;
469 else
470 g_free(utf8);
472 return cleared;
475 static void
476 tag_add_item_internal(struct tag *tag, enum tag_type type,
477 const char *value, size_t len)
479 unsigned int i = tag->num_items;
480 char *p;
482 p = fix_tag_value(value, len);
483 if (p != NULL) {
484 value = p;
485 len = strlen(value);
488 tag->num_items++;
490 if (tag->items != bulk.items)
491 /* bulk mode disabled */
492 tag->items = g_realloc(tag->items, items_size(tag));
493 else if (tag->num_items >= BULK_MAX) {
494 /* bulk list already full - switch back to non-bulk */
495 assert(bulk.busy);
497 tag->items = g_malloc(items_size(tag));
498 memcpy(tag->items, bulk.items,
499 items_size(tag) - sizeof(struct tag_item *));
502 g_mutex_lock(tag_pool_lock);
503 tag->items[i] = tag_pool_get_item(type, value, len);
504 g_mutex_unlock(tag_pool_lock);
506 g_free(p);
509 void tag_add_item_n(struct tag *tag, enum tag_type type,
510 const char *value, size_t len)
512 if (ignore_tag_items[type])
514 return;
516 if (!value || !len)
517 return;
519 tag_add_item_internal(tag, type, value, len);