1 /* EasyTAG - Tag editor for audio files
2  * Copyright (C) 2014-2015  David King <amigadave@amigadave.com>
3  * Copyright (C) 2000-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"
21 
22 #include "picture.h"
23 
24 #include <glib/gi18n.h>
25 
26 #include "easytag.h"
27 #include "log.h"
28 #include "misc.h"
29 #include "setting.h"
30 #include "charset.h"
31 
32 #include "win32/win32dep.h"
33 
G_DEFINE_BOXED_TYPE(EtPicture,et_picture,et_picture_copy_single,et_picture_free)34 G_DEFINE_BOXED_TYPE (EtPicture, et_picture, et_picture_copy_single, et_picture_free)
35 
36 /*
37  * Note :
38  * -> MP4_TAG :
39  *      Just has one picture (ET_PICTURE_TYPE_FRONT_COVER).
40  *      The format's don't matter to the MP4 side.
41  *
42  */
43 
44 /*
45  * et_picture_type_from_filename:
46  * @filename: UTF-8 representation of a filename
47  *
48  * Use some heuristics to provide an estimate of the type of the picture, based
49  * on the filename.
50  *
51  * Returns: the picture type, or %ET_PICTURE_TYPE_FRONT_COVER if the type could
52  * not be estimated
53  */
54 EtPictureType
55 et_picture_type_from_filename (const gchar *filename_utf8)
56 {
57     EtPictureType picture_type = ET_PICTURE_TYPE_FRONT_COVER;
58 
59     /* TODO: Use g_str_tokenize_and_fold(). */
60     static const struct
61     {
62         const gchar *type_str;
63         const EtPictureType pic_type;
64     } type_mappings[] =
65     {
66         { "front", ET_PICTURE_TYPE_FRONT_COVER },
67         { "back", ET_PICTURE_TYPE_BACK_COVER },
68         { "inlay", ET_PICTURE_TYPE_LEAFLET_PAGE },
69         { "inside", ET_PICTURE_TYPE_LEAFLET_PAGE },
70         { "leaflet", ET_PICTURE_TYPE_LEAFLET_PAGE },
71         { "page", ET_PICTURE_TYPE_LEAFLET_PAGE },
72         { "CD", ET_PICTURE_TYPE_MEDIA },
73         { "media", ET_PICTURE_TYPE_MEDIA },
74         { "artist", ET_PICTURE_TYPE_ARTIST_PERFORMER },
75         { "performer", ET_PICTURE_TYPE_ARTIST_PERFORMER },
76         { "conductor", ET_PICTURE_TYPE_CONDUCTOR },
77         { "band", ET_PICTURE_TYPE_BAND_ORCHESTRA },
78         { "orchestra", ET_PICTURE_TYPE_BAND_ORCHESTRA },
79         { "composer", ET_PICTURE_TYPE_COMPOSER },
80         { "lyricist", ET_PICTURE_TYPE_LYRICIST_TEXT_WRITER },
81         { "illustration", ET_PICTURE_TYPE_ILLUSTRATION },
82         { "publisher", ET_PICTURE_TYPE_PUBLISHER_STUDIO_LOGOTYPE }
83     };
84     gsize i;
85     gchar *folded_filename;
86 
87     g_return_val_if_fail (filename_utf8 != NULL, ET_PICTURE_TYPE_FRONT_COVER);
88 
89     folded_filename = g_utf8_casefold (filename_utf8, -1);
90 
91     for (i = 0; i < G_N_ELEMENTS (type_mappings); i++)
92     {
93         gchar *folded_type = g_utf8_casefold (type_mappings[i].type_str, -1);
94         if (strstr (folded_filename, folded_type) != NULL)
95         {
96             picture_type = type_mappings[i].pic_type;
97             g_free (folded_type);
98             break;
99         }
100         else
101         {
102             g_free (folded_type);
103         }
104     }
105 
106     g_free (folded_filename);
107 
108     return picture_type;
109 }
110 
111 /* FIXME: Possibly use gnome_vfs_get_mime_type_for_buffer. */
112 Picture_Format
Picture_Format_From_Data(const EtPicture * pic)113 Picture_Format_From_Data (const EtPicture *pic)
114 {
115     gsize size;
116     gconstpointer data;
117 
118     g_return_val_if_fail (pic != NULL, PICTURE_FORMAT_UNKNOWN);
119 
120     data = g_bytes_get_data (pic->bytes, &size);
121 
122     /* JPEG : "\xff\xd8\xff". */
123     if (size > 3 && (memcmp (data, "\xff\xd8\xff", 3) == 0))
124     {
125         return PICTURE_FORMAT_JPEG;
126     }
127 
128     /* PNG : "\x89PNG\x0d\x0a\x1a\x0a". */
129     if (size > 8 && (memcmp (data, "\x89PNG\x0d\x0a\x1a\x0a", 8) == 0))
130     {
131         return PICTURE_FORMAT_PNG;
132     }
133 
134     /* GIF: "GIF87a" */
135     if (size > 6 && (memcmp (data, "GIF87a", 6) == 0))
136     {
137         return PICTURE_FORMAT_GIF;
138     }
139 
140     /* GIF: "GIF89a" */
141     if (size > 6 && (memcmp (data, "GIF89a", 6) == 0))
142     {
143         return PICTURE_FORMAT_GIF;
144     }
145 
146     return PICTURE_FORMAT_UNKNOWN;
147 }
148 
149 const gchar *
Picture_Mime_Type_String(Picture_Format format)150 Picture_Mime_Type_String (Picture_Format format)
151 {
152     switch (format)
153     {
154         case PICTURE_FORMAT_JPEG:
155             return "image/jpeg";
156         case PICTURE_FORMAT_PNG:
157             return "image/png";
158         case PICTURE_FORMAT_GIF:
159             return "image/gif";
160         case PICTURE_FORMAT_UNKNOWN:
161         default:
162             g_debug ("%s", "Unrecognised image MIME type");
163             return "application/octet-stream";
164     }
165 }
166 
167 
168 static const gchar *
Picture_Format_String(Picture_Format format)169 Picture_Format_String (Picture_Format format)
170 {
171     switch (format)
172     {
173         case PICTURE_FORMAT_JPEG:
174             return _("JPEG image");
175         case PICTURE_FORMAT_PNG:
176             return _("PNG image");
177         case PICTURE_FORMAT_GIF:
178             return _("GIF image");
179         case PICTURE_FORMAT_UNKNOWN:
180         default:
181             return _("Unknown image");
182     }
183 }
184 
185 const gchar *
Picture_Type_String(EtPictureType type)186 Picture_Type_String (EtPictureType type)
187 {
188     switch (type)
189     {
190         case ET_PICTURE_TYPE_OTHER:
191             return _("Other");
192         case ET_PICTURE_TYPE_FILE_ICON:
193             return _("32×32 pixel PNG file icon");
194         case ET_PICTURE_TYPE_OTHER_FILE_ICON:
195             return _("Other file icon");
196         case ET_PICTURE_TYPE_FRONT_COVER:
197             return _("Cover (front)");
198         case ET_PICTURE_TYPE_BACK_COVER:
199             return _("Cover (back)");
200         case ET_PICTURE_TYPE_LEAFLET_PAGE:
201             return _("Leaflet page");
202         case ET_PICTURE_TYPE_MEDIA:
203             return _("Media (such as label side of CD)");
204         case ET_PICTURE_TYPE_LEAD_ARTIST_LEAD_PERFORMER_SOLOIST:
205             return _("Lead artist/lead performer/soloist");
206         case ET_PICTURE_TYPE_ARTIST_PERFORMER:
207             return _("Artist/performer");
208         case ET_PICTURE_TYPE_CONDUCTOR:
209             return _("Conductor");
210         case ET_PICTURE_TYPE_BAND_ORCHESTRA:
211             return _("Band/Orchestra");
212         case ET_PICTURE_TYPE_COMPOSER:
213             return _("Composer");
214         case ET_PICTURE_TYPE_LYRICIST_TEXT_WRITER:
215             return _("Lyricist/text writer");
216         case ET_PICTURE_TYPE_RECORDING_LOCATION:
217             return _("Recording location");
218         case ET_PICTURE_TYPE_DURING_RECORDING:
219             return _("During recording");
220         case ET_PICTURE_TYPE_DURING_PERFORMANCE:
221             return _("During performance");
222         case ET_PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE:
223             return _("Movie/video screen capture");
224         case ET_PICTURE_TYPE_A_BRIGHT_COLOURED_FISH:
225             return _("A bright colored fish");
226         case ET_PICTURE_TYPE_ILLUSTRATION:
227             return _("Illustration");
228         case ET_PICTURE_TYPE_BAND_ARTIST_LOGOTYPE:
229             return _("Band/Artist logotype");
230         case ET_PICTURE_TYPE_PUBLISHER_STUDIO_LOGOTYPE:
231             return _("Publisher/studio logotype");
232 
233         case ET_PICTURE_TYPE_UNDEFINED:
234         default:
235             return _("Unknown image type");
236     }
237 }
238 
239 gboolean
et_picture_detect_difference(const EtPicture * a,const EtPicture * b)240 et_picture_detect_difference (const EtPicture *a,
241                               const EtPicture *b)
242 {
243     if (!a && !b)
244     {
245         return FALSE;
246     }
247 
248     if ((a && !b) || (!a && b))
249     {
250         return TRUE;
251     }
252 
253     if (a->type != b->type)
254     {
255         return TRUE;
256     }
257 
258     if ((a->width != b->width) || (a->height != b->height))
259     {
260         return TRUE;
261     }
262 
263     if (et_normalized_strcmp0 (a->description, b->description) != 0)
264     {
265         return TRUE;
266     }
267 
268     if (!g_bytes_equal (a->bytes, b->bytes))
269     {
270         return TRUE;
271     }
272 
273     return FALSE;
274 }
275 
276 gchar *
et_picture_format_info(const EtPicture * pic,ET_Tag_Type tag_type)277 et_picture_format_info (const EtPicture *pic,
278                         ET_Tag_Type tag_type)
279 {
280     const gchar *format, *desc, *type;
281     gchar *r, *size_str;
282 
283     format = Picture_Format_String(Picture_Format_From_Data(pic));
284 
285     if (pic->description)
286         desc = pic->description;
287     else
288         desc = "";
289 
290     type = Picture_Type_String (pic->type);
291     size_str = g_format_size (g_bytes_get_size (pic->bytes));
292 
293     /* Behaviour following the tag type. */
294     if (tag_type == MP4_TAG)
295     {
296         r = g_strdup_printf ("%s (%s - %d×%d %s)\n%s: %s", format,
297                              size_str, pic->width, pic->height,
298                              _("pixels"), _("Type"), type);
299     }
300     else
301     {
302         r = g_strdup_printf ("%s (%s - %d×%d %s)\n%s: %s\n%s: %s", format,
303                              size_str, pic->width, pic->height,
304                              _("pixels"), _("Type"), type,
305                              _("Description"), desc);
306     }
307 
308     g_free (size_str);
309 
310     return r;
311 }
312 
313 /*
314  * et_picture_new:
315  * @type: the image type
316  * @description: a text description
317  * @width: image width
318  * @height image height
319  * @bytes: image data
320  *
321  * Create a new #EtPicture instance, copying the string and adding a reference
322  * to the image data.
323  *
324  * Returns: a new #EtPicture, or %NULL on failure
325  */
326 EtPicture *
et_picture_new(EtPictureType type,const gchar * description,guint width,guint height,GBytes * bytes)327 et_picture_new (EtPictureType type,
328                 const gchar *description,
329                 guint width,
330                 guint height,
331                 GBytes *bytes)
332 {
333     EtPicture *pic;
334 
335     g_return_val_if_fail (description != NULL, NULL);
336     g_return_val_if_fail (bytes != NULL, NULL);
337 
338     pic = g_slice_new (EtPicture);
339 
340     pic->type = type;
341     pic->description = g_strdup (description);
342     pic->width = width;
343     pic->height = height;
344     pic->bytes = g_bytes_ref (bytes);
345     pic->next = NULL;
346 
347     return pic;
348 }
349 
350 EtPicture *
et_picture_copy_single(const EtPicture * pic)351 et_picture_copy_single (const EtPicture *pic)
352 {
353     EtPicture *pic2;
354 
355     g_return_val_if_fail (pic != NULL, NULL);
356 
357     pic2 = et_picture_new (pic->type, pic->description, pic->width,
358                            pic->height, pic->bytes);
359 
360     return pic2;
361 }
362 
363 EtPicture *
et_picture_copy_all(const EtPicture * pic)364 et_picture_copy_all (const EtPicture *pic)
365 {
366     EtPicture *pic2 = et_picture_copy_single (pic);
367 
368     if (pic->next)
369     {
370         pic2->next = et_picture_copy_all (pic->next);
371     }
372 
373     return pic2;
374 }
375 
376 void
et_picture_free(EtPicture * pic)377 et_picture_free (EtPicture *pic)
378 {
379     if (pic == NULL)
380     {
381         return;
382     }
383 
384     if (pic->next)
385     {
386         et_picture_free (pic->next);
387     }
388 
389     g_free (pic->description);
390     g_bytes_unref (pic->bytes);
391     pic->bytes = NULL;
392 
393     g_slice_free (EtPicture, pic);
394 }
395 
396 
397 /*
398  * et_picture_load_file_data:
399  * @file: the GFile from which to load an image
400  * @error: a #GError to provide information on errors, or %NULL to ignore
401  *
402  * Load an image from the supplied @file.
403  *
404  * Returns: image data on success, %NULL otherwise
405  */
406 GBytes *
et_picture_load_file_data(GFile * file,GError ** error)407 et_picture_load_file_data (GFile *file, GError **error)
408 {
409     gsize size;
410     GFileInfo *info;
411     GFileInputStream *file_istream;
412     GOutputStream *ostream;
413 
414     info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
415                               G_FILE_QUERY_INFO_NONE, NULL, error);
416 
417     if (!info)
418     {
419         g_assert (error == NULL || *error != NULL);
420         return NULL;
421     }
422 
423     file_istream = g_file_read (file, NULL, error);
424 
425     if (!file_istream)
426     {
427         g_assert (error == NULL || *error != NULL);
428         return NULL;
429     }
430 
431     size = g_file_info_get_size (info);
432     g_object_unref (info);
433 
434     /* HTTP servers may not report a size, or the file could be empty. */
435     if (size == 0)
436     {
437         ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
438     }
439     else
440     {
441         gchar *buffer;
442 
443         buffer = g_malloc (size);
444         ostream = g_memory_output_stream_new (buffer, size, g_realloc, g_free);
445     }
446 
447     if (g_output_stream_splice (ostream, G_INPUT_STREAM (file_istream),
448                                 G_OUTPUT_STREAM_SPLICE_NONE, NULL,
449                                 error) == -1)
450     {
451         g_object_unref (ostream);
452         g_object_unref (file_istream);
453         g_assert (error == NULL || *error != NULL);
454         return NULL;
455     }
456     else
457     {
458         /* Image loaded. */
459         GBytes *bytes;
460 
461         g_object_unref (file_istream);
462 
463         if (!g_output_stream_close (ostream, NULL, error))
464         {
465             g_object_unref (ostream);
466             g_assert (error == NULL || *error != NULL);
467             return NULL;
468         }
469 
470         g_assert (error == NULL || *error == NULL);
471 
472         if (g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (ostream))
473             == 0)
474         {
475             g_object_unref (ostream);
476             /* FIXME: Mark up the string for translation. */
477             g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s",
478                          "Input truncated or empty");
479             return NULL;
480         }
481 
482         bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (ostream));
483 
484         g_object_unref (ostream);
485         g_assert (error == NULL || *error == NULL);
486         return bytes;
487     }
488 }
489 
490 /*
491  * et_picture_save_file_data:
492  * @pic: the #EtPicture from which to take an image
493  * @file: the #GFile for which to save an image
494  * @error: a #GError to provide information on errors, or %NULL to ignore
495  *
496  * Saves an image from @pic to the supplied @file.
497  *
498  * Returns: %TRUE on success, %FALSE otherwise
499  */
500 gboolean
et_picture_save_file_data(const EtPicture * pic,GFile * file,GError ** error)501 et_picture_save_file_data (const EtPicture *pic,
502                            GFile *file,
503                            GError **error)
504 {
505     GFileOutputStream *file_ostream;
506     gconstpointer data;
507     gsize data_size;
508     gsize bytes_written;
509 
510     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
511 
512     file_ostream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL,
513                                    error);
514 
515     if (!file_ostream)
516     {
517         g_assert (error == NULL || *error != NULL);
518         return FALSE;
519     }
520 
521     data = g_bytes_get_data (pic->bytes, &data_size);
522 
523     if (!g_output_stream_write_all (G_OUTPUT_STREAM (file_ostream), data,
524                                     data_size, &bytes_written, NULL, error))
525     {
526         g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %" G_GSIZE_FORMAT
527                  " bytes of picture data were written", bytes_written,
528                  g_bytes_get_size (pic->bytes));
529         g_object_unref (file_ostream);
530         g_assert (error == NULL || *error != NULL);
531         return FALSE;
532     }
533 
534     if (!g_output_stream_close (G_OUTPUT_STREAM (file_ostream), NULL, error))
535     {
536         g_object_unref (file_ostream);
537         g_assert (error == NULL || *error != NULL);
538         return FALSE;
539     }
540 
541     g_assert (error == NULL || *error == NULL);
542     g_object_unref (file_ostream);
543     return TRUE;
544 }
545