OSDN Git Service

LinGui: create a queue lock file to prevent simultaneous access to the queue
[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, *toggle;
862         gboolean active, hide_settings;
863         gint x, y;
864
865         g_debug("picture_settings_clicked_cb()");
866         toggle = GHB_WIDGET (ud->builder, "hide_settings");
867         hide_settings = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle));
868
869         widget = GHB_WIDGET (ud->builder, "settings_window");
870         active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(xwidget));
871         x = ghb_settings_get_int(ud->settings, "settings_x");
872         y = ghb_settings_get_int(ud->settings, "settings_y");
873         if (x >= 0 && y >= 0)
874                 gtk_window_move(GTK_WINDOW(widget), x, y);
875         set_visible(widget, active && !hide_settings);
876         if (ghb_settings_get_boolean(ud->settings, "show_preview"))
877         {
878                 widget = GHB_WIDGET (ud->builder, "preview_window");
879                 x = ghb_settings_get_int(ud->settings, "preview_x");
880                 y = ghb_settings_get_int(ud->settings, "preview_y");
881                 if (x >= 0 && y >= 0)
882                         gtk_window_move(GTK_WINDOW(widget), x, y);
883                 set_visible(widget, active);
884                 // The window may be hidden behind the main window, raise it
885                 if (active)
886                         gtk_window_present(GTK_WINDOW(widget));
887         }
888 }
889
890 G_MODULE_EXPORT void
891 picture_settings_alt_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
892 {
893         GtkWidget *toggle;
894         gboolean active;
895
896         g_debug("picture_settings_alt_clicked_cb()");
897         toggle = GHB_WIDGET (ud->builder, "show_picture");
898         active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle));
899         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toggle), !active);
900 }
901
902 static gboolean
903 go_full(signal_user_data_t *ud)
904 {
905         GtkWindow *window;
906         window = GTK_WINDOW(GHB_WIDGET (ud->builder, "preview_window"));
907         gtk_window_fullscreen(window);
908         ghb_set_preview_image(ud);
909         return FALSE;
910 }
911
912 G_MODULE_EXPORT void
913 fullscreen_clicked_cb(GtkWidget *toggle, signal_user_data_t *ud)
914 {
915         gboolean active;
916         GtkWindow *window;
917
918         g_debug("fullscreen_clicked_cb()");
919         ghb_widget_to_setting (ud->settings, toggle);
920         ghb_check_dependency(ud, toggle, NULL);
921         const gchar *name = gtk_widget_get_name(toggle);
922         ghb_pref_save(ud->settings, name);
923
924         window = GTK_WINDOW(GHB_WIDGET (ud->builder, "preview_window"));
925         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle));
926         if (active)
927         {
928                 gtk_window_set_resizable(window, TRUE);
929                 gtk_button_set_label(GTK_BUTTON(toggle), "Windowed");
930                 // Changing resizable property doesn't take effect immediately
931                 // need to delay fullscreen till after this callback returns
932                 // to mainloop
933                 g_idle_add((GSourceFunc)go_full, ud);
934         }
935         else
936         {
937                 gtk_window_unfullscreen(window);
938                 gtk_window_set_resizable(window, FALSE);
939                 gtk_button_set_label(GTK_BUTTON(toggle), "Fullscreen");
940                 ghb_set_preview_image(ud);
941         }
942 }
943
944 G_MODULE_EXPORT void
945 picture_settings_alt2_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
946 {
947         GtkWidget *toggle;
948         gboolean active;
949         GtkWidget *window;
950
951         g_debug("picture_settings_alt2_clicked_cb()");
952         toggle = GHB_WIDGET (ud->builder, "hide_settings");
953         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle));
954         window = GHB_WIDGET(ud->builder, "settings_window");
955         if (!active)
956         {
957                 gtk_button_set_label(GTK_BUTTON(toggle), "Hide Settings");
958                 gtk_widget_set_tooltip_text(toggle, 
959                         "Hide the picture settings window while "
960                         "leaving the preview visible.");
961                 gtk_widget_show(window);
962         }
963         else
964         {
965                 gtk_button_set_label(GTK_BUTTON(toggle), "Show Settings");
966                 gtk_widget_set_tooltip_text(toggle, "Show picture settings.");
967                 gtk_widget_hide(window);
968         }
969 }
970
971 G_MODULE_EXPORT void
972 preview_frame_value_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
973 {
974         if (ud->preview->live_id >= 0)
975         {
976                 ghb_stop_live_encode();
977                 ud->preview->live_id = -1;
978                 ud->preview->encode_frame = -1;
979         }
980         ghb_set_preview_image(ud);
981 }
982
983 G_MODULE_EXPORT gboolean
984 preview_window_delete_cb(
985         GtkWidget *widget, 
986         GdkEvent *event, 
987         signal_user_data_t *ud)
988 {
989         live_preview_stop(ud);
990         widget = GHB_WIDGET (ud->builder, "show_picture");
991         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE);
992         return TRUE;
993 }
994
995 G_MODULE_EXPORT gboolean
996 settings_window_delete_cb(
997         GtkWidget *widget, 
998         GdkEvent *event, 
999         signal_user_data_t *ud)
1000 {
1001         live_preview_stop(ud);
1002         widget = GHB_WIDGET (ud->builder, "show_picture");
1003         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE);
1004
1005         return TRUE;
1006 }
1007
1008 G_MODULE_EXPORT void
1009 preview_duration_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
1010 {
1011         g_debug("preview_duration_changed_cb ()");
1012         ghb_live_reset(ud);
1013         ghb_widget_to_setting (ud->settings, widget);
1014         ghb_check_dependency(ud, widget, NULL);
1015         const gchar *name = gtk_widget_get_name(widget);
1016         ghb_pref_save(ud->settings, name);
1017 }
1018
1019 static guint hud_timeout_id = 0;
1020
1021 static gboolean
1022 hud_timeout(signal_user_data_t *ud)
1023 {
1024         GtkWidget *widget;
1025
1026         g_debug("hud_timeout()");
1027         widget = GHB_WIDGET(ud->builder, "preview_hud");
1028         gtk_widget_hide(widget);
1029         hud_timeout_id = 0;
1030         return FALSE;
1031 }
1032
1033 G_MODULE_EXPORT gboolean
1034 hud_enter_cb(
1035         GtkWidget *widget,
1036         GdkEventCrossing *event,
1037         signal_user_data_t *ud)
1038 {
1039         g_debug("hud_enter_cb()");
1040         if (hud_timeout_id != 0)
1041         {
1042                 GMainContext *mc;
1043                 GSource *source;
1044
1045                 mc = g_main_context_default();
1046                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1047                 if (source != NULL)
1048                         g_source_destroy(source);
1049         }
1050         widget = GHB_WIDGET(ud->builder, "preview_hud");
1051         gtk_widget_show(widget);
1052         hud_timeout_id = 0;
1053         return FALSE;
1054 }
1055
1056 G_MODULE_EXPORT gboolean
1057 preview_leave_cb(
1058         GtkWidget *widget,
1059         GdkEventCrossing *event,
1060         signal_user_data_t *ud)
1061 {
1062         g_debug("hud_leave_cb()");
1063         if (hud_timeout_id != 0)
1064         {
1065                 GMainContext *mc;
1066                 GSource *source;
1067
1068                 mc = g_main_context_default();
1069                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1070                 if (source != NULL)
1071                         g_source_destroy(source);
1072         }
1073         hud_timeout_id = g_timeout_add(300, (GSourceFunc)hud_timeout, ud);
1074         return FALSE;
1075 }
1076
1077 G_MODULE_EXPORT gboolean
1078 preview_motion_cb(
1079         GtkWidget *widget,
1080         GdkEventMotion *event,
1081         signal_user_data_t *ud)
1082 {
1083         //g_debug("hud_motion_cb %d", hud_timeout_id);
1084         if (hud_timeout_id != 0)
1085         {
1086                 GMainContext *mc;
1087                 GSource *source;
1088
1089                 mc = g_main_context_default();
1090                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1091                 if (source != NULL)
1092                         g_source_destroy(source);
1093         }
1094         widget = GHB_WIDGET(ud->builder, "preview_hud");
1095         if (!GTK_WIDGET_VISIBLE(widget))
1096         {
1097                 gtk_widget_show(widget);
1098         }
1099         hud_timeout_id = g_timeout_add_seconds(4, (GSourceFunc)hud_timeout, ud);
1100         return FALSE;
1101 }
1102
1103 GdkDrawable*
1104 ghb_curved_rect_mask(gint width, gint height, gint radius)
1105 {
1106         GdkDrawable *shape;
1107         cairo_t *cr;
1108         double w, h;
1109
1110         if (!width || !height)
1111                 return NULL;
1112
1113         shape = (GdkDrawable *)gdk_pixmap_new (NULL, width, height, 1);
1114
1115         cr = gdk_cairo_create (shape);
1116
1117         w = width;
1118         h = height;
1119         if (radius > width / 2)
1120                 radius = width / 2;
1121         if (radius > height / 2)
1122                 radius = height / 2;
1123
1124         // fill shape with black
1125         cairo_save(cr);
1126         cairo_rectangle (cr, 0, 0, width, height);
1127         cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1128         cairo_fill (cr);
1129         cairo_restore (cr);
1130
1131         cairo_move_to  (cr, 0, radius);
1132         cairo_curve_to (cr, 0 , 0, 0 , 0, radius, 0);
1133         cairo_line_to (cr, w - radius, 0);
1134         cairo_curve_to (cr, w, 0, w, 0, w, radius);
1135         cairo_line_to (cr, w , h - radius);
1136         cairo_curve_to (cr, w, h, w, h, w - radius, h);
1137         cairo_line_to (cr, 0 + radius, h);
1138         cairo_curve_to (cr, 0, h, 0, h, 0, h - radius);
1139
1140         cairo_close_path(cr);
1141
1142         cairo_set_source_rgb(cr, 1, 1, 1);
1143         cairo_fill(cr);
1144
1145         cairo_destroy(cr);
1146
1147         return shape;
1148 }
1149
1150 G_MODULE_EXPORT void
1151 preview_hud_size_alloc_cb(
1152         GtkWidget *widget,
1153         GtkAllocation *allocation,
1154         signal_user_data_t *ud)
1155 {
1156         GdkDrawable *shape;
1157
1158         //g_message("preview_hud_size_alloc_cb()");
1159         if (GTK_WIDGET_VISIBLE(widget) && allocation->height > 50)
1160         {
1161                 shape = ghb_curved_rect_mask(allocation->width, 
1162                                                                         allocation->height, allocation->height/4);
1163                 if (shape != NULL)
1164                 {
1165                         gtk_widget_shape_combine_mask(widget, shape, 0, 0);
1166                         gdk_pixmap_unref(shape);
1167                 }
1168         }
1169 }
1170
1171 G_MODULE_EXPORT gboolean
1172 preview_configure_cb(
1173         GtkWidget *widget,
1174         GdkEventConfigure *event,
1175         signal_user_data_t *ud)
1176 {
1177         gint x, y;
1178
1179         //g_message("preview_configure_cb()");
1180         if (GTK_WIDGET_VISIBLE(widget))
1181         {
1182                 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
1183                 ghb_settings_set_int(ud->settings, "preview_x", x);
1184                 ghb_settings_set_int(ud->settings, "preview_y", y);
1185                 ghb_pref_set(ud->settings, "preview_x");
1186                 ghb_pref_set(ud->settings, "preview_y");
1187                 ghb_prefs_store();
1188         }
1189         return FALSE;
1190 }
1191
1192 G_MODULE_EXPORT gboolean
1193 settings_configure_cb(
1194         GtkWidget *widget,
1195         GdkEventConfigure *event,
1196         signal_user_data_t *ud)
1197 {
1198         gint x, y;
1199
1200         //g_message("settings_configure_cb()");
1201         if (GTK_WIDGET_VISIBLE(widget))
1202         {
1203                 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
1204                 ghb_settings_set_int(ud->settings, "settings_x", x);
1205                 ghb_settings_set_int(ud->settings, "settings_y", y);
1206                 ghb_pref_set(ud->settings, "settings_x");
1207                 ghb_pref_set(ud->settings, "settings_y");
1208                 ghb_prefs_store();
1209         }
1210         return FALSE;
1211 }
1212