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