OSDN Git Service

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