OSDN Git Service

LinGui: check gstreamer return values more carefully
[handbrake-jp/handbrake-jp-git.git] / gtk / src / preview.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * preview.c
4  * Copyright (C) John Stebbins 2008 <stebbins@stebbins>
5  * 
6  * preview.c is free software.
7  * 
8  * You may redistribute it and/or modify it under the terms of the
9  * GNU General Public License, as published by the Free Software
10  * Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  * 
13  */
14 #include <unistd.h>
15 #include <glib.h>
16 #include <glib/gstdio.h>
17 #include <glib-object.h>
18 #include <gtk/gtk.h>
19
20 #if !defined(_WIN32)
21 #include <gdk/gdkx.h>
22 #endif
23
24 #if defined(_ENABLE_GST)
25 #include <gst/gst.h>
26 #include <gst/interfaces/xoverlay.h>
27 #include <gst/video/video.h>
28 #include <gst/pbutils/missing-plugins.h>
29 #endif
30
31 #include "settings.h"
32 #include "presets.h"
33 #include "callbacks.h"
34 #include "hb-backend.h"
35 #include "preview.h"
36 #include "values.h"
37 #include "hb.h"
38
39 #define PREVIEW_STATE_IMAGE 0
40 #define PREVIEW_STATE_LIVE 1
41
42 struct preview_s
43 {
44 #if defined(_ENABLE_GST)
45         GstElement *play;
46         gulong xid;
47 #endif
48         gint64 len;
49         gint64 pos;
50         gboolean seek_lock;
51         gboolean progress_lock;
52         gint width;
53         gint height;
54         GtkWidget *view;
55         GdkPixbuf *pix;
56         gint button_width;
57         gint button_height;
58         gint frame;
59         gint state;
60         gboolean pause;
61         gboolean encoded[10];
62         gint encode_frame;
63         gint live_id;
64         gchar *current;
65 };
66
67 #if defined(_ENABLE_GST)
68 G_MODULE_EXPORT gboolean live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data);
69 static GstBusSyncReply create_window(GstBus *bus, GstMessage *msg, 
70                                 gpointer data);
71 #endif
72
73 G_MODULE_EXPORT gboolean preview_expose_cb(GtkWidget *widget, GdkEventExpose *event, 
74                                 signal_user_data_t *ud);
75
76 void
77 ghb_screen_par(signal_user_data_t *ud, gint *par_n, gint *par_d)
78 {
79 #if defined(_ENABLE_GST)
80         GValue disp_par = {0,};
81         GstElement *xover;
82         GObjectClass *klass;
83         GParamSpec *pspec;
84
85         g_value_init(&disp_par, GST_TYPE_FRACTION);
86         gst_value_set_fraction(&disp_par, 1, 1);
87         g_object_get(ud->preview->play, "video-sink", &xover, NULL);
88         if (xover == NULL)
89                 goto fail;
90
91         klass = G_OBJECT_GET_CLASS(xover);
92         if (klass == NULL)
93                 goto fail;
94
95         pspec = g_object_class_find_property(klass, "pixel-aspect_ratio");
96         if (pspec)
97         {
98                 GValue par_prop = {0,};
99
100                 g_value_init(&par_prop, pspec->value_type);
101                 g_object_get_property(G_OBJECT(xover), "pixel-aspect-ratio",
102                                                                 &par_prop);
103                 if (!g_value_transform(&par_prop, &disp_par))
104                 {
105                         g_warning("transform failed");
106                         gst_value_set_fraction(&disp_par, 1, 1);
107                 }
108                 g_value_unset(&par_prop);
109         }
110         *par_n = gst_value_get_fraction_numerator(&disp_par);
111         *par_d = gst_value_get_fraction_denominator(&disp_par);
112         g_value_unset(&disp_par);
113         return;
114
115 fail:
116         *par_n = 1;
117         *par_d = 1;
118 #else
119         *par_n = 1;
120         *par_d = 1;
121 #endif
122 }
123
124 void
125 ghb_par_scale(signal_user_data_t *ud, gint *width, gint *height, gint par_n, gint par_d)
126 {
127         gint disp_par_n, disp_par_d;
128         gint64 num, den;
129
130         ghb_screen_par(ud, &disp_par_n, &disp_par_d);
131         if (disp_par_n < 1) disp_par_n = 1;
132         if (disp_par_d < 1) disp_par_d = 1;
133         num = par_n * disp_par_d;
134         den = par_d * disp_par_n;
135
136         if (par_n > par_d)
137                 *width = *width * num / den;
138         else
139                 *height = *height * den / num;
140 }
141
142 void
143 ghb_preview_init(signal_user_data_t *ud)
144 {
145         ud->preview = g_malloc0(sizeof(preview_t));
146         ud->preview->view = GHB_WIDGET(ud->builder, "preview_image");
147         gtk_widget_realize(ud->preview->view);
148         g_signal_connect(G_OBJECT(ud->preview->view), "expose_event",
149                                         G_CALLBACK(preview_expose_cb), ud);
150
151         ud->preview->pause = TRUE;
152         ud->preview->encode_frame = -1;
153         ud->preview->live_id = -1;
154
155 #if defined(_ENABLE_GST)
156         GstBus *bus;
157         GstElement *xover;
158
159         ud->preview->xid = GDK_DRAWABLE_XID(ud->preview->view->window);
160         ud->preview->play = gst_element_factory_make("playbin", "play");
161         //xover = gst_element_factory_make("xvimagesink", "xover");
162         //xover = gst_element_factory_make("ximagesink", "xover");
163         xover = gst_element_factory_make("gconfvideosink", "xover");
164         if (xover == NULL)
165         {
166                 GtkWidget *widget = GHB_WIDGET(ud->builder, "live_preview_box");
167                 gtk_widget_hide (widget);
168                 widget = GHB_WIDGET(ud->builder, "live_preview_duration_box");
169                 gtk_widget_hide (widget);
170                 return;
171         }
172
173         g_object_set(G_OBJECT(ud->preview->play), "video-sink", xover, NULL);
174         //g_object_set(G_OBJECT(xover), "force-aspect-ratio", TRUE, NULL);
175
176         bus = gst_pipeline_get_bus(GST_PIPELINE(ud->preview->play));
177         gst_bus_add_watch(bus, live_preview_cb, ud);
178         gst_bus_set_sync_handler(bus, create_window, ud->preview);
179         gst_object_unref(bus);
180 #else
181         GtkWidget *widget = GHB_WIDGET(ud->builder, "live_preview_box");
182         gtk_widget_hide (widget);
183         widget = GHB_WIDGET(ud->builder, "live_preview_duration_box");
184         gtk_widget_hide (widget);
185 #endif
186 }
187
188 void
189 ghb_preview_cleanup(signal_user_data_t *ud)
190 {
191         if (ud->preview->current)
192         {
193                 ud->preview->current = NULL;
194                 g_free(ud->preview->current);
195         }
196 }
197
198 #if defined(_ENABLE_GST)
199 static GstBusSyncReply
200 create_window(GstBus *bus, GstMessage *msg, gpointer data)
201 {
202         preview_t *preview = (preview_t*)data;
203
204         switch (GST_MESSAGE_TYPE(msg))
205         {
206         case GST_MESSAGE_ELEMENT:
207         {
208                 if (!gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
209                         return GST_BUS_PASS;
210                 gst_x_overlay_set_xwindow_id(
211                         GST_X_OVERLAY(GST_MESSAGE_SRC(msg)), preview->xid);
212                 gst_message_unref(msg);
213                 return GST_BUS_DROP;
214         } break;
215
216         default:
217         {
218         } break;
219         }
220         return GST_BUS_PASS;
221 }
222
223 static GList *
224 get_stream_info_objects_for_type (GstElement *play, const gchar *typestr)
225 {
226         GValueArray *info_arr = NULL;
227         GList *ret = NULL;
228         guint ii;
229
230         if (play == NULL)
231                 return NULL;
232
233         g_object_get(play, "stream-info-value-array", &info_arr, NULL);
234         if (info_arr == NULL)
235                 return NULL;
236
237         for (ii = 0; ii < info_arr->n_values; ++ii) 
238         {
239                 GObject *info_obj;
240                 GValue *val;
241
242                 val = g_value_array_get_nth(info_arr, ii);
243                 info_obj = g_value_get_object(val);
244                 if (info_obj) 
245                 {
246                         GParamSpec *pspec;
247                         GEnumValue *value;
248                         gint type = -1;
249
250                         g_object_get(info_obj, "type", &type, NULL);
251                         pspec = g_object_class_find_property(
252                                                 G_OBJECT_GET_CLASS (info_obj), "type");
253                         value = g_enum_get_value(
254                                                 G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
255                         if (value) 
256                         {
257                                 if (g_ascii_strcasecmp (value->value_nick, typestr) == 0 ||
258                                         g_ascii_strcasecmp (value->value_name, typestr) == 0) 
259                                 {
260                                         ret = g_list_prepend (ret, g_object_ref (info_obj));
261                                 }
262                         }
263                 }
264         }
265         g_value_array_free (info_arr);
266         return g_list_reverse (ret);
267 }
268
269 static void
270 caps_set(GstCaps *caps, signal_user_data_t *ud)
271 {
272         GstStructure *ss;
273
274         ss = gst_caps_get_structure(caps, 0);
275         if (ss)
276         {
277                 gint fps_n, fps_d, width, height;
278                 guint num, den, par_n, par_d;
279                 gint disp_par_n, disp_par_d;
280                 const GValue *par;
281
282                 gst_structure_get_fraction(ss, "framerate", &fps_n, &fps_d);
283                 gst_structure_get_int(ss, "width", &width);
284                 gst_structure_get_int(ss, "height", &height);
285                 par = gst_structure_get_value(ss, "pixel-aspect-ratio");
286                 par_n = gst_value_get_fraction_numerator(par);
287                 par_d = gst_value_get_fraction_denominator(par);
288
289                 ghb_screen_par(ud, &disp_par_n, &disp_par_d);
290                 gst_video_calculate_display_ratio(
291                         &num, &den, width, height, par_n, par_d, disp_par_n, disp_par_d);
292
293                 if (par_n > par_d)
294                         width = gst_util_uint64_scale_int(height, num, den);
295                 else
296                         height = gst_util_uint64_scale_int(width, den, num);
297
298                 if (ghb_settings_get_boolean(ud->settings, "reduce_hd_preview"))
299                 {
300                         GdkScreen *ss;
301                         gint s_w, s_h;
302
303                         ss = gdk_screen_get_default();
304                         s_w = gdk_screen_get_width(ss);
305                         s_h = gdk_screen_get_height(ss);
306
307                         if (width > s_w * 80 / 100)
308                         {
309                                 width = s_w * 80 / 100;
310                                 height = gst_util_uint64_scale_int(width, den, num);
311                         }
312                         if (height > s_h * 80 / 100)
313                         {
314                                 height = s_h * 80 / 100;
315                                 width = gst_util_uint64_scale_int(height, num, den);
316                         }
317                 }
318                 
319                 if (width != ud->preview->width || height != ud->preview->height)
320                 {
321                         gtk_widget_set_size_request(ud->preview->view, width, height);
322                         ud->preview->width = width;
323                         ud->preview->height = height;
324                 }
325         }
326 }
327
328 static void
329 update_stream_info(signal_user_data_t *ud)
330 {
331         GList *vstreams, *ll;
332         GstPad *vpad = NULL;
333
334         vstreams = get_stream_info_objects_for_type(ud->preview->play, "video");
335         if (vstreams)
336         {
337                 for (ll = vstreams; vpad == NULL && ll != NULL; ll = ll->next)
338                 {
339                         g_object_get(ll->data, "object", &vpad, NULL);
340                 }
341         }
342         if (vpad)
343         {
344                 GstCaps *caps;
345
346                 caps = gst_pad_get_negotiated_caps(vpad);
347                 if (caps)
348                 {
349                         caps_set(caps, ud);
350                         gst_caps_unref(caps);
351                 }
352                 //g_signal_connect(vpad, "notify::caps", G_CALLBACK(caps_set_cb), preview);
353                 gst_object_unref(vpad);
354         }
355         g_list_foreach(vstreams, (GFunc)g_object_unref, NULL);
356         g_list_free(vstreams);
357 }
358
359 G_MODULE_EXPORT gboolean
360 live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data)
361 {
362         signal_user_data_t *ud = (signal_user_data_t*)data;
363
364         switch (GST_MESSAGE_TYPE(msg))
365         {
366         case GST_MESSAGE_ERROR:
367         {
368                 GError *err;
369                 gchar *debug;
370
371                 gst_message_parse_error(msg, &err, &debug);
372                 g_warning("Gstreamer Error: %s", err->message);
373                 g_error_free(err);
374                 g_free(debug);
375         } break;
376
377         case GST_MESSAGE_ELEMENT:
378         {
379                 if (gst_is_missing_plugin_message(msg))
380                 {
381                         gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
382                         gchar *message, *desc;
383                         desc = gst_missing_plugin_message_get_description(msg);
384                         message = g_strdup_printf(
385                                                 "Missing GStreamer plugin\n"
386                                                 "Audio or Video may not play as expected\n\n%s",
387                                                 desc);
388                         ghb_message_dialog(GTK_MESSAGE_WARNING, message, "Ok", NULL);
389                         g_free(message);
390                         gst_element_set_state(ud->preview->play, GST_STATE_PLAYING);
391                 }
392         } break;
393
394         case GST_MESSAGE_STATE_CHANGED:
395         {
396                 GstState state, pending;
397                 gst_element_get_state(ud->preview->play, &state, &pending, 0);
398                 if (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING)
399                 {
400                         update_stream_info(ud);
401                 }
402         } break;
403
404         case GST_MESSAGE_EOS:
405         {
406                 // Done
407                 GtkImage *img;
408
409                 img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
410                 gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
411                 gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
412                 ud->preview->pause = TRUE;
413                 gst_element_seek(ud->preview->play, 1.0,
414                         GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
415                         GST_SEEK_TYPE_SET, 0,
416                         GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
417         } break;
418
419         default:
420         {
421                 // Ignore
422         }
423         }
424         return TRUE;
425 }
426
427 void
428 live_preview_start(signal_user_data_t *ud)
429 {
430         GtkImage *img;
431         gchar *uri;
432
433         img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
434         if (!ud->preview->encoded[ud->preview->frame])
435         {
436                 gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
437                 gst_element_set_state(ud->preview->play, GST_STATE_NULL);
438                 ud->preview->pause = TRUE;
439                 return;
440         }
441
442         uri = g_strdup_printf("file://%s", ud->preview->current);
443         gtk_image_set_from_stock(img, "gtk-media-pause", GTK_ICON_SIZE_BUTTON);
444         ud->preview->state = PREVIEW_STATE_LIVE;
445         g_object_set(G_OBJECT(ud->preview->play), "uri", uri, NULL);
446         gst_element_set_state(ud->preview->play, GST_STATE_PLAYING);
447         ud->preview->pause = FALSE;
448         g_free(uri);
449 }
450
451 void
452 live_preview_pause(signal_user_data_t *ud)
453 {
454         GtkImage *img;
455
456         img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
457         gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
458         gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
459         ud->preview->pause = TRUE;
460 }
461 #endif
462
463 void
464 live_preview_stop(signal_user_data_t *ud)
465 {
466         GtkImage *img;
467         GtkRange *progress;
468
469         img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
470         gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
471 #if defined(_ENABLE_GST)
472         gst_element_set_state(ud->preview->play, GST_STATE_NULL);
473 #endif
474         ud->preview->pause = TRUE;
475         ud->preview->state = PREVIEW_STATE_IMAGE;
476
477         progress = GTK_RANGE(GHB_WIDGET(ud->builder, "live_preview_progress"));
478         gtk_range_set_value(progress, 0);
479 }
480
481 void
482 ghb_live_reset(signal_user_data_t *ud)
483 {
484         gboolean encoded;
485
486         if (ud->preview->live_id >= 0)
487         {
488                 ghb_stop_live_encode();
489         }
490         ud->preview->live_id = -1;
491         ud->preview->encode_frame = -1;
492         if (!ud->preview->pause)
493                 live_preview_stop(ud);
494         if (ud->preview->current)
495         {
496                 g_free(ud->preview->current);
497                 ud->preview->current = NULL;
498         }
499         encoded = ud->preview->encoded[ud->preview->frame];
500         memset(ud->preview->encoded, 0, sizeof(gboolean) * 10);
501         if (encoded)
502                 ghb_set_preview_image(ud);
503 }
504
505 extern void hb_get_tempory_directory(hb_handle_t *h, char path[512]);
506
507 G_MODULE_EXPORT void
508 live_preview_start_cb(GtkWidget *xwidget, signal_user_data_t *ud)
509 {
510         gchar *tmp_dir;
511         gchar *name;
512         gint frame = ud->preview->frame;
513
514         tmp_dir = ghb_get_tmp_dir();
515         name = g_strdup_printf("%s/live%02d", tmp_dir, ud->preview->frame);
516         if (ud->preview->current)
517                 g_free(ud->preview->current);
518         ud->preview->current = name;
519
520         if (ud->preview->encoded[frame] &&
521                 g_file_test(name, G_FILE_TEST_IS_REGULAR))
522         {
523 #if defined(_ENABLE_GST)
524                 if (ud->preview->pause)
525                         live_preview_start(ud);
526                 else
527                         live_preview_pause(ud);
528 #endif
529         }
530         else
531         {
532                 GValue *js;
533
534                 ud->preview->encode_frame = frame;
535                 js = ghb_value_dup(ud->settings);
536                 ghb_settings_set_string(js, "destination", name);
537                 ghb_settings_set_int(js, "start_frame", ud->preview->frame);
538                 ud->preview->live_id = 0;
539                 ghb_add_live_job(js, ud->preview->live_id);
540                 ghb_start_live_encode();
541                 ghb_value_free(js);
542         }
543 }
544
545 void
546 ghb_live_encode_done(signal_user_data_t *ud, gboolean success)
547 {
548         GtkWidget *widget;
549         GtkWidget *prog;
550
551         ud->preview->live_id = -1;
552         prog = GHB_WIDGET(ud->builder, "live_encode_progress");
553         if (success && 
554                 ud->preview->encode_frame == ud->preview->frame)
555         {
556                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), "Done");
557                 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 1);
558                 ud->preview->encoded[ud->preview->encode_frame] = TRUE;
559 #if defined(_ENABLE_GST)
560                 live_preview_start(ud);
561 #endif
562                 widget = GHB_WIDGET(ud->builder, "live_progress_box");
563                 gtk_widget_hide (widget);
564                 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
565                 gtk_widget_show (widget);
566         }
567         else
568         {
569                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), "");
570                 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 0);
571                 ud->preview->encoded[ud->preview->encode_frame] = FALSE;
572         }
573 }
574
575 #if defined(_ENABLE_GST)
576 G_MODULE_EXPORT gboolean
577 unlock_progress_cb(signal_user_data_t *ud)
578 {
579         ud->preview->progress_lock = FALSE;
580         // This function is initiated by g_idle_add.  Must return false
581         // so that it is not called again
582         return FALSE;
583 }
584 #endif
585
586 void
587 ghb_live_preview_progress(signal_user_data_t *ud)
588 {
589 #if defined(_ENABLE_GST)
590         GstFormat fmt = GST_FORMAT_TIME;
591         gint64 len = -1, pos = -1;
592
593         if (ud->preview->state != PREVIEW_STATE_LIVE || ud->preview->seek_lock)
594                 return;
595
596         ud->preview->progress_lock = TRUE;
597         if (gst_element_query_duration(ud->preview->play, &fmt, &len))
598         {
599                 if (len != -1 && fmt == GST_FORMAT_TIME)
600                 {
601                         ud->preview->len = len / GST_MSECOND;
602                 }
603         }
604         if (gst_element_query_position(ud->preview->play, &fmt, &pos))
605         {
606                 if (pos != -1 && fmt == GST_FORMAT_TIME)
607                 {
608                         ud->preview->pos = pos / GST_MSECOND;
609                 }
610         }
611         if (ud->preview->len > 0)
612         {
613                 GtkRange *progress;
614                 gdouble percent;
615
616                 percent = (gdouble)ud->preview->pos * 100 / ud->preview->len;
617                 progress = GTK_RANGE(GHB_WIDGET(ud->builder, "live_preview_progress"));
618                 gtk_range_set_value(progress, percent);
619         }
620         g_idle_add((GSourceFunc)unlock_progress_cb, ud);
621 #endif
622 }
623
624 #if defined(_ENABLE_GST)
625 G_MODULE_EXPORT gboolean
626 unlock_seek_cb(signal_user_data_t *ud)
627 {
628         ud->preview->seek_lock = FALSE;
629         // This function is initiated by g_idle_add.  Must return false
630         // so that it is not called again
631         return FALSE;
632 }
633 #endif
634
635 G_MODULE_EXPORT void
636 live_preview_seek_cb(GtkWidget *widget, signal_user_data_t *ud)
637 {
638 #if defined(_ENABLE_GST)
639         gdouble dval;
640         gint64 pos;
641
642         if (ud->preview->progress_lock)
643                 return;
644
645         ud->preview->seek_lock = TRUE;
646         dval = gtk_range_get_value(GTK_RANGE(widget));
647         pos = ((ud->preview->len * dval) / 100) * GST_MSECOND;
648         gst_element_seek(ud->preview->play, 1.0,
649                 GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
650                 GST_SEEK_TYPE_SET, pos,
651                 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
652         g_idle_add((GSourceFunc)unlock_seek_cb, ud);
653 #endif
654 }
655
656 void
657 ghb_set_preview_image(signal_user_data_t *ud)
658 {
659         GtkWidget *widget;
660         gint preview_width, preview_height, target_height, width, height;
661
662         g_debug("set_preview_button_image ()");
663         gint titleindex;
664
665         live_preview_stop(ud);
666
667         titleindex = ghb_settings_combo_int(ud->settings, "title");
668         if (titleindex < 0) return;
669         widget = GHB_WIDGET (ud->builder, "preview_frame");
670         ud->preview->frame = ghb_widget_int(widget) - 1;
671         if (ud->preview->encoded[ud->preview->frame])
672         {
673                 widget = GHB_WIDGET(ud->builder, "live_progress_box");
674                 gtk_widget_hide (widget);
675                 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
676                 gtk_widget_show (widget);
677         }
678         else
679         {
680                 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
681                 gtk_widget_hide (widget);
682                 widget = GHB_WIDGET(ud->builder, "live_progress_box");
683                 gtk_widget_show (widget);
684                 widget = GHB_WIDGET(ud->builder, "live_encode_progress");
685                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(widget), "");
686                 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(widget), 0);
687         }
688         if (ud->preview->pix != NULL)
689                 g_object_unref(ud->preview->pix);
690
691         ud->preview->pix = 
692                 ghb_get_preview_image(titleindex, ud->preview->frame, 
693                                                                 ud, &width, &height);
694         if (ud->preview->pix == NULL) return;
695         preview_width = gdk_pixbuf_get_width(ud->preview->pix);
696         preview_height = gdk_pixbuf_get_height(ud->preview->pix);
697         widget = GHB_WIDGET (ud->builder, "preview_image");
698         if (preview_width != ud->preview->width || 
699                 preview_height != ud->preview->height)
700         {
701                 gtk_widget_set_size_request(widget, preview_width, preview_height);
702                 ud->preview->width = preview_width;
703                 ud->preview->height = preview_height;
704         }
705         gdk_draw_pixbuf(
706                 widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
707                 -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
708
709         gchar *text = g_strdup_printf("%d x %d", width, height);
710         widget = GHB_WIDGET (ud->builder, "preview_dims");
711         gtk_label_set_text(GTK_LABEL(widget), text);
712         g_free(text);
713         
714         g_debug("preview %d x %d", preview_width, preview_height);
715         target_height = MIN(ud->preview->button_height, 128);
716         height = target_height;
717         width = preview_width * height / preview_height;
718
719         if ((height >= 16) && (width >= 16))
720         {
721                 GdkPixbuf *scaled_preview;
722                 scaled_preview = gdk_pixbuf_scale_simple (ud->preview->pix, width, 
723                                                                                                 height, GDK_INTERP_NEAREST);
724                 if (scaled_preview != NULL)
725                 {
726                         widget = GHB_WIDGET (ud->builder, "preview_button_image");
727                         gtk_image_set_from_pixbuf(GTK_IMAGE(widget), scaled_preview);
728                         g_object_unref (scaled_preview);
729                 }
730         }
731 }
732
733 #if defined(_ENABLE_GST)
734 G_MODULE_EXPORT gboolean
735 delayed_expose_cb(signal_user_data_t *ud)
736 {
737         GstElement *vsink;
738         GstXOverlay *xover;
739
740         g_object_get(ud->preview->play, "video-sink", &vsink, NULL);
741         if (vsink == NULL)
742                 return FALSE;
743
744         if (GST_IS_BIN(vsink))
745                 xover = GST_X_OVERLAY(gst_bin_get_by_interface(
746                                                                 GST_BIN(vsink), GST_TYPE_X_OVERLAY));
747         else
748                 xover = GST_X_OVERLAY(vsink);
749         gst_x_overlay_expose(xover);
750         // This function is initiated by g_idle_add.  Must return false
751         // so that it is not called again
752         return FALSE;
753 }
754 #endif
755
756 G_MODULE_EXPORT gboolean
757 preview_expose_cb(
758         GtkWidget *widget, 
759         GdkEventExpose *event, 
760         signal_user_data_t *ud)
761 {
762 #if defined(_ENABLE_GST)
763         if (ud->preview->state == PREVIEW_STATE_LIVE)
764         {
765                 if (GST_STATE(ud->preview->play) >= GST_STATE_PAUSED)
766                 {
767                         GstElement *vsink;
768                         GstXOverlay *xover;
769
770                         g_object_get(ud->preview->play, "video-sink", &vsink, NULL);
771                         if (GST_IS_BIN(vsink))
772                                 xover = GST_X_OVERLAY(gst_bin_get_by_interface(
773                                                                                 GST_BIN(vsink), GST_TYPE_X_OVERLAY));
774                         else
775                                 xover = GST_X_OVERLAY(vsink);
776                         gst_x_overlay_expose(xover);
777                         // For some reason, the exposed region doesn't always get
778                         // cleaned up here. But a delayed gst_x_overlay_expose()
779                         // takes care of it.
780                         g_idle_add((GSourceFunc)delayed_expose_cb, ud);
781                         return FALSE;
782                 }
783                 return TRUE;
784         }
785 #endif
786
787         if (ud->preview->pix != NULL)
788         {
789                 gdk_draw_pixbuf(
790                         widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
791                         -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
792         }
793         return TRUE;
794 }
795
796 G_MODULE_EXPORT void
797 preview_button_size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation, signal_user_data_t *ud)
798 {
799         g_debug("allocate %d x %d", allocation->width, allocation->height);
800         if (ud->preview->button_width == allocation->width &&
801                 ud->preview->button_height == allocation->height)
802         {
803                 // Nothing to do. Bug out.
804                 g_debug("nothing to do");
805                 return;
806         }
807         g_debug("prev allocate %d x %d", ud->preview->button_width, 
808                         ud->preview->button_height);
809         ud->preview->button_width = allocation->width;
810         ud->preview->button_height = allocation->height;
811         ghb_set_preview_image(ud);
812 }
813
814 static void
815 set_visible(GtkWidget *widget, gboolean visible)
816 {
817         if (visible)
818         {
819                 gtk_widget_show_now(widget);
820         }
821         else
822         {
823                 gtk_widget_hide(widget);
824         }
825 }
826
827 G_MODULE_EXPORT void
828 preview_button_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
829 {
830         gint titleindex;
831
832         g_debug("preview_button_clicked_cb()");
833         titleindex = ghb_settings_combo_int(ud->settings, "title");
834         if (titleindex >= 0)
835         {
836                 gint x, y;
837                 GtkWidget *widget = GHB_WIDGET (ud->builder, "preview_window");
838                 x = ghb_settings_get_int(ud->settings, "preview_x");
839                 y = ghb_settings_get_int(ud->settings, "preview_y");
840                 if (x >= 0 && y >= 0)
841                         gtk_window_move(GTK_WINDOW(widget), x, y);
842                 set_visible(widget, gtk_toggle_button_get_active(
843                                                         GTK_TOGGLE_BUTTON(xwidget)));
844         }
845         ghb_widget_to_setting (ud->settings, xwidget);
846         ghb_check_dependency(ud, xwidget);
847         const gchar *name = gtk_widget_get_name(xwidget);
848         ghb_pref_save(ud->settings, name);
849 }
850
851 G_MODULE_EXPORT void
852 picture_settings_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
853 {
854         GtkWidget *widget;
855         gboolean active;
856         gint x, y;
857
858         g_debug("picture_settings_clicked_cb()");
859         widget = GHB_WIDGET (ud->builder, "settings_window");
860         active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(xwidget));
861         x = ghb_settings_get_int(ud->settings, "settings_x");
862         y = ghb_settings_get_int(ud->settings, "settings_y");
863         if (x >= 0 && y >= 0)
864                 gtk_window_move(GTK_WINDOW(widget), x, y);
865         set_visible(widget, active);
866         if (ghb_settings_get_boolean(ud->settings, "show_preview"))
867         {
868                 widget = GHB_WIDGET (ud->builder, "preview_window");
869                 x = ghb_settings_get_int(ud->settings, "preview_x");
870                 y = ghb_settings_get_int(ud->settings, "preview_y");
871                 if (x >= 0 && y >= 0)
872                         gtk_window_move(GTK_WINDOW(widget), x, y);
873                 set_visible(widget, active);
874                 // The window may be hidden behind the main window, raise it
875                 if (active)
876                         gtk_window_present(GTK_WINDOW(widget));
877         }
878 }
879
880 G_MODULE_EXPORT void
881 picture_settings_alt_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
882 {
883         GtkWidget *toggle;
884         gboolean active;
885
886         g_debug("picture_settings_alt_clicked_cb()");
887         toggle = GHB_WIDGET (ud->builder, "show_picture");
888         active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle));
889         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toggle), !active);
890 }
891
892 static gboolean
893 go_full(signal_user_data_t *ud)
894 {
895         GtkWindow *window;
896         window = GTK_WINDOW(GHB_WIDGET (ud->builder, "preview_window"));
897         gtk_window_fullscreen(window);
898         ghb_set_preview_image(ud);
899         return FALSE;
900 }
901
902 G_MODULE_EXPORT void
903 fullscreen_clicked_cb(GtkWidget *toggle, signal_user_data_t *ud)
904 {
905         gboolean active;
906         GtkWindow *window;
907
908         g_debug("fullscreen_clicked_cb()");
909         ghb_widget_to_setting (ud->settings, toggle);
910         ghb_check_dependency(ud, toggle);
911         const gchar *name = gtk_widget_get_name(toggle);
912         ghb_pref_save(ud->settings, name);
913
914         window = GTK_WINDOW(GHB_WIDGET (ud->builder, "preview_window"));
915         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle));
916         if (active)
917         {
918                 gtk_window_set_resizable(window, TRUE);
919                 gtk_button_set_label(GTK_BUTTON(toggle), "Windowed");
920                 // Changing resizable property doesn't take effect immediately
921                 // need to delay fullscreen till after this callback returns
922                 // to mainloop
923                 g_idle_add((GSourceFunc)go_full, ud);
924         }
925         else
926         {
927                 gtk_window_unfullscreen(window);
928                 gtk_window_set_resizable(window, FALSE);
929                 gtk_button_set_label(GTK_BUTTON(toggle), "Fullscreen");
930                 ghb_set_preview_image(ud);
931         }
932 }
933
934 G_MODULE_EXPORT void
935 picture_settings_alt2_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
936 {
937         GtkWidget *toggle;
938         gboolean active;
939         gint signal_id;
940         gint handler_id = 0;
941
942         g_debug("picture_settings_alt2_clicked_cb()");
943         toggle = GHB_WIDGET (ud->builder, "show_picture");
944         active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle));
945         if (active)
946         {
947                 // I don't want deleting the settings window to also remove the
948                 // preview window, but changing the toggle will do this, so temporarily
949                 // ignore the toggled signal
950                 signal_id = g_signal_lookup("toggled", GTK_TYPE_TOGGLE_TOOL_BUTTON);
951                 if (signal_id > 0)
952                 {
953                         // Valid signal id found.  This should always succeed.
954                         handler_id = g_signal_handler_find((gpointer)toggle, 
955                                                                                                 G_SIGNAL_MATCH_ID, 
956                                                                                                 signal_id, 0, 0, 0, 0);
957                         if (handler_id > 0)
958                         {
959                                 // This should also always succeed
960                                 g_signal_handler_block ((gpointer)toggle, handler_id);
961                         }
962                 }
963         }
964
965         GtkWidget *widget = GHB_WIDGET (ud->builder, "settings_window");
966         gint x, y;
967
968         x = ghb_settings_get_int(ud->settings, "settings_x");
969         y = ghb_settings_get_int(ud->settings, "settings_y");
970         if (x >= 0 && y >= 0)
971                 gtk_window_move(GTK_WINDOW(widget), x, y);
972         set_visible(widget, !active);
973         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toggle), !active);
974
975         if (handler_id > 0)
976         {
977                 g_signal_handler_unblock ((gpointer)toggle, handler_id);
978         }
979 }
980
981 void
982 preview_frame_value_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
983 {
984         if (ud->preview->live_id >= 0)
985         {
986                 ghb_stop_live_encode();
987                 ud->preview->live_id = -1;
988                 ud->preview->encode_frame = -1;
989         }
990         ghb_set_preview_image(ud);
991 }
992
993 G_MODULE_EXPORT gboolean
994 preview_window_delete_cb(
995         GtkWidget *widget, 
996         GdkEvent *event, 
997         signal_user_data_t *ud)
998 {
999         live_preview_stop(ud);
1000         gtk_widget_hide(widget);
1001         return TRUE;
1002 }
1003
1004 gboolean
1005 settings_window_delete_cb(
1006         GtkWidget *widget, 
1007         GdkEvent *event, 
1008         signal_user_data_t *ud)
1009 {
1010         gint signal_id;
1011         gint handler_id = 0;
1012
1013         gtk_widget_hide(widget);
1014         widget = GHB_WIDGET (ud->builder, "show_picture");
1015
1016         // I don't want deleting the settings window to also remove the
1017         // preview window, but changing the toggle will do this, so temporarily
1018         // ignore the toggled signal
1019         signal_id = g_signal_lookup("toggled", GTK_TYPE_TOGGLE_TOOL_BUTTON);
1020         if (signal_id > 0)
1021         {
1022                 // Valid signal id found.  This should always succeed.
1023                 handler_id = g_signal_handler_find((gpointer)widget, G_SIGNAL_MATCH_ID, 
1024                                                                                         signal_id, 0, 0, 0, 0);
1025                 if (handler_id > 0)
1026                 {
1027                         // This should also always succeed
1028                         g_signal_handler_block ((gpointer)widget, handler_id);
1029                 }
1030         }
1031
1032         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE);
1033
1034         if (handler_id > 0)
1035         {
1036                 g_signal_handler_unblock ((gpointer)widget, handler_id);
1037         }
1038         return TRUE;
1039 }
1040
1041 G_MODULE_EXPORT void
1042 preview_duration_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
1043 {
1044         g_debug("preview_duration_changed_cb ()");
1045         ghb_live_reset(ud);
1046         ghb_widget_to_setting (ud->settings, widget);
1047         ghb_check_dependency(ud, widget);
1048         const gchar *name = gtk_widget_get_name(widget);
1049         ghb_pref_save(ud->settings, name);
1050 }
1051
1052 static guint hud_timeout_id = 0;
1053
1054 static gboolean
1055 hud_timeout(signal_user_data_t *ud)
1056 {
1057         GtkWidget *widget;
1058
1059         g_debug("hud_timeout()");
1060         widget = GHB_WIDGET(ud->builder, "preview_hud");
1061         gtk_widget_hide(widget);
1062         hud_timeout_id = 0;
1063         return FALSE;
1064 }
1065
1066 G_MODULE_EXPORT gboolean
1067 hud_enter_cb(
1068         GtkWidget *widget,
1069         GdkEventCrossing *event,
1070         signal_user_data_t *ud)
1071 {
1072         g_debug("hud_enter_cb()");
1073         if (hud_timeout_id != 0)
1074         {
1075                 GMainContext *mc;
1076                 GSource *source;
1077
1078                 mc = g_main_context_default();
1079                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1080                 if (source != NULL)
1081                         g_source_destroy(source);
1082         }
1083         widget = GHB_WIDGET(ud->builder, "preview_hud");
1084         gtk_widget_show(widget);
1085         hud_timeout_id = 0;
1086         return FALSE;
1087 }
1088
1089 G_MODULE_EXPORT gboolean
1090 preview_leave_cb(
1091         GtkWidget *widget,
1092         GdkEventCrossing *event,
1093         signal_user_data_t *ud)
1094 {
1095         g_debug("hud_leave_cb()");
1096         if (hud_timeout_id != 0)
1097         {
1098                 GMainContext *mc;
1099                 GSource *source;
1100
1101                 mc = g_main_context_default();
1102                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1103                 if (source != NULL)
1104                         g_source_destroy(source);
1105         }
1106         hud_timeout_id = g_timeout_add(300, (GSourceFunc)hud_timeout, ud);
1107         return FALSE;
1108 }
1109
1110 G_MODULE_EXPORT gboolean
1111 preview_motion_cb(
1112         GtkWidget *widget,
1113         GdkEventMotion *event,
1114         signal_user_data_t *ud)
1115 {
1116         //g_debug("hud_motion_cb %d", hud_timeout_id);
1117         if (hud_timeout_id != 0)
1118         {
1119                 GMainContext *mc;
1120                 GSource *source;
1121
1122                 mc = g_main_context_default();
1123                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1124                 if (source != NULL)
1125                         g_source_destroy(source);
1126         }
1127         widget = GHB_WIDGET(ud->builder, "preview_hud");
1128         if (!GTK_WIDGET_VISIBLE(widget))
1129         {
1130                 gtk_widget_show(widget);
1131         }
1132         hud_timeout_id = g_timeout_add_seconds(4, (GSourceFunc)hud_timeout, ud);
1133         return FALSE;
1134 }
1135
1136 GdkDrawable*
1137 ghb_curved_rect_mask(gint width, gint height, gint radius)
1138 {
1139         GdkDrawable *shape;
1140         cairo_t *cr;
1141         double w, h;
1142
1143         if (!width || !height)
1144         return NULL;
1145
1146         shape = (GdkDrawable *)gdk_pixmap_new (NULL, width, height, 1);
1147
1148         cr = gdk_cairo_create (shape);
1149
1150         w = width;
1151         h = height;
1152         if (radius > width / 2)
1153                 radius = width / 2;
1154         if (radius > height / 2)
1155                 radius = height / 2;
1156
1157         // fill shape with black
1158         cairo_save(cr);
1159         cairo_rectangle (cr, 0, 0, width, height);
1160         cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1161         cairo_fill (cr);
1162         cairo_restore (cr);
1163
1164         cairo_move_to  (cr, 0, radius);
1165         cairo_curve_to (cr, 0 , 0, 0 , 0, radius, 0);
1166         cairo_line_to (cr, w - radius, 0);
1167         cairo_curve_to (cr, w, 0, w, 0, w, radius);
1168         cairo_line_to (cr, w , h - radius);
1169         cairo_curve_to (cr, w, h, w, h, w - radius, h);
1170         cairo_line_to (cr, 0 + radius, h);
1171         cairo_curve_to (cr, 0, h, 0, h, 0, h - radius);
1172
1173         cairo_close_path(cr);
1174
1175         cairo_set_source_rgb(cr, 1, 1, 1);
1176         cairo_fill(cr);
1177
1178         cairo_destroy(cr);
1179
1180         return shape;
1181 }
1182
1183 G_MODULE_EXPORT void
1184 preview_hud_size_alloc_cb(
1185         GtkWidget *widget,
1186         GtkAllocation *allocation,
1187         signal_user_data_t *ud)
1188 {
1189         GdkDrawable *shape;
1190
1191         //g_message("preview_hud_size_alloc_cb()");
1192         if (GTK_WIDGET_VISIBLE(widget) && allocation->height > 50)
1193         {
1194                 shape = ghb_curved_rect_mask(allocation->width, 
1195                                                                         allocation->height, allocation->height/4);
1196                 if (shape != NULL)
1197                 {
1198                         gtk_widget_shape_combine_mask(widget, shape, 0, 0);
1199                         gdk_pixmap_unref(shape);
1200                 }
1201         }
1202 }
1203
1204 G_MODULE_EXPORT gboolean
1205 preview_configure_cb(
1206         GtkWidget *widget,
1207         GdkEventConfigure *event,
1208         signal_user_data_t *ud)
1209 {
1210         gint x, y;
1211
1212         //g_message("preview_configure_cb()");
1213         if (GTK_WIDGET_VISIBLE(widget))
1214         {
1215                 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
1216                 ghb_settings_set_int(ud->settings, "preview_x", x);
1217                 ghb_settings_set_int(ud->settings, "preview_y", y);
1218                 ghb_pref_set(ud->settings, "preview_x");
1219                 ghb_pref_set(ud->settings, "preview_y");
1220                 ghb_prefs_store();
1221         }
1222         return FALSE;
1223 }
1224
1225 G_MODULE_EXPORT gboolean
1226 settings_configure_cb(
1227         GtkWidget *widget,
1228         GdkEventConfigure *event,
1229         signal_user_data_t *ud)
1230 {
1231         gint x, y;
1232
1233         //g_message("settings_configure_cb()");
1234         if (GTK_WIDGET_VISIBLE(widget))
1235         {
1236                 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
1237                 ghb_settings_set_int(ud->settings, "settings_x", x);
1238                 ghb_settings_set_int(ud->settings, "settings_y", y);
1239                 ghb_pref_set(ud->settings, "settings_x");
1240                 ghb_pref_set(ud->settings, "settings_y");
1241                 ghb_prefs_store();
1242         }
1243         return FALSE;
1244 }
1245