1 /* EasyTAG - Tag editor for audio files
2  * Copyright (C) 2014-2015  David King <amigadave@amigadave.com>
3  * Copyright (C) 2001-2003  Jerome Couderc <easytag@gmail.com>
4  *
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.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h" // For definition of ENABLE_OGG
21 
22 #ifdef ENABLE_OGG
23 
24 #include <glib/gi18n.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <vorbis/codec.h>
28 
29 #include "ogg_tag.h"
30 #include "vcedit.h"
31 #include "et_core.h"
32 #include "misc.h"
33 #include "picture.h"
34 #include "setting.h"
35 #include "charset.h"
36 
37 /* for mkstemp. */
38 #include "win32/win32dep.h"
39 
40 
41 /***************
42  * Declaration *
43  ***************/
44 
45 #define MULTIFIELD_SEPARATOR " - "
46 
47 /*
48  * convert_to_byte_array:
49  * @n: Integer to convert
50  * @array: Destination byte array
51  *
52  * Converts an integer to byte array.
53  */
54 static void
convert_to_byte_array(guint32 n,guchar * array)55 convert_to_byte_array (guint32 n, guchar *array)
56 {
57     array [0] = (n >> 24) & 0xFF;
58     array [1] = (n >> 16) & 0xFF;
59     array [2] = (n >> 8) & 0xFF;
60     array [3] = n & 0xFF;
61 }
62 
63 /*
64  * add_to_guchar_str:
65  * @ustr: Destination string
66  * @ustr_len: Pointer to length of destination string
67  * @str: String to append
68  * @str_len: Length of str
69  *
70  * Append a guchar string to given guchar string.
71  */
72 
73 static void
add_to_guchar_str(guchar * ustr,gsize * ustr_len,const guchar * str,gsize str_len)74 add_to_guchar_str (guchar *ustr,
75                    gsize *ustr_len,
76                    const guchar *str,
77                    gsize str_len)
78 {
79     gsize i;
80 
81     for (i = *ustr_len; i < *ustr_len + str_len; i++)
82     {
83         ustr[i] = str[i - *ustr_len];
84     }
85 
86     *ustr_len += str_len;
87 }
88 
89 /*
90  * read_guint_from_byte:
91  * @str: the byte string
92  * @start: position to start with
93  *
94  * Reads and returns an integer from given byte string starting from start.
95  * Returns: Integer which is read
96  */
97 static guint32
read_guint32_from_byte(guchar * str,gsize start)98 read_guint32_from_byte (guchar *str, gsize start)
99 {
100     gsize i;
101     guint32 read = 0;
102 
103     for (i = start; i < start + 4; i++)
104     {
105         read = (read << 8) + str[i];
106     }
107 
108     return read;
109 }
110 
111 /*
112  * set_or_append_field:
113  * @field: (inout): pointer to a location in which to store the field value
114  * @field_value: (transfer full): the string to store in @field
115  *
116  * Set @field to @field_value if @field is empty, otherwise append to it.
117  * Ownership of @field_value is transferred to this function.
118  */
119 static void
set_or_append_field(gchar ** field,gchar * field_value)120 set_or_append_field (gchar **field,
121                      gchar *field_value)
122 {
123     if (*field == NULL)
124     {
125         *field = field_value;
126     }
127     else
128     {
129         gchar *field_tmp = g_strconcat (*field, MULTIFIELD_SEPARATOR,
130                                         field_value, NULL);
131         g_free (*field);
132         *field = field_tmp;
133         g_free (field_value);
134     }
135 }
136 
137 /*
138  * validate_field_utf8:
139  * @field_value: the string to validate
140  * @field_len: the length of the string
141  *
142  * Validate a Vorbis comment field to ensure that it is UTF-8. Either return a
143  * duplicate of the original (valid) string, or a converted equivalent (of an
144  * invalid UTF-8 string).
145  *
146  * Returns: a valid UTF-8 represenation of @field_value
147  */
148 static gchar *
validate_field_utf8(const gchar * field_value,gint field_len)149 validate_field_utf8 (const gchar *field_value,
150                      gint field_len)
151 {
152     gchar *result;
153 
154     if (g_utf8_validate (field_value, field_len, NULL))
155     {
156         result = g_strndup (field_value, field_len);
157     }
158     else
159     {
160         gchar *field_value_tmp = g_strndup (field_value,
161                                             field_len);
162         /* Unnecessarily validates the field again, but this should not be the
163          * common case. */
164         result = Try_To_Validate_Utf8_String (field_value_tmp);
165         g_free (field_value_tmp);
166     }
167 
168     return result;
169 }
170 
171 /*
172  * populate_tag_hash_table:
173  * @vc: a Vorbis comment block from which to read fields
174  *
175  * Add comments from the supplied @vc block to a newly-allocated hash table.
176  * Normalise the field names to upper-case ASCII, taking care to ignore the
177  * current locale. Validate the field values are UTF-8 before inserting them in
178  * the hash table. Add the values as strings in a GSList.
179  *
180  * Returns: (transfer full): a newly-allocated hash table of tags
181  */
182 static GHashTable *
populate_tag_hash_table(const vorbis_comment * vc)183 populate_tag_hash_table (const vorbis_comment *vc)
184 {
185     GHashTable *ret;
186     gint i;
187 
188     /* Free the string lists manually, to avoid having to duplicate them each
189      * time that an existing key is inserted. */
190     ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
191 
192     for (i = 0; i < vc->comments; i++)
193     {
194         const gchar *comment = vc->user_comments[i];
195         const gint len = vc->comment_lengths[i];
196         const gchar *separator;
197         gchar *field;
198         gchar *field_up;
199         gchar *value;
200         /* TODO: Use a GPtrArray instead? */
201         GSList *l;
202 
203         separator = memchr (comment, '=', len);
204 
205         if (!separator)
206         {
207             g_warning ("Field separator not found when reading Vorbis tag: %s",
208                        comment);
209             continue;
210         }
211 
212         field = g_strndup (comment, separator - comment);
213         field_up = g_ascii_strup (field, -1);
214         g_free (field);
215 
216         l = g_hash_table_lookup (ret, field_up);
217 
218         /* If the lookup failed, a new list is created. The list takes
219          * ownership of the field value. */
220         value = validate_field_utf8 (separator + 1, len - ((separator + 1) -
221                                                            comment));
222 
223         /* Appending is slower, but much easier here (and the lists should be
224          * short). */
225         l = g_slist_append (l, value);
226 
227         g_hash_table_insert (ret, field_up, l);
228     }
229 
230     return ret;
231 }
232 
233 /*
234  * values_list_foreach
235  * @data: (transfer full): the tag value
236  * @user_data: (inout); a tag location to fill
237  *
238  * Called on each element in a GSList of tag values, to set or append the
239  * string to the tag.
240  */
241 static void
values_list_foreach(gpointer data,gpointer user_data)242 values_list_foreach (gpointer data,
243                      gpointer user_data)
244 {
245     gchar *value = (gchar *)data;
246     gchar **tag = (gchar **)user_data;
247 
248     if (!et_str_empty (value))
249     {
250         set_or_append_field (tag, value);
251     }
252 }
253 
254 /*
255  * et_add_file_tags_from_vorbis_comments:
256  * @vc: Vorbis comment from which to fill @FileTag
257  * @FileTag: tag to populate from @vc
258  *
259  * Reads Vorbis comments and copies them to file tag.
260  */
261 void
et_add_file_tags_from_vorbis_comments(vorbis_comment * vc,File_Tag * FileTag)262 et_add_file_tags_from_vorbis_comments (vorbis_comment *vc,
263                                        File_Tag *FileTag)
264 {
265     GHashTable *tags;
266     GSList *strings;
267     GHashTableIter tags_iter;
268     gchar *key;
269     EtPicture *prev_pic = NULL;
270 
271     tags = populate_tag_hash_table (vc);
272 
273     /* Note : don't forget to add any new field to 'Save unsupported fields' */
274 
275     /* Title. */
276     if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_TITLE)))
277     {
278         g_slist_foreach (strings, values_list_foreach, &FileTag->title);
279         g_slist_free (strings);
280         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_TITLE);
281     }
282 
283     /* Artist. */
284     if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_ARTIST)))
285     {
286         g_slist_foreach (strings, values_list_foreach, &FileTag->artist);
287         g_slist_free (strings);
288         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ARTIST);
289     }
290 
291     /* Album artist. */
292     if ((strings = g_hash_table_lookup (tags,
293                                         ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST)))
294     {
295         g_slist_foreach (strings, values_list_foreach, &FileTag->album_artist);
296         g_slist_free (strings);
297         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST);
298     }
299 
300     /* Album */
301     if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_ALBUM)))
302     {
303         g_slist_foreach (strings, values_list_foreach, &FileTag->album);
304         g_slist_free (strings);
305         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ALBUM);
306     }
307 
308     /* Disc number and total discs. */
309     if ((strings = g_hash_table_lookup (tags,
310                                         ET_VORBIS_COMMENT_FIELD_DISC_TOTAL)))
311     {
312         /* Only take values from the first total discs field. */
313         if (!et_str_empty (strings->data))
314         {
315             FileTag->disc_total = et_disc_number_to_string (atoi (strings->data));
316         }
317 
318         g_slist_free_full (strings, g_free);
319         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DISC_TOTAL);
320     }
321 
322     if ((strings = g_hash_table_lookup (tags,
323                                         ET_VORBIS_COMMENT_FIELD_DISC_NUMBER)))
324     {
325         /* Only take values from the first disc number field. */
326         if (!et_str_empty (strings->data))
327         {
328             gchar *separator;
329 
330             separator = strchr (strings->data, '/');
331 
332             if (separator && !FileTag->disc_total)
333             {
334                 FileTag->disc_total = et_disc_number_to_string (atoi (separator + 1));
335                 *separator = '\0';
336             }
337 
338             FileTag->disc_number = et_disc_number_to_string (atoi (strings->data));
339         }
340 
341         g_slist_free_full (strings, g_free);
342         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DISC_NUMBER);
343     }
344 
345     /* Year. */
346     if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_DATE)))
347     {
348         g_slist_foreach (strings, values_list_foreach, &FileTag->year);
349         g_slist_free (strings);
350         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DATE);
351     }
352 
353     /* Track number and total tracks. */
354     if ((strings = g_hash_table_lookup (tags,
355                                         ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL)))
356     {
357         /* Only take values from the first total tracks field. */
358         if (!et_str_empty (strings->data))
359         {
360             FileTag->track_total = et_track_number_to_string (atoi (strings->data));
361         }
362 
363         g_slist_free_full (strings, g_free);
364         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL);
365     }
366 
367     if ((strings = g_hash_table_lookup (tags,
368                                         ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER)))
369     {
370         /* Only take values from the first track number field. */
371         if (!et_str_empty (strings->data))
372         {
373             gchar *separator;
374 
375             separator = strchr (strings->data, '/');
376 
377             if (separator && !FileTag->track_total)
378             {
379                 FileTag->track_total = et_track_number_to_string (atoi (separator + 1));
380                 *separator = '\0';
381             }
382 
383             FileTag->track = et_track_number_to_string (atoi (strings->data));
384         }
385 
386         g_slist_free_full (strings, g_free);
387         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER);
388     }
389 
390     /* Genre. */
391     if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_GENRE)))
392     {
393         g_slist_foreach (strings, values_list_foreach, &FileTag->genre);
394         g_slist_free (strings);
395         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_GENRE);
396     }
397 
398     /* Comment */
399     {
400         GSList *descs;
401         GSList *comments;
402 
403         descs = g_hash_table_lookup (tags,
404                                      ET_VORBIS_COMMENT_FIELD_DESCRIPTION);
405         comments = g_hash_table_lookup (tags,
406                                         ET_VORBIS_COMMENT_FIELD_COMMENT);
407 
408         if (descs && !comments)
409         {
410             g_slist_foreach (descs, values_list_foreach, &FileTag->comment);
411         }
412         else if (descs && comments)
413         {
414             /* Mark the file as modified, so that comments are written to the
415              * DESCRIPTION field on saving. */
416             FileTag->saved = FALSE;
417 
418             g_slist_foreach (descs, values_list_foreach, &FileTag->comment);
419             g_slist_foreach (comments, values_list_foreach, &FileTag->comment);
420         }
421         else if (comments)
422         {
423             FileTag->saved = FALSE;
424 
425             g_slist_foreach (comments, values_list_foreach, &FileTag->comment);
426         }
427 
428         g_slist_free (descs);
429         g_slist_free (comments);
430         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DESCRIPTION);
431         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COMMENT);
432     }
433 
434     /* Composer. */
435     if ((strings = g_hash_table_lookup (tags,
436                                         ET_VORBIS_COMMENT_FIELD_COMPOSER)))
437     {
438         g_slist_foreach (strings, values_list_foreach, &FileTag->composer);
439         g_slist_free (strings);
440         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COMPOSER);
441     }
442 
443     /* Original artist. */
444     if ((strings = g_hash_table_lookup (tags,
445                                         ET_VORBIS_COMMENT_FIELD_PERFORMER)))
446     {
447         g_slist_foreach (strings, values_list_foreach, &FileTag->orig_artist);
448         g_slist_free (strings);
449         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_PERFORMER);
450     }
451 
452     /* Copyright. */
453     if ((strings = g_hash_table_lookup (tags,
454                                         ET_VORBIS_COMMENT_FIELD_COPYRIGHT)))
455     {
456         g_slist_foreach (strings, values_list_foreach, &FileTag->copyright);
457         g_slist_free (strings);
458         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COPYRIGHT);
459     }
460 
461     /* URL. */
462     if ((strings = g_hash_table_lookup (tags,
463                                         ET_VORBIS_COMMENT_FIELD_CONTACT)))
464     {
465         g_slist_foreach (strings, values_list_foreach, &FileTag->url);
466         g_slist_free (strings);
467         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_CONTACT);
468     }
469 
470     /* Encoded by. */
471     if ((strings = g_hash_table_lookup (tags,
472                                         ET_VORBIS_COMMENT_FIELD_ENCODED_BY)))
473     {
474         g_slist_foreach (strings, values_list_foreach, &FileTag->encoded_by);
475         g_slist_free (strings);
476         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ENCODED_BY);
477     }
478 
479     /* Cover art. */
480     if ((strings = g_hash_table_lookup (tags,
481                                         ET_VORBIS_COMMENT_FIELD_COVER_ART)))
482     {
483         GSList *l;
484         GSList *m;
485         GSList *n;
486         GSList *types;
487         GSList *descs;
488 
489         /* Force marking the file as modified, so that the deprecated cover art
490          * field is converted to a METADATA_PICTURE_BLOCK field. */
491         FileTag->saved = FALSE;
492 
493         types = g_hash_table_lookup (tags,
494                                      ET_VORBIS_COMMENT_FIELD_COVER_ART_TYPE);
495         descs = g_hash_table_lookup (tags,
496                                      ET_VORBIS_COMMENT_FIELD_COVER_ART_DESCRIPTION);
497 
498         l = strings;
499         m = types;
500         n = descs;
501 
502         while (l && !et_str_empty (l->data))
503         {
504             EtPicture *pic;
505             guchar *data;
506             gsize data_size;
507             GBytes *bytes;
508             EtPictureType type;
509             const gchar *description;
510 
511             /* Decode picture data. */
512             data = g_base64_decode (l->data, &data_size);
513             bytes = g_bytes_new_take (data, data_size);
514 
515             /* It is only necessary for there to be image data, but the type
516              * and description are optional. */
517             if (m)
518             {
519                 type = !et_str_empty (m->data) ? atoi (m->data)
520                                                : ET_PICTURE_TYPE_FRONT_COVER;
521 
522                 m = g_slist_next (m);
523             }
524             else
525             {
526                 type = ET_PICTURE_TYPE_FRONT_COVER;
527             }
528 
529             if (n)
530             {
531                 description = !et_str_empty (n->data) ? n->data : "";
532 
533                 n = g_slist_next (n);
534             }
535             else
536             {
537                 description = "";
538             }
539 
540             pic = et_picture_new (type, description, 0, 0, bytes);
541             g_bytes_unref (bytes);
542 
543             if (!prev_pic)
544             {
545                 FileTag->picture = pic;
546             }
547             else
548             {
549                 prev_pic->next = pic;
550             }
551 
552             prev_pic = pic;
553 
554             l = g_slist_next (l);
555         }
556 
557         g_slist_free_full (strings, g_free);
558         g_slist_free_full (types, g_free);
559         g_slist_free_full (descs, g_free);
560         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COVER_ART);
561         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COVER_ART_DESCRIPTION);
562         g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COVER_ART_TYPE);
563     }
564 
565     /* METADATA_BLOCK_PICTURE tag used for picture information. */
566     if ((strings = g_hash_table_lookup (tags,
567                                         ET_VORBIS_COMMENT_FIELD_METADATA_BLOCK_PICTURE)))
568     {
569         GSList *l;
570 
571         for (l = strings; l != NULL; l = g_slist_next (l))
572         {
573             EtPicture *pic;
574             gsize bytes_pos, mimelen, desclen;
575             guchar *decoded_ustr;
576             GBytes *bytes = NULL;
577             EtPictureType type;
578             gchar *description;
579             GBytes *pic_bytes;
580             gsize decoded_size;
581             gsize data_size;
582 
583             /* Decode picture data. */
584             decoded_ustr = g_base64_decode (l->data, &decoded_size);
585 
586             /* Check that the comment decoded to a long enough string to hold the
587              * whole structure (8 fields of 4 bytes each). */
588             if (decoded_size < 8 * 4)
589             {
590                 g_free (decoded_ustr);
591                 goto invalid_picture;
592             }
593 
594             bytes = g_bytes_new_take (decoded_ustr, decoded_size);
595 
596             /* Reading picture type. */
597             type = read_guint32_from_byte (decoded_ustr, 0);
598             bytes_pos = 4;
599 
600             /* TODO: Check that there is a maximum of 1 of each of
601              * ET_PICTURE_TYPE_FILE_ICON and ET_PICTURE_TYPE_OTHER_FILE_ICON types
602              * in the file. */
603             if (type >= ET_PICTURE_TYPE_UNDEFINED)
604             {
605                 goto invalid_picture;
606             }
607 
608             /* Reading MIME data. */
609             mimelen = read_guint32_from_byte (decoded_ustr, bytes_pos);
610             bytes_pos += 4;
611 
612             if (mimelen > decoded_size - bytes_pos - (6 * 4))
613             {
614                 goto invalid_picture;
615             }
616 
617             /* Check for a valid MIME type. */
618             if (mimelen > 0)
619             {
620                 const gchar *mime;
621 
622                 mime = (const gchar *)&decoded_ustr[bytes_pos];
623 
624                 /* TODO: Check for "-->" when adding linked image support. */
625                 if (strncmp (mime, "image/", mimelen) != 0
626                     && strncmp (mime, "image/png", mimelen) != 0
627                     && strncmp (mime, "image/jpeg", mimelen) != 0)
628                 {
629                     gchar *mime_str;
630 
631                     mime_str = g_strndup (mime, mimelen);
632                     g_debug ("Invalid Vorbis comment image MIME type: %s",
633                              mime_str);
634 
635                     g_free (mime_str);
636                     goto invalid_picture;
637                 }
638             }
639 
640             /* Skip over the MIME type, as gdk-pixbuf does not use it. */
641             bytes_pos += mimelen;
642 
643             /* Reading description */
644             desclen = read_guint32_from_byte (decoded_ustr, bytes_pos);
645             bytes_pos += 4;
646 
647             if (desclen > decoded_size - bytes_pos - (5 * 4))
648             {
649                 goto invalid_picture;
650             }
651 
652             description = g_strndup ((const gchar *)&decoded_ustr[bytes_pos],
653                                      desclen);
654 
655             /* Skip the width, height, color depth and number-of-colors fields. */
656             bytes_pos += desclen + 16;
657 
658             /* Reading picture size */
659             data_size = read_guint32_from_byte (decoded_ustr, bytes_pos);
660             bytes_pos += 4;
661 
662             if (data_size > decoded_size - bytes_pos)
663             {
664                 g_free (description);
665                 goto invalid_picture;
666             }
667 
668             /* Read only the image data into a new GBytes. */
669             pic_bytes = g_bytes_new_from_bytes (bytes, bytes_pos, data_size);
670 
671             pic = et_picture_new (type, description, 0, 0, pic_bytes);
672 
673             g_free (description);
674             g_bytes_unref (pic_bytes);
675 
676             if (!prev_pic)
677             {
678                 FileTag->picture = pic;
679             }
680             else
681             {
682                 prev_pic->next = pic;
683             }
684 
685             prev_pic = pic;
686 
687             /* pic->bytes still holds a ref on the decoded data. */
688             g_bytes_unref (bytes);
689             continue;
690 
691 invalid_picture:
692             /* Mark the file as modified, so that the invalid field is removed upon
693              * saving. */
694             FileTag->saved = FALSE;
695 
696             g_bytes_unref (bytes);
697         }
698 
699         g_slist_free_full (strings, g_free);
700         g_hash_table_remove (tags,
701                              ET_VORBIS_COMMENT_FIELD_METADATA_BLOCK_PICTURE);
702     }
703 
704     /* Save unsupported fields. */
705     g_hash_table_iter_init (&tags_iter, tags);
706 
707     while (g_hash_table_iter_next (&tags_iter, (gpointer *)&key,
708                                    (gpointer *)&strings))
709     {
710         GSList *l;
711 
712         for (l = strings; l != NULL; l = g_slist_next (l))
713         {
714             FileTag->other = g_list_prepend (FileTag->other,
715                                              g_strconcat (key, "=", l->data,
716                                                           NULL));
717         }
718 
719         g_slist_free_full (strings, g_free);
720         g_hash_table_iter_remove (&tags_iter);
721     }
722 
723     if (FileTag->other)
724     {
725         FileTag->other = g_list_reverse (FileTag->other);
726     }
727 
728     /* The hash table should now only contain keys. */
729     g_hash_table_unref (tags);
730 }
731 
732 /*
733  * Read tag data into an Ogg Vorbis file.
734  * Note:
735  *  - if field is found but contains no info (strlen(str)==0), we don't read it
736  */
737 gboolean
ogg_tag_read_file_tag(GFile * file,File_Tag * FileTag,GError ** error)738 ogg_tag_read_file_tag (GFile *file,
739                        File_Tag *FileTag,
740                        GError **error)
741 {
742     GFileInputStream *istream;
743     EtOggState *state;
744 
745     g_return_val_if_fail (file != NULL && FileTag != NULL, FALSE);
746     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
747 
748     istream = g_file_read (file, NULL, error);
749 
750     if (!istream)
751     {
752         g_assert (error == NULL || *error != NULL);
753         return FALSE;
754     }
755 
756     {
757     /* Check for an unsupported ID3v2 tag. */
758     guchar tmp_id3[4];
759 
760     if (g_input_stream_read (G_INPUT_STREAM (istream), tmp_id3, 4, NULL,
761                              error) == 4)
762     {
763         /* Calculate ID3v2 length. */
764         if (tmp_id3[0] == 'I' && tmp_id3[1] == 'D' && tmp_id3[2] == '3'
765             && tmp_id3[3] < 0xFF)
766         {
767             /* ID3v2 tag skipper $49 44 33 yy yy xx zz zz zz zz [zz size]. */
768             /* Size is 6-9 position */
769             if (!g_seekable_seek (G_SEEKABLE (istream), 2, G_SEEK_CUR,
770                                   NULL, error))
771             {
772                 goto err;
773             }
774 
775             if (g_input_stream_read (G_INPUT_STREAM (istream), tmp_id3, 4,
776                                      NULL, error) == 4)
777             {
778                 gchar *path;
779 
780                 path = g_file_get_path (file);
781                 g_debug ("Ogg file '%s' contains an ID3v2 tag", path);
782                 g_free (path);
783 
784                 /* Mark the file as modified, so that the ID3 tag is removed
785                  * upon saving. */
786                 FileTag->saved = FALSE;
787             }
788         }
789     }
790 
791     if (error && *error != NULL)
792     {
793         goto err;
794     }
795 
796     }
797 
798     g_assert (error == NULL || *error == NULL);
799 
800     g_object_unref (istream);
801 
802     state = vcedit_new_state();    // Allocate memory for 'state'
803 
804     if (!vcedit_open (state, file, error))
805     {
806         g_assert (error == NULL || *error != NULL);
807         vcedit_clear(state);
808         return FALSE;
809     }
810 
811     g_assert (error == NULL || *error == NULL);
812 
813     /* Get data from tag */
814     /*{
815         gint i;
816         for (i=0;i<vc->comments;i++)
817             g_print("%s -> Ogg vc:'%s'\n",g_path_get_basename(filename),vc->user_comments[i]);
818     }*/
819 
820     et_add_file_tags_from_vorbis_comments (vcedit_comments(state), FileTag);
821     vcedit_clear(state);
822 
823     return TRUE;
824 
825 err:
826     g_assert (error == NULL || *error != NULL);
827     g_object_unref (istream);
828     return FALSE;
829 }
830 
831 /*
832  * Save field value in separated tags if it contains multifields
833  */
834 static void
et_ogg_write_delimited_tag(vorbis_comment * vc,const gchar * tag_name,const gchar * values)835 et_ogg_write_delimited_tag (vorbis_comment *vc,
836                             const gchar *tag_name,
837                             const gchar *values)
838 {
839     gchar **strings;
840     gsize i;
841 
842     strings = g_strsplit (values, MULTIFIELD_SEPARATOR, 255);
843 
844     for (i = 0; strings[i] != NULL; i++)
845     {
846         if (*strings[i])
847         {
848             vorbis_comment_add_tag (vc, tag_name, strings[i]);
849         }
850     }
851 
852     g_strfreev (strings);
853 }
854 
855 static void
et_ogg_set_tag(vorbis_comment * vc,const gchar * tag_name,const gchar * value,gboolean split)856 et_ogg_set_tag (vorbis_comment *vc,
857                 const gchar *tag_name,
858                 const gchar *value,
859                 gboolean split)
860 {
861     if (value)
862     {
863         if (split)
864         {
865             et_ogg_write_delimited_tag (vc, tag_name, value);
866         }
867         else
868         {
869             vorbis_comment_add_tag (vc, tag_name, value);
870         }
871     }
872 }
873 
874 gboolean
ogg_tag_write_file_tag(const ET_File * ETFile,GError ** error)875 ogg_tag_write_file_tag (const ET_File *ETFile,
876                         GError **error)
877 {
878     const File_Tag *FileTag;
879     const gchar *filename;
880     GFile           *file;
881     EtOggState *state;
882     vorbis_comment *vc;
883     GList *l;
884     EtPicture *pic;
885 
886     g_return_val_if_fail (ETFile != NULL && ETFile->FileTag != NULL, FALSE);
887     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
888 
889     FileTag       = (File_Tag *)ETFile->FileTag->data;
890     filename      = ((File_Name *)ETFile->FileNameCur->data)->value;
891 
892     file = g_file_new_for_path (filename);
893 
894     state = vcedit_new_state();    // Allocate memory for 'state'
895 
896     if (!vcedit_open (state, file, error))
897     {
898         g_assert (error == NULL || *error != NULL);
899         g_object_unref (file);
900         vcedit_clear(state);
901         return FALSE;
902     }
903 
904     g_assert (error == NULL || *error == NULL);
905 
906     /* Get data from tag */
907     vc = vcedit_comments(state);
908     vorbis_comment_clear(vc);
909     vorbis_comment_init(vc);
910 
911     /*********
912      * Title *
913      *********/
914     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_TITLE, FileTag->title,
915                     g_settings_get_boolean (MainSettings, "ogg-split-title"));
916 
917     /**********
918      * Artist *
919      **********/
920     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_ARTIST, FileTag->artist,
921                     g_settings_get_boolean (MainSettings, "ogg-split-artist"));
922 
923     /****************
924      * Album Artist *
925      ****************/
926     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST,
927                     FileTag->album_artist,
928                     g_settings_get_boolean (MainSettings, "ogg-split-artist"));
929 
930     /*********
931      * Album *
932      *********/
933     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_ALBUM, FileTag->album,
934                     g_settings_get_boolean (MainSettings, "ogg-split-album"));
935 
936     /***************
937      * Disc Number *
938      ***************/
939     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_DISC_NUMBER,
940                     FileTag->disc_number, FALSE);
941     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_DISC_TOTAL,
942                     FileTag->disc_total, FALSE);
943 
944     /********
945      * Year *
946      ********/
947     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_DATE, FileTag->year, FALSE);
948 
949     /*************************
950      * Track and Total Track *
951      *************************/
952     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER, FileTag->track,
953                     FALSE);
954     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL,
955                     FileTag->track_total, FALSE);
956 
957     /*********
958      * Genre *
959      *********/
960     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_GENRE, FileTag->genre,
961                     g_settings_get_boolean (MainSettings, "ogg-split-genre"));
962 
963     /***********
964      * Comment *
965      ***********/
966     /* Format of new specification. */
967     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_DESCRIPTION, FileTag->comment,
968                     g_settings_get_boolean (MainSettings,
969                                             "ogg-split-comment"));
970 
971     /************
972      * Composer *
973      ************/
974     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_COMPOSER, FileTag->composer,
975                     g_settings_get_boolean (MainSettings,
976                                             "ogg-split-composer"));
977 
978     /*******************
979      * Original artist *
980      *******************/
981     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_PERFORMER,
982                     FileTag->orig_artist,
983                     g_settings_get_boolean (MainSettings,
984                                             "ogg-split-original-artist"));
985 
986     /*************
987      * Copyright *
988      *************/
989     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_COPYRIGHT, FileTag->copyright,
990                     FALSE);
991 
992     /*******
993      * URL *
994      *******/
995     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_CONTACT, FileTag->url, FALSE);
996 
997     /**************
998      * Encoded by *
999      **************/
1000     et_ogg_set_tag (vc, ET_VORBIS_COMMENT_FIELD_ENCODED_BY,
1001                     FileTag->encoded_by, FALSE);
1002 
1003     /***********
1004      * Picture *
1005      ***********/
1006     for (pic = FileTag->picture; pic != NULL; pic = pic->next)
1007     {
1008         const gchar *mime;
1009         guchar array[4];
1010         guchar *ustring = NULL;
1011         gsize ustring_len = 0;
1012         gchar *base64_string;
1013         gsize desclen;
1014         gconstpointer data;
1015         gsize data_size;
1016         Picture_Format format = Picture_Format_From_Data (pic);
1017 
1018         /* According to the specification, only PNG and JPEG images should
1019          * be added to Vorbis comments. */
1020         if (format != PICTURE_FORMAT_PNG && format != PICTURE_FORMAT_JPEG)
1021         {
1022             GdkPixbufLoader *loader;
1023             GError *loader_error = NULL;
1024 
1025             loader = gdk_pixbuf_loader_new ();
1026 
1027             if (!gdk_pixbuf_loader_write_bytes (loader, pic->bytes,
1028                                                 &loader_error))
1029             {
1030                 g_debug ("Error parsing image data: %s",
1031                          loader_error->message);
1032                 g_error_free (loader_error);
1033                 g_object_unref (loader);
1034                 continue;
1035             }
1036             else
1037             {
1038                 GdkPixbuf *pixbuf;
1039                 gchar *buffer;
1040                 gsize buffer_size;
1041 
1042                 if (!gdk_pixbuf_loader_close (loader, &loader_error))
1043                 {
1044                     g_debug ("Error parsing image data: %s",
1045                              loader_error->message);
1046                     g_error_free (loader_error);
1047                     g_object_unref (loader);
1048                     continue;
1049                 }
1050 
1051                 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1052 
1053                 if (!pixbuf)
1054                 {
1055                     g_object_unref (loader);
1056                     continue;
1057                 }
1058 
1059                 g_object_ref (pixbuf);
1060                 g_object_unref (loader);
1061 
1062                 /* Always convert to PNG. */
1063                 if (!gdk_pixbuf_save_to_buffer (pixbuf, &buffer,
1064                                                 &buffer_size, "png",
1065                                                 &loader_error, NULL))
1066                 {
1067                     g_debug ("Error while converting image to PNG: %s",
1068                              loader_error->message);
1069                     g_error_free (loader_error);
1070                     g_object_unref (pixbuf);
1071                     continue;
1072                 }
1073 
1074                 g_object_unref (pixbuf);
1075 
1076                 g_bytes_unref (pic->bytes);
1077                 pic->bytes = g_bytes_new_take (buffer, buffer_size);
1078 
1079                 /* Set the picture format to reflect the new data. */
1080                 format = Picture_Format_From_Data (pic);
1081             }
1082         }
1083 
1084         mime = Picture_Mime_Type_String (format);
1085 
1086         data = g_bytes_get_data (pic->bytes, &data_size);
1087 
1088         /* Calculating full length of byte string and allocating. */
1089         desclen = pic->description ? strlen (pic->description) : 0;
1090         ustring = g_malloc (4 * 8 + strlen (mime) + desclen + data_size);
1091 
1092         /* Adding picture type. */
1093         convert_to_byte_array (pic->type, array);
1094         add_to_guchar_str (ustring, &ustring_len, array, 4);
1095 
1096         /* Adding MIME string and its length. */
1097         convert_to_byte_array (strlen (mime), array);
1098         add_to_guchar_str (ustring, &ustring_len, array, 4);
1099         add_to_guchar_str (ustring, &ustring_len, (const guchar *)mime,
1100                            strlen (mime));
1101 
1102         /* Adding picture description. */
1103         convert_to_byte_array (desclen, array);
1104         add_to_guchar_str (ustring, &ustring_len, array, 4);
1105         add_to_guchar_str (ustring, &ustring_len,
1106                            (guchar *)pic->description,
1107                            desclen);
1108 
1109         /* Adding width, height, color depth, indexed colors. */
1110         convert_to_byte_array (pic->width, array);
1111         add_to_guchar_str (ustring, &ustring_len, array, 4);
1112 
1113         convert_to_byte_array (pic->height, array);
1114         add_to_guchar_str (ustring, &ustring_len, array, 4);
1115 
1116         convert_to_byte_array (0, array);
1117         add_to_guchar_str (ustring, &ustring_len, array, 4);
1118 
1119         convert_to_byte_array (0, array);
1120         add_to_guchar_str (ustring, &ustring_len, array, 4);
1121 
1122         /* Adding picture data and its size. */
1123         convert_to_byte_array (data_size, array);
1124         add_to_guchar_str (ustring, &ustring_len, array, 4);
1125 
1126         add_to_guchar_str (ustring, &ustring_len, data, data_size);
1127 
1128         base64_string = g_base64_encode (ustring, ustring_len);
1129         vorbis_comment_add_tag (vc,
1130                                 ET_VORBIS_COMMENT_FIELD_METADATA_BLOCK_PICTURE,
1131                                 base64_string);
1132 
1133         g_free (base64_string);
1134         g_free (ustring);
1135     }
1136 
1137     /**************************
1138      * Set unsupported fields *
1139      **************************/
1140     for (l = FileTag->other; l != NULL; l = g_list_next (l))
1141     {
1142         if (l->data)
1143         {
1144             vorbis_comment_add (vc, (gchar *)l->data);
1145         }
1146     }
1147 
1148     /* Write tag to 'file' in all cases */
1149     if (!vcedit_write (state, file, error))
1150     {
1151         g_assert (error == NULL || *error != NULL);
1152         g_object_unref (file);
1153         vcedit_clear(state);
1154         return FALSE;
1155     }
1156     else
1157     {
1158         vcedit_clear (state);
1159     }
1160 
1161     g_object_unref (file);
1162 
1163     g_assert (error == NULL || *error == NULL);
1164     return TRUE;
1165 }
1166 #endif /* ENABLE_OGG */
1167