add 2206 to copyright notice
[platform/upstream/flac.git] / src / plugin_common / tags.c
1 /* plugin_common - Routines common to several plugins
2  * Copyright (C) 2002,2003,2004,2005,2006  Josh Coalson
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  */
18
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22
23 #include "tags.h"
24 #include "FLAC/assert.h"
25 #include "FLAC/metadata.h"
26
27
28 static __inline unsigned local__wide_strlen(const FLAC__uint16 *s)
29 {
30         unsigned n = 0;
31         while(*s++)
32                 n++;
33         return n;
34 }
35
36 /*
37  * also disallows non-shortest-form encodings, c.f.
38  *   http://www.unicode.org/versions/corrigendum1.html
39  * and a more clear explanation at the end of this section:
40  *   http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
41  */
42 static __inline unsigned local__utf8len(const FLAC__byte *utf8)
43 {
44         FLAC__ASSERT(0 != utf8);
45         if ((utf8[0] & 0x80) == 0) {
46                 return 1;
47         }
48         else if ((utf8[0] & 0xE0) == 0xC0 && (utf8[1] & 0xC0) == 0x80) {
49                 if ((utf8[0] & 0xFE) == 0xC0) /* overlong sequence check */
50                         return 0;
51                 return 2;
52         }
53         else if ((utf8[0] & 0xF0) == 0xE0 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80) {
54                 if (utf8[0] == 0xE0 && (utf8[1] & 0xE0) == 0x80) /* overlong sequence check */
55                         return 0;
56                 /* illegal surrogates check (U+D800...U+DFFF and U+FFFE...U+FFFF) */
57                 if (utf8[0] == 0xED && (utf8[1] & 0xE0) == 0xA0) /* D800-DFFF */
58                         return 0;
59                 if (utf8[0] == 0xEF && utf8[1] == 0xBF && (utf8[2] & 0xFE) == 0xBE) /* FFFE-FFFF */
60                         return 0;
61                 return 3;
62         }
63         else if ((utf8[0] & 0xF8) == 0xF0 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80) {
64                 if (utf8[0] == 0xF0 && (utf8[1] & 0xF0) == 0x80) /* overlong sequence check */
65                         return 0;
66                 return 4;
67         }
68         else if ((utf8[0] & 0xFC) == 0xF8 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80 && (utf8[4] & 0xC0) == 0x80) {
69                 if (utf8[0] == 0xF8 && (utf8[1] & 0xF8) == 0x80) /* overlong sequence check */
70                         return 0;
71                 return 5;
72         }
73         else if ((utf8[0] & 0xFE) == 0xFC && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80 && (utf8[4] & 0xC0) == 0x80 && (utf8[5] & 0xC0) == 0x80) {
74                 if (utf8[0] == 0xFC && (utf8[1] & 0xFC) == 0x80) /* overlong sequence check */
75                         return 0;
76                 return 6;
77         }
78         else {
79                 return 0;
80         }
81 }
82
83
84 static __inline unsigned local__utf8_to_ucs2(const FLAC__byte *utf8, FLAC__uint16 *ucs2)
85 {
86         const unsigned len = local__utf8len(utf8);
87
88         FLAC__ASSERT(0 != ucs2);
89
90         if (len == 1)
91                 *ucs2 = *utf8;
92         else if (len == 2)
93                 *ucs2 = (*utf8 & 0x3F)<<6 | (*(utf8+1) & 0x3F);
94         else if (len == 3)
95                 *ucs2 = (*utf8 & 0x1F)<<12 | (*(utf8+1) & 0x3F)<<6 | (*(utf8+2) & 0x3F);
96         else
97                 *ucs2 = '?';
98
99         return len;
100 }
101
102 static FLAC__uint16 *local__convert_utf8_to_ucs2(const char *src, unsigned length)
103 {
104         FLAC__uint16 *out;
105         unsigned chars = 0;
106
107         FLAC__ASSERT(0 != src);
108
109         /* calculate length */
110         {
111                 const unsigned char *s, *end;
112                 for (s=(const unsigned char *)src, end=s+length; s<end; chars++) {
113                         const unsigned n = local__utf8len(s);
114                         if (n == 0)
115                                 return 0;
116                         s += n;
117                 }
118                 FLAC__ASSERT(s == end);
119         }
120
121         /* allocate */
122         out = (FLAC__uint16*)malloc(chars * sizeof(FLAC__uint16));
123         if (0 == out) {
124                 FLAC__ASSERT(0);
125                 return 0;
126         }
127
128         /* convert */
129         {
130                 const unsigned char *s = (const unsigned char *)src;
131                 FLAC__uint16 *u = out;
132                 for ( ; chars; chars--)
133                         s += local__utf8_to_ucs2(s, u++);
134         }
135
136         return out;
137 }
138
139 static __inline unsigned local__ucs2len(FLAC__uint16 ucs2)
140 {
141         if (ucs2 < 0x0080)
142                 return 1;
143         else if (ucs2 < 0x0800)
144                 return 2;
145         else
146                 return 3;
147 }
148
149 static __inline unsigned local__ucs2_to_utf8(FLAC__uint16 ucs2, FLAC__byte *utf8)
150 {
151         if (ucs2 < 0x080) {
152                 utf8[0] = (FLAC__byte)ucs2;
153                 return 1;
154         }
155         else if (ucs2 < 0x800) {
156                 utf8[0] = 0xc0 | (ucs2 >> 6);
157                 utf8[1] = 0x80 | (ucs2 & 0x3f);
158                 return 2;
159         }
160         else {
161                 utf8[0] = 0xe0 | (ucs2 >> 12);
162                 utf8[1] = 0x80 | ((ucs2 >> 6) & 0x3f);
163                 utf8[2] = 0x80 | (ucs2 & 0x3f);
164                 return 3;
165         }
166 }
167
168 static char *local__convert_ucs2_to_utf8(const FLAC__uint16 *src, unsigned length)
169 {
170         char *out;
171         unsigned len = 0;
172
173         FLAC__ASSERT(0 != src);
174
175         /* calculate length */
176         {
177                 unsigned i;
178                 for (i = 0; i < length; i++)
179                         len += local__ucs2len(src[i]);
180         }
181
182         /* allocate */
183         out = (char*)malloc(len * sizeof(char));
184         if (0 == out)
185                 return 0;
186
187         /* convert */
188         {
189                 unsigned char *u = (unsigned char *)out;
190                 for ( ; *src; src++)
191                         u += local__ucs2_to_utf8(*src, u);
192                 local__ucs2_to_utf8(*src, u);
193         }
194
195         return out;
196 }
197
198
199 FLAC__bool FLAC_plugin__tags_get(const char *filename, FLAC__StreamMetadata **tags)
200 {
201         if(!FLAC__metadata_get_tags(filename, tags))
202                 if(0 == (*tags = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)))
203                         return false;
204         return true;
205 }
206
207 FLAC__bool FLAC_plugin__tags_set(const char *filename, const FLAC__StreamMetadata *tags)
208 {
209         FLAC__Metadata_Chain *chain;
210         FLAC__Metadata_Iterator *iterator;
211         FLAC__StreamMetadata *block;
212         FLAC__bool got_vorbis_comments = false;
213         FLAC__bool ok;
214
215         if(0 == (chain = FLAC__metadata_chain_new()))
216                 return false;
217
218         if(!FLAC__metadata_chain_read(chain, filename)) {
219                 FLAC__metadata_chain_delete(chain);
220                 return false;
221         }
222
223         if(0 == (iterator = FLAC__metadata_iterator_new())) {
224                 FLAC__metadata_chain_delete(chain);
225                 return false;
226         }
227
228         FLAC__metadata_iterator_init(iterator, chain);
229
230         do {
231                 if(FLAC__metadata_iterator_get_block_type(iterator) == FLAC__METADATA_TYPE_VORBIS_COMMENT)
232                         got_vorbis_comments = true;
233         } while(!got_vorbis_comments && FLAC__metadata_iterator_next(iterator));
234
235         if(0 == (block = FLAC__metadata_object_clone(tags))) {
236                 FLAC__metadata_chain_delete(chain);
237                 FLAC__metadata_iterator_delete(iterator);
238                 return false;
239         }
240
241         if(got_vorbis_comments)
242                 ok = FLAC__metadata_iterator_set_block(iterator, block);
243         else
244                 ok = FLAC__metadata_iterator_insert_block_after(iterator, block);
245
246         FLAC__metadata_iterator_delete(iterator);
247
248         if(ok) {
249                 FLAC__metadata_chain_sort_padding(chain);
250                 ok = FLAC__metadata_chain_write(chain, /*use_padding=*/true, /*preserve_file_stats=*/true);
251         }
252
253         FLAC__metadata_chain_delete(chain);
254
255         return ok;
256 }
257
258 void FLAC_plugin__tags_destroy(FLAC__StreamMetadata **tags)
259 {
260         FLAC__metadata_object_delete(*tags);
261         *tags = 0;
262 }
263
264 const char *FLAC_plugin__tags_get_tag_utf8(const FLAC__StreamMetadata *tags, const char *name)
265 {
266         const int i = FLAC__metadata_object_vorbiscomment_find_entry_from(tags, /*offset=*/0, name);
267         return (i < 0? 0 : strchr((const char *)tags->data.vorbis_comment.comments[i].entry, '=')+1);
268 }
269
270 FLAC__uint16 *FLAC_plugin__tags_get_tag_ucs2(const FLAC__StreamMetadata *tags, const char *name)
271 {
272         const char *utf8 = FLAC_plugin__tags_get_tag_utf8(tags, name);
273         if(0 == utf8)
274                 return 0;
275         return local__convert_utf8_to_ucs2(utf8, strlen(utf8)+1); /* +1 for terminating null */
276 }
277
278 int FLAC_plugin__tags_delete_tag(FLAC__StreamMetadata *tags, const char *name)
279 {
280         return FLAC__metadata_object_vorbiscomment_remove_entries_matching(tags, name);
281 }
282
283 int FLAC_plugin__tags_delete_all(FLAC__StreamMetadata *tags)
284 {
285         int n = (int)tags->data.vorbis_comment.num_comments;
286         if(n > 0) {
287                 if(!FLAC__metadata_object_vorbiscomment_resize_comments(tags, 0))
288                         n = -1;
289         }
290         return n;
291 }
292
293 FLAC__bool FLAC_plugin__tags_add_tag_utf8(FLAC__StreamMetadata *tags, const char *name, const char *value, const char *separator)
294 {
295         int i;
296
297         FLAC__ASSERT(0 != tags);
298         FLAC__ASSERT(0 != name);
299         FLAC__ASSERT(0 != value);
300
301         if(separator && (i = FLAC__metadata_object_vorbiscomment_find_entry_from(tags, /*offset=*/0, name)) >= 0) {
302                 FLAC__StreamMetadata_VorbisComment_Entry *entry = tags->data.vorbis_comment.comments+i;
303                 const size_t value_len = strlen(value);
304                 const size_t separator_len = strlen(separator);
305                 FLAC__byte *new_entry;
306                 if(0 == (new_entry = (FLAC__byte*)realloc(entry->entry, entry->length + value_len + separator_len + 1)))
307                         return false;
308                 memcpy(new_entry+entry->length, separator, separator_len);
309                 entry->length += separator_len;
310                 memcpy(new_entry+entry->length, value, value_len);
311                 entry->length += value_len;
312                 new_entry[entry->length] = '\0';
313                 entry->entry = new_entry;
314         }
315         else {
316                 FLAC__StreamMetadata_VorbisComment_Entry entry;
317                 if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, name, value))
318                         return false;
319                 FLAC__metadata_object_vorbiscomment_append_comment(tags, entry, /*copy=*/false);
320         }
321         return true;
322 }
323
324 FLAC__bool FLAC_plugin__tags_set_tag_ucs2(FLAC__StreamMetadata *tags, const char *name, const FLAC__uint16 *value, FLAC__bool replace_all)
325 {
326         FLAC__StreamMetadata_VorbisComment_Entry entry;
327
328         FLAC__ASSERT(0 != tags);
329         FLAC__ASSERT(0 != name);
330         FLAC__ASSERT(0 != value);
331
332         {
333                 char *utf8 = local__convert_ucs2_to_utf8(value, local__wide_strlen(value)+1); /* +1 for the terminating null */
334                 if(0 == utf8)
335                         return false;
336                 if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, name, utf8)) {
337                         free(utf8);
338                         return false;
339                 }
340                 free(utf8);
341         }
342         if(!FLAC__metadata_object_vorbiscomment_replace_comment(tags, entry, replace_all, /*copy=*/false))
343                 return false;
344         return true;
345 }