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