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