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