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  * Copyright (C) 2003       Pavel Minayev <thalion@front.ru>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #include "config.h"
22 
23 #ifdef ENABLE_FLAC
24 
25 #include <glib/gi18n.h>
26 #include <errno.h>
27 
28 #include "flac_private.h"
29 #include "flac_tag.h"
30 #include "vcedit.h"
31 #include "et_core.h"
32 #include "id3_tag.h"
33 #include "misc.h"
34 #include "setting.h"
35 #include "picture.h"
36 #include "charset.h"
37 
38 #define MULTIFIELD_SEPARATOR " - "
39 
40 /*
41  * validate_field_utf8:
42  * @field_value: the string to validate
43  * @field_len: the length of the string
44  *
45  * Validate a Vorbis comment field to ensure that it is UTF-8. Either return a
46  * duplicate of the original (valid) string, or a converted equivalent (of an
47  * invalid UTF-8 string).
48  *
49  * Returns: a valid UTF-8 represenation of @field_value
50  */
51 static gchar *
validate_field_utf8(const gchar * field_value,guint32 field_len)52 validate_field_utf8 (const gchar *field_value,
53                      guint32 field_len)
54 {
55     gchar *result;
56 
57     if (g_utf8_validate (field_value, field_len, NULL))
58     {
59         result = g_strndup (field_value, field_len);
60     }
61     else
62     {
63         gchar *field_value_tmp = g_strndup (field_value,
64                                             field_len);
65         /* Unnecessarily validates the field again, but this should not be the
66          * common case. */
67         result = Try_To_Validate_Utf8_String (field_value_tmp);
68         g_free (field_value_tmp);
69     }
70 
71     return result;
72 }
73 
74 /*
75  * set_or_append_field:
76  * @field: (inout): pointer to a location in which to store the field value
77  * @field_value: (transfer full): the string to store in @field
78  *
79  * Set @field to @field_value if @field is empty, otherwise append to it.
80  * Ownership of @field_value is transferred to this function.
81  */
82 static void
set_or_append_field(gchar ** field,gchar * field_value)83 set_or_append_field (gchar **field,
84                      gchar *field_value)
85 {
86     if (*field == NULL)
87     {
88         *field = field_value;
89     }
90     else
91     {
92         gchar *field_tmp = g_strconcat (*field, MULTIFIELD_SEPARATOR,
93                                         field_value, NULL);
94         g_free (*field);
95         *field = field_tmp;
96         g_free (field_value);
97     }
98 }
99 
100 /*
101  * populate_tag_hash_table:
102  * @vc: a Vorbis comment block from which to read fields
103  *
104  * Add comments from the supplied @vc block to a newly-allocated hash table.
105  * Normalise the field names to upper-case ASCII, taking care to ignore the
106  * current locale. Validate the field values are UTF-8 before inserting them in
107  * the hash table. Add the values as strings in a GSList.
108  *
109  * Returns: (transfer full): a newly-allocated hash table of tags
110  */
111 static GHashTable *
populate_tag_hash_table(const FLAC__StreamMetadata_VorbisComment * vc)112 populate_tag_hash_table (const FLAC__StreamMetadata_VorbisComment *vc)
113 {
114     GHashTable *ret;
115     guint32 i;
116 
117     /* Free the string lists manually, to avoid having to duplicate them each
118      * time that an existing key is inserted. */
119     ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
120 
121     for (i = 0; i < vc->num_comments; i++)
122     {
123         const FLAC__StreamMetadata_VorbisComment_Entry comment = vc->comments[i];
124         const gchar *separator;
125         gchar *field;
126         gchar *field_up;
127         gchar *value;
128         /* TODO: Use a GPtrArray instead? */
129         GSList *l;
130 
131         separator = memchr (comment.entry, '=', comment.length);
132 
133         if (!separator)
134         {
135             g_warning ("Field separator not found when reading FLAC tag: %s",
136                        vc->comments[i].entry);
137             continue;
138         }
139 
140         field = g_strndup ((const gchar *)comment.entry,
141                            separator - (const gchar *)comment.entry);
142         field_up = g_ascii_strup (field, -1);
143         g_free (field);
144 
145         l = g_hash_table_lookup (ret, field_up);
146 
147         /* If the lookup failed, a new list is created. The list takes
148          * ownership of the field value. */
149         value = validate_field_utf8 (separator + 1,
150                                      comment.length - ((separator + 1)
151                                                        - (const gchar *)comment.entry));
152 
153         /* Appending is slower, but much easier here (and the lists should be
154          * short). */
155         l = g_slist_append (l, value);
156 
157         g_hash_table_insert (ret, field_up, l);
158     }
159 
160     return ret;
161 }
162 
163 /*
164  * values_list_foreach:
165  * @data: (transfer full): the tag value
166  * @user_data: (inout): a tag location to fill
167  *
168  * Called on each element in a GSList of tag values, to set or append the
169  * string to the tag.
170  */
171 static void
values_list_foreach(gpointer data,gpointer user_data)172 values_list_foreach (gpointer data,
173                      gpointer user_data)
174 {
175     gchar *value = (gchar *)data;
176     gchar **tag = (gchar **)user_data;
177 
178     if (!et_str_empty (value))
179     {
180         set_or_append_field (tag, value);
181     }
182 }
183 
184 /*
185  * Read tag data from a FLAC file using the level 2 flac interface,
186  * Note:
187  *  - if field is found but contains no info (strlen(str)==0), we don't read it
188  */
189 gboolean
flac_tag_read_file_tag(GFile * file,File_Tag * FileTag,GError ** error)190 flac_tag_read_file_tag (GFile *file,
191                         File_Tag *FileTag,
192                         GError **error)
193 {
194     FLAC__Metadata_Chain *chain;
195     EtFlacReadState state;
196     FLAC__IOCallbacks callbacks = { et_flac_read_func,
197                                     NULL, /* Do not set a write callback. */
198                                     et_flac_seek_func, et_flac_tell_func,
199                                     et_flac_eof_func,
200                                     et_flac_read_close_func };
201     FLAC__Metadata_Iterator *iter;
202 
203     EtPicture *prev_pic = NULL;
204 
205     g_return_val_if_fail (file != NULL && FileTag != NULL, FALSE);
206     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
207 
208     chain = FLAC__metadata_chain_new ();
209 
210     if (chain == NULL)
211     {
212         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM, "%s",
213                      g_strerror (ENOMEM));
214         return FALSE;
215     }
216 
217     state.error = NULL;
218     state.istream = g_file_read (file, NULL, &state.error);
219     state.seekable = G_SEEKABLE (state.istream);
220 
221     if (!FLAC__metadata_chain_read_with_callbacks (chain, &state, callbacks))
222     {
223         /* TODO: Provide a dedicated error enum corresponding to status. */
224         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s",
225                      _("Error opening FLAC file"));
226         et_flac_read_close_func (&state);
227 
228         return FALSE;
229     }
230 
231     iter = FLAC__metadata_iterator_new ();
232 
233     if (iter == NULL)
234     {
235         et_flac_read_close_func (&state);
236         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM, "%s",
237                      g_strerror (ENOMEM));
238         return FALSE;
239     }
240 
241     FLAC__metadata_iterator_init (iter, chain);
242 
243     while (FLAC__metadata_iterator_next (iter))
244     {
245         FLAC__StreamMetadata *block;
246 
247         block = FLAC__metadata_iterator_get_block (iter);
248 
249         if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
250         {
251             const FLAC__StreamMetadata_VorbisComment *vc;
252             GHashTable *tags;
253             GSList *strings;
254             GHashTableIter tags_iter;
255             gchar *key;
256 
257             /* Get comments from block. */
258             vc = &block->data.vorbis_comment;
259             tags = populate_tag_hash_table (vc);
260 
261             /* Title */
262             if ((strings = g_hash_table_lookup (tags,
263                                                 ET_VORBIS_COMMENT_FIELD_TITLE)))
264             {
265                 g_slist_foreach (strings, values_list_foreach,
266                                  &FileTag->title);
267                 g_slist_free (strings);
268                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_TITLE);
269             }
270 
271             /* Artist */
272             if ((strings = g_hash_table_lookup (tags,
273                                                 ET_VORBIS_COMMENT_FIELD_ARTIST)))
274             {
275                 g_slist_foreach (strings, values_list_foreach,
276                                  &FileTag->artist);
277                 g_slist_free (strings);
278                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ARTIST);
279             }
280 
281             /* Album artist. */
282             if ((strings = g_hash_table_lookup (tags,
283                                                 ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST)))
284             {
285                 g_slist_foreach (strings, values_list_foreach,
286                                  &FileTag->album_artist);
287                 g_slist_free (strings);
288                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST);
289             }
290 
291             /* Album. */
292             if ((strings = g_hash_table_lookup (tags,
293                                                 ET_VORBIS_COMMENT_FIELD_ALBUM)))
294             {
295                 g_slist_foreach (strings, values_list_foreach,
296                                  &FileTag->album);
297                 g_slist_free (strings);
298                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ALBUM);
299             }
300 
301             /* Disc number and total discs. */
302             if ((strings = g_hash_table_lookup (tags,
303                                                 ET_VORBIS_COMMENT_FIELD_DISC_TOTAL)))
304             {
305                 /* Only take values from the first total discs field. */
306                 if (!et_str_empty (strings->data))
307                 {
308                     FileTag->disc_total = et_disc_number_to_string (atoi (strings->data));
309                 }
310 
311                 g_slist_free_full (strings, g_free);
312                 g_hash_table_remove (tags,
313                                      ET_VORBIS_COMMENT_FIELD_DISC_TOTAL);
314             }
315 
316             if ((strings = g_hash_table_lookup (tags,
317                                                 ET_VORBIS_COMMENT_FIELD_DISC_NUMBER)))
318             {
319                 /* Only take values from the first disc number field. */
320                 if (!et_str_empty (strings->data))
321                 {
322                     gchar *separator;
323 
324                     separator = strchr (strings->data, '/');
325 
326                     if (separator && !FileTag->disc_total)
327                     {
328                         FileTag->disc_total = et_disc_number_to_string (atoi (separator + 1));
329                         *separator = '\0';
330                     }
331 
332                     FileTag->disc_number = et_disc_number_to_string (atoi (strings->data));
333                 }
334 
335                 g_slist_free_full (strings, g_free);
336                 g_hash_table_remove (tags,
337                                      ET_VORBIS_COMMENT_FIELD_DISC_NUMBER);
338             }
339 
340             /* Track number and total tracks. */
341             if ((strings = g_hash_table_lookup (tags,
342                                                 ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL)))
343             {
344                 /* Only take values from the first total tracks field. */
345                 if (!et_str_empty (strings->data))
346                 {
347                     FileTag->track_total = et_track_number_to_string (atoi (strings->data));
348                 }
349 
350                 g_slist_free_full (strings, g_free);
351                 g_hash_table_remove (tags,
352                                      ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL);
353             }
354 
355             if ((strings = g_hash_table_lookup (tags,
356                                                 ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER)))
357             {
358                 /* Only take values from the first track number field. */
359                 if (!et_str_empty (strings->data))
360                 {
361                     gchar *separator;
362 
363                     separator = strchr (strings->data, '/');
364 
365                     if (separator && !FileTag->track_total)
366                     {
367                         FileTag->track_total = et_track_number_to_string (atoi (separator + 1));
368                         *separator = '\0';
369                     }
370 
371                     FileTag->track = et_track_number_to_string (atoi (strings->data));
372                 }
373 
374                 g_slist_free_full (strings, g_free);
375                 g_hash_table_remove (tags,
376                                      ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER);
377             }
378 
379             /* Year. */
380             if ((strings = g_hash_table_lookup (tags,
381                                                 ET_VORBIS_COMMENT_FIELD_DATE)))
382             {
383                 g_slist_foreach (strings, values_list_foreach,
384                                  &FileTag->year);
385                 g_slist_free (strings);
386                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DATE);
387             }
388 
389             /* Genre. */
390             if ((strings = g_hash_table_lookup (tags,
391                                                 ET_VORBIS_COMMENT_FIELD_GENRE)))
392             {
393                 g_slist_foreach (strings, values_list_foreach,
394                                  &FileTag->genre);
395                 g_slist_free (strings);
396                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_GENRE);
397             }
398 
399             /* Comment. */
400             {
401                 GSList *descs;
402                 GSList *comments;
403 
404                 descs = g_hash_table_lookup (tags,
405                                              ET_VORBIS_COMMENT_FIELD_DESCRIPTION);
406                 comments = g_hash_table_lookup (tags,
407                                                 ET_VORBIS_COMMENT_FIELD_COMMENT);
408 
409                 /* Prefer DESCRIPTION, as it is part of the spec. */
410                 if (descs && !comments)
411                 {
412                     g_slist_foreach (descs, values_list_foreach,
413                                      &FileTag->comment);
414                 }
415                 else if (descs && comments)
416                 {
417                     /* Mark the file as modified, so that comments are written
418                      * to the DESCRIPTION field on saving. */
419                     FileTag->saved = FALSE;
420 
421                     g_slist_foreach (descs, values_list_foreach,
422                                      &FileTag->comment);
423                     g_slist_foreach (comments, values_list_foreach,
424                                      &FileTag->comment);
425                 }
426                 else if (comments)
427                 {
428                     FileTag->saved = FALSE;
429 
430                     g_slist_foreach (comments, values_list_foreach,
431                                      &FileTag->comment);
432                 }
433 
434                 g_slist_free (descs);
435                 g_slist_free (comments);
436                 g_hash_table_remove (tags,
437                                      ET_VORBIS_COMMENT_FIELD_DESCRIPTION);
438                 g_hash_table_remove (tags,
439                                      ET_VORBIS_COMMENT_FIELD_COMMENT);
440             }
441 
442             /* Composer. */
443             if ((strings = g_hash_table_lookup (tags,
444                                                 ET_VORBIS_COMMENT_FIELD_COMPOSER)))
445             {
446                 g_slist_foreach (strings, values_list_foreach,
447                                  &FileTag->composer);
448                 g_slist_free (strings);
449                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COMPOSER);
450             }
451 
452             /* Original artist. */
453             if ((strings = g_hash_table_lookup (tags,
454                                                 ET_VORBIS_COMMENT_FIELD_PERFORMER)))
455             {
456                 g_slist_foreach (strings, values_list_foreach,
457                                  &FileTag->orig_artist);
458                 g_slist_free (strings);
459                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_PERFORMER);
460             }
461 
462             /* Copyright. */
463             if ((strings = g_hash_table_lookup (tags,
464                                                 ET_VORBIS_COMMENT_FIELD_COPYRIGHT)))
465             {
466                 g_slist_foreach (strings, values_list_foreach,
467                                  &FileTag->copyright);
468                 g_slist_free (strings);
469                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COPYRIGHT);
470             }
471 
472             /* URL. */
473             if ((strings = g_hash_table_lookup (tags,
474                                                 ET_VORBIS_COMMENT_FIELD_CONTACT)))
475             {
476                 g_slist_foreach (strings, values_list_foreach,
477                                  &FileTag->url);
478                 g_slist_free (strings);
479                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_CONTACT);
480             }
481 
482             /* Encoded by. */
483             if ((strings = g_hash_table_lookup (tags,
484                                                 ET_VORBIS_COMMENT_FIELD_ENCODED_BY)))
485             {
486                 g_slist_foreach (strings, values_list_foreach,
487                                  &FileTag->encoded_by);
488                 g_slist_free (strings);
489                 g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ENCODED_BY);
490             }
491 
492             /* Save unsupported fields. */
493             g_hash_table_iter_init (&tags_iter, tags);
494 
495             while (g_hash_table_iter_next (&tags_iter, (gpointer *)&key,
496                                            (gpointer *)&strings))
497             {
498                 GSList *l;
499 
500                 for (l = strings; l != NULL; l = g_slist_next (l))
501                 {
502                     FileTag->other = g_list_prepend (FileTag->other,
503                                                      g_strconcat (key,
504                                                                   "=",
505                                                                   l->data,
506                                                                   NULL));
507                 }
508 
509                 g_slist_free_full (strings, g_free);
510                 g_hash_table_iter_remove (&tags_iter);
511             }
512 
513             if (FileTag->other)
514             {
515                 FileTag->other = g_list_reverse (FileTag->other);
516             }
517 
518             /* The hash table should now only contain keys. */
519             g_hash_table_unref (tags);
520         }
521         else if (block->type == FLAC__METADATA_TYPE_PICTURE)
522         {
523             /* Picture. */
524             const FLAC__StreamMetadata_Picture *p;
525             GBytes *bytes;
526             EtPicture *pic;
527 
528             /* Get picture data from block. */
529             p = &block->data.picture;
530 
531             bytes = g_bytes_new (p->data, p->data_length);
532 
533             pic = et_picture_new (p->type, (const gchar *)p->description,
534                                   0, 0, bytes);
535             g_bytes_unref (bytes);
536 
537             if (!prev_pic)
538             {
539                 FileTag->picture = pic;
540             }
541             else
542             {
543                 prev_pic->next = pic;
544             }
545 
546             prev_pic = pic;
547         }
548     }
549 
550     FLAC__metadata_iterator_delete (iter);
551     FLAC__metadata_chain_delete (chain);
552     et_flac_read_close_func (&state);
553 
554 #ifdef ENABLE_MP3
555     /* If no FLAC vorbis tag found : we try to get the ID3 tag if it exists
556      * (but it will be deleted when rewriting the tag) */
557     if ( FileTag->title       == NULL
558       && FileTag->artist      == NULL
559       && FileTag->album_artist == NULL
560       && FileTag->album       == NULL
561       && FileTag->disc_number == NULL
562       && FileTag->disc_total == NULL
563       && FileTag->year        == NULL
564       && FileTag->track       == NULL
565       && FileTag->track_total == NULL
566       && FileTag->genre       == NULL
567       && FileTag->comment     == NULL
568       && FileTag->composer    == NULL
569       && FileTag->orig_artist == NULL
570       && FileTag->copyright   == NULL
571       && FileTag->url         == NULL
572       && FileTag->encoded_by  == NULL
573       && FileTag->picture     == NULL)
574     {
575         id3tag_read_file_tag (file, FileTag, NULL);
576 
577         // If an ID3 tag has been found (and no FLAC tag), we mark the file as
578         // unsaved to rewrite a flac tag.
579         if ( FileTag->title       != NULL
580           || FileTag->artist      != NULL
581           || FileTag->album_artist != NULL
582           || FileTag->album       != NULL
583           || FileTag->disc_number != NULL
584           || FileTag->disc_total != NULL
585           || FileTag->year        != NULL
586           || FileTag->track       != NULL
587           || FileTag->track_total != NULL
588           || FileTag->genre       != NULL
589           || FileTag->comment     != NULL
590           || FileTag->composer    != NULL
591           || FileTag->orig_artist != NULL
592           || FileTag->copyright   != NULL
593           || FileTag->url         != NULL
594           || FileTag->encoded_by  != NULL
595           || FileTag->picture     != NULL)
596         {
597             FileTag->saved = FALSE;
598         }
599     }
600 #endif
601 
602     return TRUE;
603 }
604 
605 /*
606  * vc_block_append_other_tag:
607  * @vc_block: the Vorbis comment in which to add the tag
608  * @tag: the name and value of the tag
609  *
610  * Append the unsupported (not shown in the UI) @tag to @vc_block.
611  */
612 static void
vc_block_append_other_tag(FLAC__StreamMetadata * vc_block,const gchar * tag)613 vc_block_append_other_tag (FLAC__StreamMetadata *vc_block,
614                            const gchar *tag)
615 {
616     FLAC__StreamMetadata_VorbisComment_Entry field;
617 
618     field.entry = (FLAC__byte *)tag;
619     field.length = strlen (tag);
620 
621     /* Safe to pass const data, if the last argument (copy) is true, according
622      * to the FLAC API reference. */
623     if (!FLAC__metadata_object_vorbiscomment_append_comment (vc_block, field,
624                                                              true))
625     {
626         g_critical ("Invalid Vorbis comment, or memory allocation failed, when writing other FLAC tag '%s'",
627                     tag);
628     }
629 }
630 
631 /*
632  * vc_block_append_single_tag:
633  * @vc_block: the Vorbis comment in which to add the tag
634  * @tag_name: the name of the tag
635  * @value: the value of the tag
636  *
637  * Save field value in a single tag.
638  */
639 static void
vc_block_append_single_tag(FLAC__StreamMetadata * vc_block,const gchar * tag_name,const gchar * value)640 vc_block_append_single_tag (FLAC__StreamMetadata *vc_block,
641                             const gchar *tag_name,
642                             const gchar *value)
643 {
644     FLAC__StreamMetadata_VorbisComment_Entry field;
645 
646     if (!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair (&field,
647                                                                          tag_name,
648                                                                          value))
649     {
650         g_critical ("Invalid Vorbis comment, or memory allocation failed, when creating FLAC entry from tag name '%s' and value '%s'",
651                     tag_name, value);
652         return;
653     }
654 
655     if (!FLAC__metadata_object_vorbiscomment_append_comment (vc_block, field,
656                                                              false))
657     {
658         g_critical ("Invalid Vorbis comment, or memory allocation failed, when writing FLAC tag '%s' with value '%s'",
659                     tag_name, value);
660     }
661 }
662 
663 /*
664  * vc_block_append_multiple_tags:
665  * @vc_block: the Vorbis comment block to which tags should be appended
666  * @tag_name: the name of the tag
667  * @values: the values of the tag
668  *
669  * Append multiple copies of the supplied @tag_name to @vc_block, splitting
670  * @values at %MULTIFIELD_SEPARATOR.
671  */
672 static void
vc_block_append_multiple_tags(FLAC__StreamMetadata * vc_block,const gchar * tag_name,const gchar * values)673 vc_block_append_multiple_tags (FLAC__StreamMetadata *vc_block,
674                                const gchar *tag_name,
675                                const gchar *values)
676 {
677     gchar **strings = g_strsplit (values, MULTIFIELD_SEPARATOR, 255);
678     guint i;
679     guint len;
680 
681     for (i = 0, len = g_strv_length (strings); i < len; i++)
682     {
683         if (!et_str_empty (strings[i]))
684         {
685             vc_block_append_single_tag (vc_block, tag_name, strings[i]);
686         }
687     }
688 
689     g_strfreev (strings);
690 }
691 
692 /*
693  * vc_block_append_tag:
694  * @vc_block: the Vorbis comment block to which a tag should be appended
695  * @tag_name: the name of the tag, including the trailing '=', or the empty
696  *            string (if @value is to be taken as the combination of the tag
697  *            name and value)
698  * @value: the value of the tag
699  * @split: %TRUE to split @value into multiple tags at %MULTIFIELD_SEPARATOR,
700  *         %FALSE to keep the tag value unchanged
701  *
702  * Append the supplied @tag_name and @value to @vc_block, optionally splitting
703  * @value if @split is %TRUE.
704  */
705 static void
vc_block_append_tag(FLAC__StreamMetadata * vc_block,const gchar * tag_name,const gchar * value,gboolean split)706 vc_block_append_tag (FLAC__StreamMetadata *vc_block,
707                      const gchar *tag_name,
708                      const gchar *value,
709                      gboolean split)
710 {
711     if (value && split)
712     {
713         vc_block_append_multiple_tags (vc_block, tag_name, value);
714     }
715     else if (value)
716     {
717         vc_block_append_single_tag (vc_block, tag_name, value);
718     }
719 }
720 
721 /*
722  * Write Flac tag, using the level 2 flac interface
723  */
724 gboolean
flac_tag_write_file_tag(const ET_File * ETFile,GError ** error)725 flac_tag_write_file_tag (const ET_File *ETFile,
726                          GError **error)
727 {
728     const File_Tag *FileTag;
729     GFile *file;
730     GFileIOStream *iostream;
731     EtFlacWriteState state;
732     FLAC__IOCallbacks callbacks = { et_flac_read_func, et_flac_write_func,
733                                     et_flac_seek_func, et_flac_tell_func,
734                                     et_flac_eof_func,
735                                     et_flac_write_close_func };
736     const gchar *filename;
737     const gchar *filename_utf8;
738     const gchar *flac_error_msg;
739     FLAC__Metadata_Chain *chain;
740     FLAC__Metadata_Iterator *iter;
741     FLAC__StreamMetadata_VorbisComment_Entry vce_field_vendor_string; // To save vendor string
742     gboolean vce_field_vendor_string_found = FALSE;
743 
744     g_return_val_if_fail (ETFile != NULL && ETFile->FileTag != NULL, FALSE);
745     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
746 
747     FileTag       = (File_Tag *)ETFile->FileTag->data;
748     filename      = ((File_Name *)ETFile->FileNameCur->data)->value;
749     filename_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8;
750 
751     /* libFLAC is able to detect (and skip) ID3v2 tags by itself */
752 
753     /* Create a new chain instance to get all blocks in one time. */
754     chain = FLAC__metadata_chain_new ();
755 
756     if (chain == NULL)
757     {
758         flac_error_msg = FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR];
759 
760         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
761                      _("Error while opening file ‘%s’ as FLAC: %s"),
762                      filename_utf8, flac_error_msg);
763         return FALSE;
764     }
765 
766     file = g_file_new_for_path (filename);
767 
768     state.file = file;
769     state.error = NULL;
770     /* TODO: Fallback to an in-memory copy of the file for non-local files,
771      * where creation of the GFileIOStream may fail. */
772     iostream = g_file_open_readwrite (file, NULL, &state.error);
773 
774     if (iostream == NULL)
775     {
776         FLAC__metadata_chain_delete (chain);
777         g_propagate_error (error, state.error);
778         g_object_unref (file);
779         return FALSE;
780     }
781 
782     state.istream = G_FILE_INPUT_STREAM (g_io_stream_get_input_stream (G_IO_STREAM (iostream)));
783     state.ostream = G_FILE_OUTPUT_STREAM (g_io_stream_get_output_stream (G_IO_STREAM (iostream)));
784     state.seekable = G_SEEKABLE (iostream);
785     state.iostream = iostream;
786 
787     if (!FLAC__metadata_chain_read_with_callbacks (chain, &state, callbacks))
788     {
789         const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain);
790         flac_error_msg = FLAC__Metadata_ChainStatusString[status];
791         FLAC__metadata_chain_delete (chain);
792 
793         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
794                      _("Error while opening file ‘%s’ as FLAC: %s"),
795                      filename_utf8, flac_error_msg);
796         et_flac_write_close_func (&state);
797         return FALSE;
798     }
799 
800     /* Create a new iterator instance for the chain. */
801     iter = FLAC__metadata_iterator_new ();
802 
803     if (iter == NULL)
804     {
805         flac_error_msg = FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR];
806 
807         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
808                      _("Error while opening file ‘%s’ as FLAC: %s"),
809                      filename_utf8, flac_error_msg);
810         FLAC__metadata_chain_delete (chain);
811         et_flac_write_close_func (&state);
812         return FALSE;
813     }
814 
815     FLAC__metadata_iterator_init (iter, chain);
816 
817     while (FLAC__metadata_iterator_next (iter))
818     {
819         const FLAC__MetadataType block_type = FLAC__metadata_iterator_get_block_type (iter);
820 
821         /* TODO: Modify the blocks directly, rather than deleting and
822          * recreating. */
823         if (block_type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
824         {
825             // Delete the VORBIS_COMMENT block and convert to padding. But before, save the original vendor string.
826             /* Get block data. */
827             FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iter);
828             FLAC__StreamMetadata_VorbisComment *vc = &block->data.vorbis_comment;
829 
830             if (vc->vendor_string.entry != NULL)
831             {
832                 // Get initial vendor string, to don't alterate it by FLAC__VENDOR_STRING when saving file
833                 vce_field_vendor_string.entry = (FLAC__byte *)g_strdup ((gchar *)vc->vendor_string.entry);
834                 vce_field_vendor_string.length = strlen ((gchar *)vce_field_vendor_string.entry);
835                 vce_field_vendor_string_found = TRUE;
836             }
837 
838             /* Free block data. */
839             FLAC__metadata_iterator_delete_block (iter, true);
840         }
841         else if (block_type == FLAC__METADATA_TYPE_PICTURE)
842         {
843             /* Delete all the PICTURE blocks, and convert to padding. */
844             FLAC__metadata_iterator_delete_block (iter, true);
845         }
846     }
847 
848 
849     //
850     // Create and insert a new VORBISCOMMENT block
851     //
852     {
853         FLAC__StreamMetadata *vc_block; // For vorbis comments
854         GList *l;
855 
856         // Allocate a block for Vorbis comments
857         vc_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
858 
859         // Set the original vendor string, else will be use the version of library
860         if (vce_field_vendor_string_found)
861         {
862             // must set 'copy' param to false, because the API will reuse the  pointer of an empty
863             // string (yet still return 'true', indicating it was copied); the string is free'd during
864             // metadata_chain_delete routine
865             FLAC__metadata_object_vorbiscomment_set_vendor_string(vc_block, vce_field_vendor_string, /*copy=*/false);
866         }
867 
868 
869         /*********
870          * Title *
871          *********/
872         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_TITLE,
873                              FileTag->title,
874                              g_settings_get_boolean (MainSettings,
875                                                      "ogg-split-title"));
876 
877         /**********
878          * Artist *
879          **********/
880         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_ARTIST,
881                              FileTag->artist,
882                              g_settings_get_boolean (MainSettings,
883                                                      "ogg-split-artist"));
884 
885         /****************
886          * Album Artist *
887          ****************/
888         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST,
889                              FileTag->album_artist,
890                              g_settings_get_boolean (MainSettings,
891                                                      "ogg-split-artist"));
892 
893         /*********
894          * Album *
895          *********/
896         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_ALBUM,
897                              FileTag->album,
898                              g_settings_get_boolean (MainSettings,
899                                                      "ogg-split-album"));
900 
901         /******************************
902          * Disc Number and Disc Total *
903          ******************************/
904         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_DISC_NUMBER,
905                              FileTag->disc_number, FALSE);
906         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_DISC_TOTAL,
907                              FileTag->disc_total, FALSE);
908 
909         /********
910          * Year *
911          ********/
912         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_DATE,
913                              FileTag->year, FALSE);
914 
915         /*************************
916          * Track and Total Track *
917          *************************/
918         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER,
919                              FileTag->track, FALSE);
920         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL,
921                              FileTag->track_total, FALSE);
922 
923         /*********
924          * Genre *
925          *********/
926         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_GENRE,
927                              FileTag->genre,
928                              g_settings_get_boolean (MainSettings,
929                                                      "ogg-split-genre"));
930 
931         /***********
932          * Comment *
933          ***********/
934         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_DESCRIPTION,
935                              FileTag->comment,
936                              g_settings_get_boolean (MainSettings,
937                                                      "ogg-split-comment"));
938 
939         /************
940          * Composer *
941          ************/
942         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_COMPOSER,
943                              FileTag->composer,
944                              g_settings_get_boolean (MainSettings,
945                                                      "ogg-split-composer"));
946 
947         /*******************
948          * Original artist *
949          *******************/
950         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_PERFORMER,
951                              FileTag->orig_artist,
952                              g_settings_get_boolean (MainSettings,
953                                                      "ogg-split-original-artist"));
954 
955         /*************
956          * Copyright *
957          *************/
958         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_COPYRIGHT,
959                              FileTag->copyright, FALSE);
960 
961         /*******
962          * URL *
963          *******/
964         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_CONTACT,
965                              FileTag->url, FALSE);
966 
967         /**************
968          * Encoded by *
969          **************/
970         vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_ENCODED_BY,
971                              FileTag->encoded_by, FALSE);
972 
973 
974         /**************************
975          * Set unsupported fields *
976          **************************/
977         for (l = FileTag->other; l != NULL; l = g_list_next (l))
978         {
979             vc_block_append_other_tag (vc_block, (gchar *)l->data);
980         }
981 
982         // Add the block to the the chain (so we don't need to free the block)
983         FLAC__metadata_iterator_insert_block_after(iter, vc_block);
984     }
985 
986 
987 
988     //
989     // Create and insert PICTURE blocks
990     //
991 
992     /***********
993      * Picture *
994      ***********/
995     {
996         EtPicture *pic = FileTag->picture;
997 
998         while (pic)
999         {
1000             /* TODO: Can this ever be NULL? */
1001             if (pic->bytes)
1002             {
1003                 const gchar *violation;
1004                 FLAC__StreamMetadata *picture_block; // For picture data
1005                 Picture_Format format;
1006                 gconstpointer data;
1007                 gsize data_size;
1008 
1009                 // Allocate block for picture data
1010                 picture_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE);
1011 
1012                 // Type
1013                 picture_block->data.picture.type = pic->type;
1014 
1015                 // Mime type
1016                 format = Picture_Format_From_Data(pic);
1017                 /* Safe to pass a const string, according to the FLAC API
1018                  * reference. */
1019                 FLAC__metadata_object_picture_set_mime_type(picture_block, (gchar *)Picture_Mime_Type_String(format), TRUE);
1020 
1021                 // Description
1022                 if (pic->description)
1023                 {
1024                     FLAC__metadata_object_picture_set_description(picture_block, (FLAC__byte *)pic->description, TRUE);
1025                 }
1026 
1027                 // Resolution
1028                 picture_block->data.picture.width  = pic->width;
1029                 picture_block->data.picture.height = pic->height;
1030                 picture_block->data.picture.depth  = 0;
1031 
1032                 /* Picture data. */
1033                 data = g_bytes_get_data (pic->bytes, &data_size);
1034                 /* Safe to pass const data, if the last argument (copy) is
1035                  * TRUE, according the the FLAC API reference. */
1036                 FLAC__metadata_object_picture_set_data (picture_block,
1037                                                         (FLAC__byte *)data,
1038                                                         (FLAC__uint32)data_size,
1039                                                         true);
1040 
1041                 if (!FLAC__metadata_object_picture_is_legal (picture_block,
1042                                                              &violation))
1043                 {
1044                     g_critical ("Created an invalid picture block: ‘%s’",
1045                                 violation);
1046                     FLAC__metadata_object_delete (picture_block);
1047                 }
1048                 else
1049                 {
1050                     // Add the block to the the chain (so we don't need to free the block)
1051                     FLAC__metadata_iterator_insert_block_after(iter, picture_block);
1052                 }
1053             }
1054 
1055             pic = pic->next;
1056         }
1057     }
1058 
1059     FLAC__metadata_iterator_delete (iter);
1060 
1061     //
1062     // Prepare for writing tag
1063     //
1064 
1065     FLAC__metadata_chain_sort_padding (chain);
1066 
1067     /* Write tag. */
1068     if (FLAC__metadata_chain_check_if_tempfile_needed (chain, true))
1069     {
1070         EtFlacWriteState temp_state;
1071         GFile *temp_file;
1072         GFileIOStream *temp_iostream;
1073         GError *temp_error = NULL;
1074 
1075         temp_file = g_file_new_tmp ("easytag-XXXXXX", &temp_iostream,
1076                                     &temp_error);
1077 
1078         if (temp_file == NULL)
1079         {
1080             FLAC__metadata_chain_delete (chain);
1081             g_propagate_error (error, temp_error);
1082             et_flac_write_close_func (&state);
1083             return FALSE;
1084         }
1085 
1086         temp_state.file = temp_file;
1087         temp_state.error = NULL;
1088         temp_state.istream = G_FILE_INPUT_STREAM (g_io_stream_get_input_stream (G_IO_STREAM (temp_iostream)));
1089         temp_state.ostream = G_FILE_OUTPUT_STREAM (g_io_stream_get_output_stream (G_IO_STREAM (temp_iostream)));
1090         temp_state.seekable = G_SEEKABLE (temp_iostream);
1091         temp_state.iostream = temp_iostream;
1092 
1093         if (!FLAC__metadata_chain_write_with_callbacks_and_tempfile (chain,
1094                                                                      true,
1095                                                                      &state,
1096                                                                      callbacks,
1097                                                                      &temp_state,
1098                                                                      callbacks))
1099         {
1100             const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain);
1101             flac_error_msg = FLAC__Metadata_ChainStatusString[status];
1102 
1103             FLAC__metadata_chain_delete (chain);
1104             et_flac_write_close_func (&temp_state);
1105             et_flac_write_close_func (&state);
1106 
1107             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
1108                          _("Failed to write comments to file ‘%s’: %s"),
1109                          filename_utf8, flac_error_msg);
1110             return FALSE;
1111         }
1112 
1113         if (!g_file_move (temp_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL,
1114                           NULL, &state.error))
1115         {
1116             FLAC__metadata_chain_delete (chain);
1117             et_flac_write_close_func (&temp_state);
1118 
1119             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
1120                          _("Failed to write comments to file ‘%s’: %s"),
1121                          filename_utf8, state.error->message);
1122             et_flac_write_close_func (&state);
1123             return FALSE;
1124         }
1125 
1126         et_flac_write_close_func (&temp_state);
1127     }
1128     else
1129     {
1130         if (!FLAC__metadata_chain_write_with_callbacks (chain, true, &state,
1131                                                         callbacks))
1132         {
1133             const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain);
1134             flac_error_msg = FLAC__Metadata_ChainStatusString[status];
1135 
1136             FLAC__metadata_chain_delete (chain);
1137             et_flac_write_close_func (&state);
1138 
1139             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
1140                          _("Failed to write comments to file ‘%s’: %s"),
1141                          filename_utf8, flac_error_msg);
1142             return FALSE;
1143         }
1144     }
1145 
1146     FLAC__metadata_chain_delete (chain);
1147     et_flac_write_close_func (&state);
1148 
1149 #ifdef ENABLE_MP3
1150     {
1151         // Delete the ID3 tags (create a dummy ETFile for the Id3tag_... function)
1152         ET_File   *ETFile_tmp    = ET_File_Item_New();
1153         File_Name *FileName_tmp = et_file_name_new ();
1154         File_Tag  *FileTag_tmp = et_file_tag_new ();
1155         // Same file...
1156         FileName_tmp->value      = g_strdup(filename);
1157         FileName_tmp->value_utf8 = g_strdup(filename_utf8); // Not necessary to fill 'value_ck'
1158         ETFile_tmp->FileNameList = g_list_append(NULL,FileName_tmp);
1159         ETFile_tmp->FileNameCur  = ETFile_tmp->FileNameList;
1160         // With empty tag...
1161         ETFile_tmp->FileTagList  = g_list_append(NULL,FileTag_tmp);
1162         ETFile_tmp->FileTag      = ETFile_tmp->FileTagList;
1163         id3tag_write_file_tag (ETFile_tmp, NULL);
1164         ET_Free_File_List_Item(ETFile_tmp);
1165     }
1166 #endif
1167 
1168     return TRUE;
1169 }
1170 
1171 
1172 #endif /* ENABLE_FLAC */
1173