OSDN Git Service

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