1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
4 * Copyright (C) John Stebbins 2008 <stebbins@stebbins>
6 * preview.c is free software.
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)
16 #include <glib/gstdio.h>
17 #include <glib-object.h>
21 #include <gst/interfaces/xoverlay.h>
22 #include <gst/video/video.h>
23 #include <gst/pbutils/missing-plugins.h>
25 #include "callbacks.h"
26 #include "hb-backend.h"
31 #define PREVIEW_STATE_IMAGE 0
32 #define PREVIEW_STATE_LIVE 1
40 gboolean progress_lock;
56 static gboolean live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data);
57 static GstBusSyncReply create_window(GstBus *bus, GstMessage *msg,
59 gboolean preview_expose_cb(GtkWidget *widget, GdkEventExpose *event,
60 signal_user_data_t *ud);
63 ghb_screen_par(signal_user_data_t *ud, gint *par_n, gint *par_d)
65 GValue disp_par = {0,};
70 g_value_init(&disp_par, GST_TYPE_FRACTION);
71 gst_value_set_fraction(&disp_par, 1, 1);
72 g_object_get(ud->preview->play, "video-sink", &xover, NULL);
73 klass = G_OBJECT_GET_CLASS(xover);
74 pspec = g_object_class_find_property(klass, "pixel-aspect_ratio");
77 GValue par_prop = {0,};
79 g_value_init(&par_prop, pspec->value_type);
80 g_object_get_property(G_OBJECT(xover), "pixel-aspect-ratio",
82 if (!g_value_transform(&par_prop, &disp_par))
84 g_warning("transform failed");
85 gst_value_set_fraction(&disp_par, 1, 1);
87 g_value_unset(&par_prop);
89 *par_n = gst_value_get_fraction_numerator(&disp_par);
90 *par_d = gst_value_get_fraction_denominator(&disp_par);
91 g_value_unset(&disp_par);
95 ghb_par_scale(signal_user_data_t *ud, gint *width, gint *height, gint par_n, gint par_d)
97 gint disp_par_n, disp_par_d;
100 ghb_screen_par(ud, &disp_par_n, &disp_par_d);
101 num = par_n * disp_par_d;
102 den = par_d * disp_par_n;
105 *width = *width * num / den;
107 *height = *height * den / num;
111 ghb_preview_init(signal_user_data_t *ud)
116 ud->preview = g_malloc0(sizeof(preview_t));
117 ud->preview->view = GHB_WIDGET(ud->builder, "preview_image");
118 gtk_widget_realize(ud->preview->view);
119 g_signal_connect(G_OBJECT(ud->preview->view), "expose_event",
120 G_CALLBACK(preview_expose_cb), ud);
121 ud->preview->xid = GDK_DRAWABLE_XID(ud->preview->view->window);
123 ud->preview->play = gst_element_factory_make("playbin", "play");
124 ud->preview->pause = TRUE;
125 //xover = gst_element_factory_make("xvimagesink", "xover");
126 xover = gst_element_factory_make("gconfvideosink", "xover");
127 g_object_set(G_OBJECT(ud->preview->play), "video-sink", xover, NULL);
128 //g_object_set(G_OBJECT(xover), "force-aspect-ratio", TRUE, NULL);
130 bus = gst_pipeline_get_bus(GST_PIPELINE(ud->preview->play));
131 gst_bus_add_watch(bus, live_preview_cb, ud);
132 gst_bus_set_sync_handler(bus, create_window, ud->preview);
133 gst_object_unref(bus);
137 ghb_preview_cleanup(signal_user_data_t *ud)
139 if (ud->preview->current)
141 ud->preview->current = NULL;
142 g_free(ud->preview->current);
146 static GstBusSyncReply
147 create_window(GstBus *bus, GstMessage *msg, gpointer data)
149 preview_t *preview = (preview_t*)data;
151 switch (GST_MESSAGE_TYPE(msg))
153 case GST_MESSAGE_ELEMENT:
155 if (!gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
157 gst_x_overlay_set_xwindow_id(
158 GST_X_OVERLAY(GST_MESSAGE_SRC(msg)), preview->xid);
159 gst_message_unref(msg);
171 get_stream_info_objects_for_type (GstElement *play, const gchar *typestr)
173 GValueArray *info_arr = NULL;
180 g_object_get(play, "stream-info-value-array", &info_arr, NULL);
181 if (info_arr == NULL)
184 for (ii = 0; ii < info_arr->n_values; ++ii)
189 val = g_value_array_get_nth(info_arr, ii);
190 info_obj = g_value_get_object(val);
197 g_object_get(info_obj, "type", &type, NULL);
198 pspec = g_object_class_find_property(
199 G_OBJECT_GET_CLASS (info_obj), "type");
200 value = g_enum_get_value(
201 G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
204 if (g_ascii_strcasecmp (value->value_nick, typestr) == 0 ||
205 g_ascii_strcasecmp (value->value_name, typestr) == 0)
207 ret = g_list_prepend (ret, g_object_ref (info_obj));
212 g_value_array_free (info_arr);
213 return g_list_reverse (ret);
217 caps_set(GstCaps *caps, signal_user_data_t *ud)
221 ss = gst_caps_get_structure(caps, 0);
224 gint fps_n, fps_d, width, height;
225 guint num, den, par_n, par_d;
226 gint disp_par_n, disp_par_d;
229 gst_structure_get_fraction(ss, "framerate", &fps_n, &fps_d);
230 gst_structure_get_int(ss, "width", &width);
231 gst_structure_get_int(ss, "height", &height);
232 par = gst_structure_get_value(ss, "pixel-aspect-ratio");
233 par_n = gst_value_get_fraction_numerator(par);
234 par_d = gst_value_get_fraction_denominator(par);
236 ghb_screen_par(ud, &disp_par_n, &disp_par_d);
237 gst_video_calculate_display_ratio(
238 &num, &den, width, height, par_n, par_d, disp_par_n, disp_par_d);
241 width = gst_util_uint64_scale_int(height, num, den);
243 height = gst_util_uint64_scale_int(width, den, num);
245 if (ghb_settings_get_boolean(ud->settings, "reduce_hd_preview"))
250 ss = gdk_screen_get_default();
251 s_w = gdk_screen_get_width(ss);
252 s_h = gdk_screen_get_height(ss);
254 if (width > s_w * 80 / 100)
256 width = s_w * 80 / 100;
257 height = gst_util_uint64_scale_int(width, den, num);
259 if (height > s_h * 80 / 100)
261 height = s_h * 80 / 100;
262 width = gst_util_uint64_scale_int(height, num, den);
266 if (width != ud->preview->width || height != ud->preview->height)
268 gtk_widget_set_size_request(ud->preview->view, width, height);
269 ud->preview->width = width;
270 ud->preview->height = height;
276 update_stream_info(signal_user_data_t *ud)
278 GList *vstreams, *ll;
281 vstreams = get_stream_info_objects_for_type(ud->preview->play, "video");
284 for (ll = vstreams; vpad == NULL && ll != NULL; ll = ll->next)
286 g_object_get(ll->data, "object", &vpad, NULL);
293 caps = gst_pad_get_negotiated_caps(vpad);
297 gst_caps_unref(caps);
299 //g_signal_connect(vpad, "notify::caps", G_CALLBACK(caps_set_cb), preview);
300 gst_object_unref(vpad);
302 g_list_foreach(vstreams, (GFunc)g_object_unref, NULL);
303 g_list_free(vstreams);
307 live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data)
309 signal_user_data_t *ud = (signal_user_data_t*)data;
311 switch (GST_MESSAGE_TYPE(msg))
313 case GST_MESSAGE_ERROR:
318 gst_message_parse_error(msg, &err, &debug);
319 g_warning("Gstreamer Error: %s", err->message);
324 case GST_MESSAGE_ELEMENT:
326 if (gst_is_missing_plugin_message(msg))
328 gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
329 gchar *message, *desc;
330 desc = gst_missing_plugin_message_get_description(msg);
331 message = g_strdup_printf(
332 "Missing GStreamer plugin\n"
333 "Audio or Video may not play as expected\n\n%s",
335 ghb_message_dialog(GTK_MESSAGE_WARNING, message, "Ok", NULL);
337 gst_element_set_state(ud->preview->play, GST_STATE_PLAYING);
341 case GST_MESSAGE_STATE_CHANGED:
343 GstState state, pending;
344 gst_element_get_state(ud->preview->play, &state, &pending, 0);
345 if (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING)
347 update_stream_info(ud);
351 case GST_MESSAGE_EOS:
356 img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
357 gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
358 gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
359 ud->preview->pause = TRUE;
360 gst_element_seek(ud->preview->play, 1.0,
361 GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
362 GST_SEEK_TYPE_SET, 0,
363 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
375 live_preview_start(signal_user_data_t *ud)
380 img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
381 if (!ud->preview->encoded[ud->preview->frame])
383 gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
384 gst_element_set_state(ud->preview->play, GST_STATE_NULL);
385 ud->preview->pause = TRUE;
389 uri = g_strdup_printf("file://%s", ud->preview->current);
390 gtk_image_set_from_stock(img, "gtk-media-pause", GTK_ICON_SIZE_BUTTON);
391 ud->preview->state = PREVIEW_STATE_LIVE;
392 g_object_set(G_OBJECT(ud->preview->play), "uri", uri, NULL);
393 gst_element_set_state(ud->preview->play, GST_STATE_PLAYING);
394 ud->preview->pause = FALSE;
399 live_preview_pause(signal_user_data_t *ud)
403 img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
404 gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
405 gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
406 ud->preview->pause = TRUE;
410 live_preview_stop(signal_user_data_t *ud)
415 img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
416 gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
417 gst_element_set_state(ud->preview->play, GST_STATE_NULL);
418 ud->preview->pause = TRUE;
419 ud->preview->state = PREVIEW_STATE_IMAGE;
421 progress = GTK_RANGE(GHB_WIDGET(ud->builder, "live_preview_progress"));
422 gtk_range_set_value(progress, 0);
426 ghb_live_reset(signal_user_data_t *ud)
430 if (!ud->preview->pause)
431 live_preview_stop(ud);
432 if (ud->preview->current)
434 g_free(ud->preview->current);
435 ud->preview->current = NULL;
437 encoded = ud->preview->encoded[ud->preview->frame];
438 memset(ud->preview->encoded, 0, sizeof(gboolean) * 10);
440 ghb_set_preview_image(ud);
443 extern void hb_get_tempory_directory(hb_handle_t *h, char path[512]);
446 live_preview_start_cb(GtkWidget *xwidget, signal_user_data_t *ud)
450 gint frame = ud->preview->frame;
452 tmp_dir = ghb_get_tmp_dir();
453 name = g_strdup_printf("%s/live%02d", tmp_dir, ud->preview->frame);
454 if (ud->preview->current)
455 g_free(ud->preview->current);
456 ud->preview->current = name;
458 if (ud->preview->encoded[frame] &&
459 g_file_test(name, G_FILE_TEST_IS_REGULAR))
461 if (ud->preview->pause)
462 live_preview_start(ud);
464 live_preview_pause(ud);
470 ud->preview->encode_frame = frame;
471 js = ghb_value_dup(ud->settings);
472 ghb_settings_set_string(js, "destination", name);
473 ghb_settings_set_int(js, "start_frame", ud->preview->frame);
474 ghb_settings_set_int(js, "live_duration", 15);
475 ghb_add_live_job(js, 0);
476 ghb_start_live_encode();
482 ghb_live_encode_done(signal_user_data_t *ud, gboolean success)
487 prog = GHB_WIDGET(ud->builder, "live_encode_progress");
489 ud->preview->encode_frame == ud->preview->frame)
491 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), "Done");
492 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 1);
493 ud->preview->encoded[ud->preview->encode_frame] = TRUE;
494 live_preview_start(ud);
495 widget = GHB_WIDGET(ud->builder, "live_progress_box");
496 gtk_widget_hide (widget);
497 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
498 gtk_widget_show (widget);
502 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), "");
503 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 0);
504 ud->preview->encoded[ud->preview->encode_frame] = FALSE;
509 unlock_progress_cb(signal_user_data_t *ud)
511 ud->preview->progress_lock = FALSE;
512 // This function is initiated by g_idle_add. Must return false
513 // so that it is not called again
518 ghb_live_preview_progress(signal_user_data_t *ud)
520 GstFormat fmt = GST_FORMAT_TIME;
521 gint64 len = -1, pos = -1;
523 if (ud->preview->state != PREVIEW_STATE_LIVE || ud->preview->seek_lock)
526 ud->preview->progress_lock = TRUE;
527 if (gst_element_query_duration(ud->preview->play, &fmt, &len))
529 if (len != -1 && fmt == GST_FORMAT_TIME)
531 ud->preview->len = len / GST_MSECOND;
534 if (gst_element_query_position(ud->preview->play, &fmt, &pos))
536 if (pos != -1 && fmt == GST_FORMAT_TIME)
538 ud->preview->pos = pos / GST_MSECOND;
541 if (ud->preview->len > 0)
546 percent = (gdouble)ud->preview->pos * 100 / ud->preview->len;
547 progress = GTK_RANGE(GHB_WIDGET(ud->builder, "live_preview_progress"));
548 gtk_range_set_value(progress, percent);
550 g_idle_add((GSourceFunc)unlock_progress_cb, ud);
554 unlock_seek_cb(signal_user_data_t *ud)
556 ud->preview->seek_lock = FALSE;
557 // This function is initiated by g_idle_add. Must return false
558 // so that it is not called again
563 live_preview_seek_cb(GtkWidget *widget, signal_user_data_t *ud)
568 if (ud->preview->progress_lock)
571 ud->preview->seek_lock = TRUE;
572 dval = gtk_range_get_value(GTK_RANGE(widget));
573 pos = ((ud->preview->len * dval) / 100) * GST_MSECOND;
574 gst_element_seek(ud->preview->play, 1.0,
575 GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
576 GST_SEEK_TYPE_SET, pos,
577 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
578 g_idle_add((GSourceFunc)unlock_seek_cb, ud);
582 ghb_set_preview_image(signal_user_data_t *ud)
585 gint preview_width, preview_height, target_height, width, height;
587 g_debug("set_preview_button_image ()");
590 live_preview_stop(ud);
592 titleindex = ghb_settings_combo_int(ud->settings, "title");
593 if (titleindex < 0) return;
594 widget = GHB_WIDGET (ud->builder, "preview_frame");
595 ud->preview->frame = ghb_widget_int(widget) - 1;
596 if (ud->preview->encoded[ud->preview->frame])
598 widget = GHB_WIDGET(ud->builder, "live_progress_box");
599 gtk_widget_hide (widget);
600 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
601 gtk_widget_show (widget);
605 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
606 gtk_widget_hide (widget);
607 widget = GHB_WIDGET(ud->builder, "live_progress_box");
608 gtk_widget_show (widget);
609 widget = GHB_WIDGET(ud->builder, "live_encode_progress");
610 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(widget), "");
611 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(widget), 0);
613 if (ud->preview->pix != NULL)
614 g_object_unref(ud->preview->pix);
617 ghb_get_preview_image(titleindex, ud->preview->frame,
618 ud, TRUE, &width, &height);
619 if (ud->preview->pix == NULL) return;
620 preview_width = gdk_pixbuf_get_width(ud->preview->pix);
621 preview_height = gdk_pixbuf_get_height(ud->preview->pix);
622 widget = GHB_WIDGET (ud->builder, "preview_image");
623 if (preview_width != ud->preview->width ||
624 preview_height != ud->preview->height)
626 gtk_widget_set_size_request(widget, preview_width, preview_height);
627 ud->preview->width = preview_width;
628 ud->preview->height = preview_height;
631 widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
632 -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
634 gchar *text = g_strdup_printf("%d x %d", width, height);
635 widget = GHB_WIDGET (ud->builder, "preview_dims");
636 gtk_label_set_text(GTK_LABEL(widget), text);
639 g_debug("preview %d x %d", preview_width, preview_height);
640 target_height = MIN(ud->preview->button_height, 128);
641 height = target_height;
642 width = preview_width * height / preview_height;
644 if ((height >= 16) && (width >= 16))
646 GdkPixbuf *scaled_preview;
647 scaled_preview = gdk_pixbuf_scale_simple (ud->preview->pix, width,
648 height, GDK_INTERP_NEAREST);
649 if (scaled_preview != NULL)
651 widget = GHB_WIDGET (ud->builder, "preview_button_image");
652 gtk_image_set_from_pixbuf(GTK_IMAGE(widget), scaled_preview);
653 g_object_unref (scaled_preview);
659 delayed_expose_cb(signal_user_data_t *ud)
664 g_object_get(ud->preview->play, "video-sink", &vsink, NULL);
665 if (GST_IS_BIN(vsink))
666 xover = GST_X_OVERLAY(gst_bin_get_by_interface(
667 GST_BIN(vsink), GST_TYPE_X_OVERLAY));
669 xover = GST_X_OVERLAY(vsink);
670 gst_x_overlay_expose(xover);
671 // This function is initiated by g_idle_add. Must return false
672 // so that it is not called again
679 GdkEventExpose *event,
680 signal_user_data_t *ud)
682 if (ud->preview->state == PREVIEW_STATE_LIVE)
684 if (GST_STATE(ud->preview->play) >= GST_STATE_PAUSED)
689 g_object_get(ud->preview->play, "video-sink", &vsink, NULL);
690 if (GST_IS_BIN(vsink))
691 xover = GST_X_OVERLAY(gst_bin_get_by_interface(
692 GST_BIN(vsink), GST_TYPE_X_OVERLAY));
694 xover = GST_X_OVERLAY(vsink);
695 gst_x_overlay_expose(xover);
696 // For some reason, the exposed region doesn't always get
697 // cleaned up here. But a delayed gst_x_overlay_expose()
699 g_idle_add((GSourceFunc)delayed_expose_cb, ud);
706 widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
707 -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
712 preview_button_size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation, signal_user_data_t *ud)
714 g_debug("allocate %d x %d", allocation->width, allocation->height);
715 if (ud->preview->button_width == allocation->width &&
716 ud->preview->button_height == allocation->height)
718 // Nothing to do. Bug out.
719 g_debug("nothing to do");
722 g_debug("prev allocate %d x %d", ud->preview->button_width,
723 ud->preview->button_height);
724 ud->preview->button_width = allocation->width;
725 ud->preview->button_height = allocation->height;
726 ghb_set_preview_image(ud);
730 preview_button_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
734 titleindex = ghb_settings_combo_int(ud->settings, "title");
735 if (titleindex < 0) return;
736 g_debug("titleindex %d", titleindex);
738 GtkWidget *widget = GHB_WIDGET (ud->builder, "preview_window");
739 gtk_widget_show (widget);
743 preview_frame_value_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
745 ghb_set_preview_image(ud);