1 /*
2 * frogr-util.c -- Misc tools.
3 *
4 * Copyright (C) 2010-2012 Mario Sanchez Prada
5 * Authors: Mario Sanchez Prada <msanchez@gnome.org>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 3 of the GNU General Public
9 * License as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>
18 *
19 * Parts of this file based on code from gst-plugins-base, licensed as
20 * GPL version 2 or later (Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>)
21 */
22
23 #include "frogr-picture.h"
24 #include "frogr-util.h"
25 #include "frogr-global-defs.h"
26
27 #include <config.h>
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30 #include <gst/gst.h>
31 #include <libexif/exif-byte-order.h>
32 #include <libexif/exif-data.h>
33 #include <libexif/exif-entry.h>
34 #include <libexif/exif-format.h>
35 #include <libexif/exif-loader.h>
36 #include <libexif/exif-tag.h>
37
38 #define CAPS "video/x-raw-rgb,width=160,pixel-aspect-ratio=1/1,bpp=(int)24,depth=(int)24,endianness=(int)4321,red_mask=(int)0xff0000, green_mask=(int)0x00ff00, blue_mask=(int)0x0000ff"
39
40 static gboolean
_spawn_command(const gchar * cmd)41 _spawn_command (const gchar* cmd)
42 {
43 GError *error = NULL;
44
45 if (!g_spawn_command_line_async (cmd, &error))
46 {
47 if (error)
48 {
49 DEBUG ("Error spawning command '%s': %s", cmd, error->message);
50 g_error_free (error);
51 }
52
53 return FALSE;
54 }
55 return TRUE;
56 }
57
58 const gchar *
_get_data_dir(void)59 _get_data_dir (void)
60 {
61 #ifdef PLATFORM_MAC
62 /* For MacOSX, we return the value of the environment value set by
63 the wrapper script running the application */
64 static gchar *xdg_data_dir = NULL;
65 if (!xdg_data_dir)
66 xdg_data_dir = g_strdup (g_getenv("XDG_DATA_DIRS"));
67 return (const gchar *) xdg_data_dir;
68 #else
69 /* For GNOME, we just return DATA_DIR */
70 return DATA_DIR;
71 #endif
72 }
73
74 const gchar *
frogr_util_get_app_data_dir(void)75 frogr_util_get_app_data_dir (void)
76 {
77 static gchar *app_data_dir = NULL;
78 if (!app_data_dir)
79 app_data_dir = g_strdup_printf ("%s/frogr", _get_data_dir ());
80
81 return (const gchar *) app_data_dir;
82 }
83
84 const gchar *
frogr_util_get_icons_dir(void)85 frogr_util_get_icons_dir (void)
86 {
87 static gchar *icons_dir = NULL;
88 if (!icons_dir)
89 icons_dir = g_strdup_printf ("%s/icons", _get_data_dir ());
90
91 return (const gchar *) icons_dir;
92 }
93
94 const gchar *
frogr_util_get_locale_dir(void)95 frogr_util_get_locale_dir (void)
96 {
97 static const gchar *locale_dir = NULL;
98 if (!locale_dir)
99 {
100 #ifndef PLATFORM_MAC
101 /* If not in MacOSX, we trust the defined variable better */
102 locale_dir = g_strdup (FROGR_LOCALE_DIR);
103 #endif
104
105 /* Fallback for MacOSX and cases where FROGR_LOCALE_DIR was not
106 defined yet because of any reason */
107 if (!locale_dir)
108 locale_dir = g_strdup_printf ("%s/locale", _get_data_dir ());
109 }
110
111 return (const gchar *) locale_dir;
112 }
113
114 gchar *
_get_uris_string_from_list(GList * uris_list)115 _get_uris_string_from_list (GList *uris_list)
116 {
117 GList *current_uri = NULL;
118 gchar **uris_array = NULL;
119 gchar *uris_str = NULL;
120 gint n_uris = 0;
121 gint i = 0;
122
123 n_uris = g_list_length (uris_list);
124 if (n_uris == 0)
125 return NULL;
126
127 uris_array = g_new0 (gchar*, n_uris + 1);
128 for (current_uri = uris_list; current_uri; current_uri = g_list_next (current_uri))
129 uris_array[i++] = (gchar *) (current_uri->data);
130
131 uris_str = g_strjoinv (" ", uris_array);
132 g_free (uris_array);
133
134 return uris_str;
135 }
136
137 static void
_open_uris_with_app_info(GList * uris_list,GAppInfo * app_info)138 _open_uris_with_app_info (GList *uris_list, GAppInfo *app_info)
139 {
140 GError *error = NULL;
141
142 /* Early return */
143 if (!uris_list)
144 return;
145
146 if (!app_info || !g_app_info_launch_uris (app_info, uris_list, NULL, &error))
147 {
148 /* The default app didn't succeed, so try 'gnome-open' / 'open' */
149 gchar *command = NULL;
150 gchar *uris = NULL;
151
152 uris = _get_uris_string_from_list (uris_list);
153
154 #ifdef PLATFORM_MAC
155 /* In MacOSX use 'open' instead of 'gnome-open' */
156 command = g_strdup_printf ("open %s", uris);
157 #else
158 command = g_strdup_printf ("gnome-open %s", uris);
159 #endif
160 _spawn_command (command);
161
162 if (error)
163 {
164 DEBUG ("Error opening URI(s) %s: %s", uris, error->message);
165 g_error_free (error);
166 }
167
168 g_free (command);
169 g_free (uris);
170 }
171
172 g_list_foreach (uris_list, (GFunc) g_free, NULL);
173 g_list_free (uris_list);
174 }
175
176 void
frogr_util_open_uri(const gchar * uri)177 frogr_util_open_uri (const gchar *uri)
178 {
179 GAppInfo *app_info = NULL;
180 GList *uris_list = NULL;
181
182 /* Early return */
183 if (!uri)
184 return;
185
186 #ifndef PLATFORM_MAC
187 /* Supported network URIs */
188 if (g_str_has_prefix (uri, "http:") || g_str_has_prefix (uri, "https:"))
189 app_info = g_app_info_get_default_for_uri_scheme ("http");
190
191 /* Supported help URIs */
192 if (g_str_has_prefix (uri, "ghelp:"))
193 app_info = g_app_info_get_default_for_uri_scheme ("ghelp");
194 #endif
195
196 uris_list = g_list_append (uris_list, g_strdup (uri));
197 _open_uris_with_app_info (uris_list, app_info);
198 }
199
200 void
frogr_util_open_pictures_in_viewer(GSList * pictures)201 frogr_util_open_pictures_in_viewer (GSList *pictures)
202 {
203 GAppInfo *app_info = NULL;
204 GList *uris_list = NULL;
205 GSList *current_pic = NULL;
206 FrogrPicture *picture = NULL;
207
208 /* Early return */
209 if (!pictures)
210 return;
211
212 for (current_pic = pictures; current_pic; current_pic = g_slist_next (current_pic))
213 {
214 picture = FROGR_PICTURE (current_pic->data);
215 uris_list = g_list_append (uris_list, g_strdup (frogr_picture_get_fileuri (picture)));
216 }
217
218 /* We currently choose the application based in the mime type of the
219 first picture. This is very basic, but probably good enough for now */
220 picture = FROGR_PICTURE (pictures->data);
221 if (frogr_picture_is_video (picture))
222 app_info = g_app_info_get_default_for_type ("video/mpeg", TRUE);
223 else
224 app_info = g_app_info_get_default_for_type ("image/jpg", TRUE);
225
226 /* uris_list will be freed inside of the function */
227 _open_uris_with_app_info (uris_list, app_info);
228 }
229
230 static void
_show_message_dialog(GtkWindow * parent,const gchar * message,GtkMessageType type)231 _show_message_dialog (GtkWindow *parent, const gchar *message, GtkMessageType type)
232 {
233 /* Show alert */
234 GtkWidget *dialog =
235 gtk_message_dialog_new (parent,
236 GTK_DIALOG_MODAL,
237 type,
238 GTK_BUTTONS_CLOSE,
239 "%s", message);
240 gtk_window_set_title (GTK_WINDOW (dialog), APP_SHORTNAME);
241
242 g_signal_connect (G_OBJECT (dialog), "response",
243 G_CALLBACK (gtk_widget_destroy), dialog);
244
245 gtk_widget_show_all (dialog);
246 }
247
248 void
frogr_util_show_info_dialog(GtkWindow * parent,const gchar * message)249 frogr_util_show_info_dialog (GtkWindow *parent, const gchar *message)
250 {
251 _show_message_dialog (parent, message, GTK_MESSAGE_INFO);
252 }
253
254 void
frogr_util_show_warning_dialog(GtkWindow * parent,const gchar * message)255 frogr_util_show_warning_dialog (GtkWindow *parent, const gchar *message)
256 {
257 _show_message_dialog (parent, message, GTK_MESSAGE_WARNING);
258 }
259
260 void
frogr_util_show_error_dialog(GtkWindow * parent,const gchar * message)261 frogr_util_show_error_dialog (GtkWindow *parent, const gchar *message)
262 {
263 _show_message_dialog (parent, message, GTK_MESSAGE_ERROR);
264 }
265
266 static GdkPixbuf *
_get_corrected_pixbuf(GdkPixbuf * pixbuf,gint max_width,gint max_height)267 _get_corrected_pixbuf (GdkPixbuf *pixbuf, gint max_width, gint max_height)
268 {
269 GdkPixbuf *scaled_pixbuf = NULL;
270 GdkPixbuf *rotated_pixbuf;
271 const gchar *orientation;
272 gint width;
273 gint height;
274
275 g_return_val_if_fail (max_width > 0, NULL);
276 g_return_val_if_fail (max_height > 0, NULL);
277
278 /* Look for the right side to reduce */
279 width = gdk_pixbuf_get_width (pixbuf);
280 height = gdk_pixbuf_get_height (pixbuf);
281
282 DEBUG ("Original size: %dx%d\n", width, height);
283
284 if (width > max_width)
285 {
286 height = (float)height * max_width / width;
287 width = max_width;
288 }
289
290 if (height > max_height)
291 {
292 width = (float)width * max_height / height;
293 height = max_height;
294 }
295
296 DEBUG ("Scaled size: %dx%d\n", width, height);
297
298 /* Scale the pixbuf to its best size */
299 scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, width, height,
300 GDK_INTERP_BILINEAR);
301
302 /* Correct orientation if needed */
303 orientation = gdk_pixbuf_get_option (pixbuf, "orientation");
304
305 /* No orientation defined or 0 degrees rotation: we're done */
306 if (!orientation || !g_strcmp0 (orientation, "1"))
307 return scaled_pixbuf;
308
309 DEBUG ("File orientation for file: %s", orientation);
310 rotated_pixbuf = NULL;
311
312 /* Rotated 90 degrees */
313 if (!g_strcmp0 (orientation, "8"))
314 rotated_pixbuf = gdk_pixbuf_rotate_simple (scaled_pixbuf,
315 GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
316 /* Rotated 180 degrees */
317 if (!g_strcmp0 (orientation, "3"))
318 rotated_pixbuf = gdk_pixbuf_rotate_simple (scaled_pixbuf,
319 GDK_PIXBUF_ROTATE_UPSIDEDOWN);
320 /* Rotated 270 degrees */
321 if (!g_strcmp0 (orientation, "6"))
322 rotated_pixbuf = gdk_pixbuf_rotate_simple (scaled_pixbuf,
323 GDK_PIXBUF_ROTATE_CLOCKWISE);
324 if (rotated_pixbuf)
325 {
326 g_object_unref (scaled_pixbuf);
327 return rotated_pixbuf;
328 }
329
330 /* No rotation was applied, return the scaled pixbuf */
331 return scaled_pixbuf;
332 }
333
334 static GdkPixbuf *
_get_pixbuf_from_image_contents(const guchar * contents,gsize length,GError ** out_error)335 _get_pixbuf_from_image_contents (const guchar *contents, gsize length, GError **out_error)
336 {
337 GdkPixbufLoader *pixbuf_loader = NULL;
338 GdkPixbuf *pixbuf = NULL;
339 GError *error = NULL;
340
341 pixbuf_loader = gdk_pixbuf_loader_new ();
342 if (gdk_pixbuf_loader_write (pixbuf_loader,
343 (const guchar *)contents,
344 length,
345 &error))
346 {
347 gdk_pixbuf_loader_close (pixbuf_loader, NULL);
348 pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
349 }
350
351 if (error)
352 {
353 DEBUG ("Error loading pixbuf: %s", error->message);
354 g_propagate_error (out_error, error);
355 }
356
357 /* Keep the pixbuf before destroying the loader */
358 if (pixbuf)
359 g_object_ref (pixbuf);
360 g_object_unref (pixbuf_loader);
361
362 return pixbuf;
363 }
364
365 /* The following function is based in GStreamer's snapshot example,
366 from gst-plugins-base, licensed as GPL version 2 or later
367 (Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>) */
368 static GdkPixbuf *
_get_pixbuf_from_video_file(GFile * file,GError ** out_error)369 _get_pixbuf_from_video_file (GFile *file, GError **out_error)
370 {
371 GdkPixbuf *pixbuf = NULL;
372 GstElement *pipeline, *sink;
373 GstBuffer *buffer;
374 GstFormat format;
375 GstStateChangeReturn ret;
376 gchar *file_uri;
377 gchar *descr;
378 gint width, height;
379 gint64 duration, position;
380 GError *error = NULL;
381 gboolean res;
382
383 /* create a new pipeline */
384 file_uri = g_file_get_uri (file);
385 descr = g_strdup_printf ("uridecodebin uri=%s ! ffmpegcolorspace ! videoscale ! "
386 " appsink name=sink caps=\"" CAPS "\"", file_uri);
387 g_free (file_uri);
388
389 pipeline = gst_parse_launch (descr, &error);
390 g_free (descr);
391
392 if (error != NULL) {
393 DEBUG ("Could not construct pipeline: %s\n", error->message);
394 g_propagate_error (out_error, error);
395 return NULL;
396 }
397
398 /* get sink */
399 sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
400
401 /* set to PAUSED to make the first frame arrive in the sink */
402 ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
403 switch (ret) {
404 case GST_STATE_CHANGE_FAILURE:
405 DEBUG ("failed to play the file\n");
406 return NULL;
407 case GST_STATE_CHANGE_NO_PREROLL:
408 /* for live sources, we need to set the pipeline to PLAYING before we can
409 * receive a buffer. We don't do that yet */
410 DEBUG ("live sources not supported yet\n");
411 return NULL;
412 default:
413 break;
414 }
415
416 /* This can block for up to 5 seconds. If your machine is really overloaded,
417 * it might time out before the pipeline prerolled and we generate an error. A
418 * better way is to run a mainloop and catch errors there. */
419 ret = gst_element_get_state (pipeline, NULL, NULL, 5 * GST_SECOND);
420 if (ret == GST_STATE_CHANGE_FAILURE) {
421 DEBUG ("failed to play the file\n");
422 return NULL;
423 }
424
425 /* get the duration */
426 format = GST_FORMAT_TIME;
427 gst_element_query_duration (pipeline, &format, &duration);
428
429 if (duration != -1)
430 /* we have a duration, seek to 50% */
431 position = duration * 0.5;
432 else
433 /* no duration, seek to 1 second, this could EOS */
434 position = 1 * GST_SECOND;
435
436 /* seek to the a position in the file. Most files have a black first frame so
437 * by seeking to somewhere else we have a bigger chance of getting something
438 * more interesting. */
439 gst_element_seek_simple (pipeline, GST_FORMAT_TIME,
440 GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_FLUSH, position);
441
442 /* get the preroll buffer from appsink, this block untils appsink really prerolls */
443 g_signal_emit_by_name (sink, "pull-preroll", &buffer, NULL);
444
445 /* if we have a buffer now, convert it to a pixbuf. It's possible that we
446 * don't have a buffer because we went EOS right away or had an error. */
447 if (buffer) {
448 GstCaps *caps;
449 GstStructure *s;
450
451 /* get the snapshot buffer format now. We set the caps on the appsink so
452 * that it can only be an rgb buffer. The only thing we have not specified
453 * on the caps is the height, which is dependant on the pixel-aspect-ratio
454 * of the source material */
455 caps = GST_BUFFER_CAPS (buffer);
456 if (!caps) {
457 DEBUG ("could not get snapshot format\n");
458 return NULL;
459 }
460 s = gst_caps_get_structure (caps, 0);
461
462 /* we need to get the final caps on the buffer to get the size */
463 res = gst_structure_get_int (s, "width", &width);
464 res |= gst_structure_get_int (s, "height", &height);
465 if (!res) {
466 DEBUG ("could not get snapshot dimension\n");
467 return NULL;
468 }
469
470 /* create pixmap from buffer and save, gstreamer video buffers have a stride
471 * that is rounded up to the nearest multiple of 4 */
472 pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buffer),
473 GDK_COLORSPACE_RGB, FALSE, 8, width, height,
474 GST_ROUND_UP_4 (width * 3), NULL, NULL);
475
476 } else {
477 DEBUG ("could not make snapshot\n");
478 }
479
480 /* cleanup and exit */
481 gst_element_set_state (pipeline, GST_STATE_NULL);
482 gst_object_unref (pipeline);
483
484 return pixbuf;
485 }
486
487 GdkPixbuf *
frogr_util_get_pixbuf_for_video_file(GFile * file,gint max_width,gint max_height,GError ** error)488 frogr_util_get_pixbuf_for_video_file (GFile *file, gint max_width, gint max_height, GError **error)
489 {
490 GdkPixbuf *pixbuf = NULL;
491
492 pixbuf = _get_pixbuf_from_video_file (file, error);
493 if (pixbuf)
494 {
495 GdkPixbuf *c_pixbuf = NULL;
496 c_pixbuf = _get_corrected_pixbuf (pixbuf, max_width, max_height);
497 g_object_unref (pixbuf);
498 pixbuf = c_pixbuf;
499 }
500
501 return pixbuf;
502 }
503
504 GdkPixbuf *
frogr_util_get_pixbuf_from_image_contents(const guchar * contents,gsize length,gint max_width,gint max_height,GError ** error)505 frogr_util_get_pixbuf_from_image_contents (const guchar *contents, gsize length, gint max_width, gint max_height, GError **error)
506 {
507 GdkPixbuf *pixbuf = NULL;
508
509 pixbuf = _get_pixbuf_from_image_contents (contents, length, error);
510 if (pixbuf)
511 {
512 GdkPixbuf *c_pixbuf = NULL;
513 c_pixbuf = _get_corrected_pixbuf (pixbuf, max_width, max_height);
514 g_object_unref (pixbuf);
515 pixbuf = c_pixbuf;
516 }
517
518 return pixbuf;
519 }
520
521 gchar *
frogr_util_get_datasize_string(gulong datasize)522 frogr_util_get_datasize_string (gulong datasize)
523 {
524 gchar *result = NULL;
525
526 if (datasize != G_MAXULONG)
527 {
528 gfloat datasize_float = G_MAXFLOAT;
529 gchar *unit_str = NULL;
530 int n_divisions = 0;
531
532 datasize_float = datasize;
533 while (datasize_float > 1000.0 && n_divisions < 3)
534 {
535 datasize_float /= 1024;
536 n_divisions++;
537 }
538
539 switch (n_divisions)
540 {
541 case 0:
542 unit_str = g_strdup ("KB");
543 break;
544 case 1:
545 unit_str = g_strdup ("MB");
546 break;
547 case 2:
548 unit_str = g_strdup ("GB");
549 break;
550 default:
551 unit_str = NULL;;
552 }
553
554 if (unit_str)
555 {
556 result = g_strdup_printf ("%.1f %s", datasize_float, unit_str);
557 g_free (unit_str);
558 }
559 }
560
561 return result;
562 }
563
564 const gchar * const *
frogr_util_get_supported_mimetypes(void)565 frogr_util_get_supported_mimetypes (void)
566 {
567 static const gchar *supported_mimetypes[] = {
568 "image/jpg",
569 "image/jpeg",
570 "image/png",
571 "image/bmp",
572 "image/gif",
573 "video/mpeg",
574 "video/mp4",
575 "video/quicktime",
576 "video/x-msvideo",
577 "video/ogg",
578 "video/x-ms-wmv",
579 "video/3gpp",
580 "video/m2ts",
581 "video/avchd-stream",
582 "video/mp2t",
583 "video/vnd.dlna.mpeg-tts",
584 "application/ogg",
585 NULL
586 };
587
588 return supported_mimetypes;
589 }
590