1 /* EasyTAG - Tag editor for MP3 and Ogg Vorbis files
2  * Copyright (C) 2012-1014  David King <amigadave@amigadave.com>
3  * Copyright (C) 2001-2005  Jerome Couderc <easytag@gmail.com>
4  * Copyright (C) 2005  Michael Ihde <mike.ihde@randomwalking.com>
5  * Copyright (C) 2005  Stewart Whitman <swhitman@cox.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #include "config.h" // For definition of ENABLE_MP4
23 
24 #ifdef ENABLE_MP4
25 
26 #include <glib/gi18n.h>
27 
28 #include "mp4_header.h"
29 #include "mp4_tag.h"
30 #include "picture.h"
31 #include "misc.h"
32 #include "et_core.h"
33 #include "charset.h"
34 #include "gio_wrapper.h"
35 
36 /* Shadow warning in public TagLib headers. */
37 #pragma GCC diagnostic push
38 #pragma GCC diagnostic ignored "-Wshadow"
39 #ifdef G_OS_WIN32
40 #undef NOMINMAX /* Warning in TagLib headers, fixed in git. */
41 #endif /* G_OS_WIN32 */
42 #include <mp4file.h>
43 #include <mp4tag.h>
44 #pragma GCC diagnostic pop
45 #include <tpropertymap.h>
46 
47 /* Include mp4_header.cc directly. */
48 #include "mp4_header.cc"
49 
50 /*
51  * Mp4_Tag_Read_File_Tag:
52  *
53  * Read tag data into an Mp4 file.
54  */
55 gboolean
mp4tag_read_file_tag(GFile * file,File_Tag * FileTag,GError ** error)56 mp4tag_read_file_tag (GFile *file,
57                       File_Tag *FileTag,
58                       GError **error)
59 {
60     TagLib::MP4::Tag *tag;
61     guint year;
62     TagLib::String str;
63 
64     g_return_val_if_fail (file != NULL && FileTag != NULL, FALSE);
65 
66     /* Get data from tag. */
67     GIO_InputStream stream (file);
68 
69     if (!stream.isOpen ())
70     {
71         const GError *tmp_error = stream.getError ();
72         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
73                      _("Error while opening file: %s"), tmp_error->message);
74         return FALSE;
75     }
76 
77     TagLib::MP4::File mp4file (&stream);
78 
79     if (!mp4file.isOpen ())
80     {
81         const GError *tmp_error = stream.getError ();
82 
83         if (tmp_error)
84         {
85             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
86                          _("Error while opening file: %s"),
87                          tmp_error->message);
88         }
89         else
90         {
91             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
92                          _("Error while opening file: %s"),
93                          _("MP4 format invalid"));
94         }
95 
96         return FALSE;
97     }
98 
99     if (!(tag = mp4file.tag ()))
100     {
101         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s",
102                      _("Error reading tags from file"));
103         return FALSE;
104     }
105 
106     /*********
107      * Title *
108      *********/
109     str = tag->title ();
110 
111     if (!str.isEmpty ())
112     {
113         et_file_tag_set_title (FileTag, str.toCString (true));
114     }
115 
116     /**********
117      * Artist *
118      **********/
119     str = tag->artist ();
120 
121     if (!str.isEmpty ())
122     {
123         et_file_tag_set_artist (FileTag, str.toCString (true));
124     }
125 
126     /*********
127      * Album *
128      *********/
129     str = tag->album ();
130 
131     if (!str.isEmpty ())
132     {
133         et_file_tag_set_album (FileTag, str.toCString (true));
134     }
135 
136     const TagLib::PropertyMap extra_tag = tag->properties ();
137 
138     /* Disc number. */
139     /* Total disc number support in TagLib reads multiple disc numbers and
140      * joins them with a "/". */
141     if (extra_tag.contains ("DISCNUMBER"))
142     {
143         const TagLib::StringList disc_numbers = extra_tag["DISCNUMBER"];
144         int offset = disc_numbers.front ().find ("/");
145 
146         if (offset != -1)
147         {
148             FileTag->disc_total = et_disc_number_to_string (disc_numbers.front ().substr (offset + 1).toInt ());
149         }
150 
151         FileTag->disc_number = et_disc_number_to_string (disc_numbers.front ().toInt ());
152     }
153 
154     /********
155      * Year *
156      ********/
157     year = tag->year ();
158 
159     if (year != 0)
160     {
161         FileTag->year = g_strdup_printf ("%u", year);
162     }
163 
164     /*************************
165      * Track and Total Track *
166      *************************/
167     if (extra_tag.contains ("TRACKNUMBER"))
168     {
169         const TagLib::StringList track_numbers = extra_tag["TRACKNUMBER"];
170         int offset = track_numbers.front ().find ("/");
171 
172         if (offset != -1)
173         {
174             FileTag->track_total = et_track_number_to_string (track_numbers.front ().substr (offset + 1).toInt ());
175         }
176 
177         FileTag->track = et_track_number_to_string (track_numbers.front ().toInt ());
178     }
179 
180     /*********
181      * Genre *
182      *********/
183     str = tag->genre ();
184 
185     if (!str.isEmpty ())
186     {
187         et_file_tag_set_genre (FileTag, str.toCString (true));
188     }
189 
190     /***********
191      * Comment *
192      ***********/
193     str = tag->comment ();
194 
195     if (!str.isEmpty ())
196     {
197         et_file_tag_set_comment (FileTag, str.toCString (true));
198     }
199 
200     /**********************
201      * Composer or Writer *
202      **********************/
203     if (extra_tag.contains ("COMPOSER"))
204     {
205         const TagLib::StringList composers = extra_tag["COMPOSER"];
206         FileTag->composer = g_strdup (composers.front ().toCString (true));
207     }
208 
209     /* Copyright. */
210     if (extra_tag.contains ("COPYRIGHT"))
211     {
212         const TagLib::StringList copyrights = extra_tag["COPYRIGHT"];
213         FileTag->copyright = g_strdup (copyrights.front ().toCString (true));
214     }
215 
216     /*****************
217      * Encoding Tool *
218      *****************/
219     if (extra_tag.contains ("ENCODEDBY"))
220     {
221         const TagLib::StringList encodedbys = extra_tag["ENCODEDBY"];
222         FileTag->encoded_by = g_strdup (encodedbys.front ().toCString (true));
223     }
224 
225     const TagLib::MP4::ItemListMap &extra_items = tag->itemListMap ();
226 
227     /* Album Artist */
228 #if (TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION < 10)
229     /* No "ALBUMARTIST" support in TagLib until 1.10; use atom directly. */
230     if (extra_items.contains ("aART"))
231     {
232         const TagLib::MP4::Item album_artists = extra_items["aART"];
233         FileTag->album_artist = g_strdup (album_artists.toStringList ().front ().toCString (true));
234     }
235 #else
236     if (extra_tag.contains ("ALBUMARTIST"))
237     {
238         const TagLib::StringList album_artists = extra_tag["ALBUMARTIST"];
239         FileTag->album_artist = g_strdup (album_artists.front ().toCString (true));
240     }
241 #endif
242 
243     /***********
244      * Picture *
245      ***********/
246     if (extra_items.contains ("covr"))
247     {
248         const TagLib::MP4::Item cover = extra_items["covr"];
249         const TagLib::MP4::CoverArtList covers = cover.toCoverArtList ();
250         const TagLib::MP4::CoverArt &art = covers.front ();
251 
252         /* TODO: Use g_bytes_new_with_free_func()? */
253         GBytes *bytes = g_bytes_new (art.data ().data (), art.data ().size ());
254 
255         /* MP4 does not support image types, nor descriptions. */
256         FileTag->picture = et_picture_new (ET_PICTURE_TYPE_FRONT_COVER, "", 0,
257                                            0, bytes);
258         g_bytes_unref (bytes);
259     }
260     else
261     {
262         et_file_tag_set_picture (FileTag, NULL);
263     }
264 
265     return TRUE;
266 }
267 
268 
269 /*
270  * Mp4_Tag_Write_File_Tag:
271  *
272  * Write tag data into an Mp4 file.
273  */
274 gboolean
mp4tag_write_file_tag(const ET_File * ETFile,GError ** error)275 mp4tag_write_file_tag (const ET_File *ETFile,
276                        GError **error)
277 {
278     const File_Tag *FileTag;
279     const gchar *filename;
280     const gchar *filename_utf8;
281     TagLib::MP4::Tag *tag;
282     gboolean success;
283 
284     g_return_val_if_fail (ETFile != NULL && ETFile->FileTag != NULL, FALSE);
285 
286     FileTag = (File_Tag *)ETFile->FileTag->data;
287     filename      = ((File_Name *)ETFile->FileNameCur->data)->value;
288     filename_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8;
289 
290     /* Open file for writing */
291     GFile *file = g_file_new_for_path (filename);
292     GIO_IOStream stream (file);
293 
294     if (!stream.isOpen ())
295     {
296         const GError *tmp_error = stream.getError ();
297         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
298                      _("Error while opening file ‘%s’: %s"), filename_utf8,
299                      tmp_error->message);
300         return FALSE;
301     }
302 
303     TagLib::MP4::File mp4file (&stream);
304 
305     g_object_unref (file);
306 
307     if (!mp4file.isOpen ())
308     {
309         const GError *tmp_error = stream.getError ();
310 
311         if (tmp_error)
312         {
313             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
314                          _("Error while opening file ‘%s’: %s"), filename_utf8,
315                          tmp_error->message);
316         }
317         else
318         {
319             g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
320                          _("Error while opening file ‘%s’: %s"), filename_utf8,
321                          _("MP4 format invalid"));
322         }
323 
324 
325         return FALSE;
326     }
327 
328     if (!(tag = mp4file.tag ()))
329     {
330         g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
331                      _("Error reading tags from file ‘%s’"), filename_utf8);
332         return FALSE;
333     }
334 
335     TagLib::PropertyMap fields;
336 
337     /* Title */
338     if (!et_str_empty (FileTag->title))
339     {
340         TagLib::String string (FileTag->title, TagLib::String::UTF8);
341         fields.insert ("TITLE", string);
342     }
343 
344     /* Artist */
345     if (!et_str_empty (FileTag->artist))
346     {
347         TagLib::String string (FileTag->artist, TagLib::String::UTF8);
348         fields.insert ("ARTIST", string);
349     }
350 
351     /* Album */
352     if (!et_str_empty (FileTag->album))
353     {
354         TagLib::String string (FileTag->album, TagLib::String::UTF8);
355         fields.insert ("ALBUM", string);
356     }
357 
358     /* Disc number. */
359     if (!et_str_empty (FileTag->disc_number))
360     {
361         if (!et_str_empty (FileTag->disc_total))
362         {
363             gchar *str;
364 
365             str = g_strconcat (FileTag->disc_number, "/", FileTag->disc_total,
366                                NULL);
367             TagLib::String string (str, TagLib::String::UTF8);
368             fields.insert ("DISCNUMBER", string);
369             g_free (str);
370         }
371         else
372         {
373             TagLib::String string (FileTag->disc_number, TagLib::String::UTF8);
374             fields.insert ("DISCNUMBER", string);
375         }
376     }
377 
378     /* Year */
379     if (!et_str_empty (FileTag->year))
380     {
381         TagLib::String string (FileTag->year, TagLib::String::UTF8);
382         fields.insert ("DATE", string);
383     }
384 
385     /* Track and Total Track */
386     if (!et_str_empty (FileTag->track))
387     {
388         if (!et_str_empty (FileTag->track_total))
389         {
390             gchar *str;
391 
392             str = g_strconcat (FileTag->track, "/", FileTag->track_total,
393                                NULL);
394             TagLib::String string (str, TagLib::String::UTF8);
395             fields.insert ("TRACKNUMBER", string);
396             g_free (str);
397         }
398         else
399         {
400             TagLib::String string (FileTag->track, TagLib::String::UTF8);
401             fields.insert ("TRACKNUMBER", string);
402         }
403     }
404 
405     /* Genre */
406     if (!et_str_empty (FileTag->genre))
407     {
408         TagLib::String string (FileTag->genre, TagLib::String::UTF8);
409         fields.insert ("GENRE", string);
410     }
411 
412     /* Comment */
413     if (!et_str_empty (FileTag->comment))
414     {
415         TagLib::String string (FileTag->comment, TagLib::String::UTF8);
416         fields.insert ("COMMENT", string);
417     }
418 
419     /* Composer or Writer */
420     if (!et_str_empty (FileTag->composer))
421     {
422         TagLib::String string (FileTag->composer, TagLib::String::UTF8);
423         fields.insert ("COMPOSER", string);
424     }
425 
426     /* Copyright. */
427     if (!et_str_empty (FileTag->copyright))
428     {
429         TagLib::String string (FileTag->copyright, TagLib::String::UTF8);
430         fields.insert ("COPYRIGHT", string);
431     }
432 
433     /* Encoding Tool */
434     if (!et_str_empty (FileTag->encoded_by))
435     {
436         TagLib::String string (FileTag->encoded_by, TagLib::String::UTF8);
437         fields.insert ("ENCODEDBY", string);
438     }
439 
440     TagLib::MP4::ItemListMap &extra_items = tag->itemListMap ();
441 
442     /* Album artist. */
443     if (!et_str_empty (FileTag->album_artist))
444     {
445         TagLib::String string (FileTag->album_artist, TagLib::String::UTF8);
446 #if (TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION < 10)
447         /* No "ALBUMARTIST" support in TagLib until 1.10; use atom directly. */
448         extra_items.insert ("aART", TagLib::MP4::Item (string));
449 #else
450         fields.insert ("ALBUMARTIST", string);
451 #endif
452     }
453 #if (TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION < 10)
454     else
455     {
456         extra_items.erase ("aART");
457     }
458 #endif
459 
460     /***********
461      * Picture *
462      ***********/
463     if (FileTag->picture)
464     {
465         Picture_Format pf;
466         TagLib::MP4::CoverArt::Format f;
467         gconstpointer data;
468         gsize data_size;
469 
470         pf = Picture_Format_From_Data (FileTag->picture);
471 
472         switch (pf)
473         {
474             case PICTURE_FORMAT_JPEG:
475                 f = TagLib::MP4::CoverArt::JPEG;
476                 break;
477             case PICTURE_FORMAT_PNG:
478                 f = TagLib::MP4::CoverArt::PNG;
479                 break;
480             case PICTURE_FORMAT_GIF:
481                 f = TagLib::MP4::CoverArt::GIF;
482                 break;
483             case PICTURE_FORMAT_UNKNOWN:
484             default:
485                 g_critical ("Unknown format");
486                 f = TagLib::MP4::CoverArt::JPEG;
487                 break;
488         }
489 
490         data = g_bytes_get_data (FileTag->picture->bytes, &data_size);
491         TagLib::MP4::CoverArt art (f, TagLib::ByteVector((char *)data,
492                                                          data_size));
493 
494         extra_items.insert ("covr",
495                             TagLib::MP4::Item (TagLib::MP4::CoverArtList ().append (art)));
496     }
497     else
498     {
499         extra_items.erase ("covr");
500     }
501 
502     tag->setProperties (fields);
503     success = mp4file.save () ? TRUE : FALSE;
504 
505     return success;
506 }
507 
508 #endif /* ENABLE_MP4 */
509