OSDN Git Service

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