/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ /* * preview.c * Copyright (C) John Stebbins 2008-2019 * * preview.c is free software. * * You may redistribute it and/or modify it under the terms of the * GNU General Public License, as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * preview.c is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with main.c. If not, write to: * The Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "ghbcompat.h" #if defined(_ENABLE_GST) #include #include #include #endif #include "settings.h" #include "jobdict.h" #include "presets.h" #include "callbacks.h" #include "hb-backend.h" #include "preview.h" #include "values.h" #include "queuehandler.h" #include "hb.h" #define PREVIEW_STATE_IMAGE 0 #define PREVIEW_STATE_LIVE 1 struct preview_s { #if defined(_ENABLE_GST) GstElement *play; GstElement *vsink; #endif gint64 len; gint64 pos; gboolean seek_lock; gboolean progress_lock; gint width; gint height; gint render_width; gint render_height; GtkWidget * view; GdkPixbuf * pix; GdkPixbuf * scaled_pix; gint button_width; gint button_height; gint frame; gint state; gboolean pause; gboolean encoded[GHB_PREVIEW_MAX]; gint encode_frame; gint live_id; gchar * current; gint live_enabled; }; #if defined(_ENABLE_GST) G_MODULE_EXPORT gboolean live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data); #endif void ghb_screen_par(signal_user_data_t *ud, gint *par_n, gint *par_d) { // Assume 1:1 // I could get it from GtkWindow->GdkScreen monitor methods. // But it's going to be 1:1 anyway, so why bother. *par_n = 1; *par_d = 1; } void ghb_par_scale(signal_user_data_t *ud, gint *width, gint *height, gint par_n, gint par_d) { gint disp_par_n, disp_par_d; gint64 num, den; ghb_screen_par(ud, &disp_par_n, &disp_par_d); if (disp_par_n < 1 || disp_par_d < 1) { disp_par_n = 1; disp_par_d = 1; } num = par_n * disp_par_d; den = par_d * disp_par_n; if (par_n > par_d) *width = *width * num / den; else *height = *height * den / num; } void preview_set_size(signal_user_data_t *ud, int width, int height) { ud->preview->width = width; ud->preview->height = height; } void preview_set_render_size(signal_user_data_t *ud, int width, int height) { GtkWidget * widget; GtkWindow * window; GhbSurface * ss; GdkGeometry geo; widget = GHB_WIDGET (ud->builder, "preview_image"); gtk_widget_set_size_request(widget, width, height); window = GTK_WINDOW(GHB_WIDGET(ud->builder, "preview_window")); ss = ghb_widget_get_surface(GTK_WIDGET(window)); gtk_window_unmaximize(window); if (ss != NULL) { geo.min_aspect = (double)(width - 4) / height; geo.max_aspect = (double)(width + 4) / height; geo.width_inc = geo.height_inc = 2; ghb_surface_set_geometry_hints(ss, &geo, GDK_HINT_ASPECT|GDK_HINT_RESIZE_INC); } gtk_window_resize(window, width, height); ud->preview->render_width = width; ud->preview->render_height = height; } void ghb_preview_init(signal_user_data_t *ud) { GtkWidget *widget; ud->preview = g_malloc0(sizeof(preview_t)); ud->preview->pause = TRUE; ud->preview->encode_frame = -1; ud->preview->live_id = -1; widget = GHB_WIDGET(ud->builder, "preview_button_image"); gtk_widget_get_size_request(widget, &ud->preview->button_width, &ud->preview->button_height); #if defined(_ENABLE_GST) GstBus *bus; ud->preview->play = gst_element_factory_make("playbin", "play"); ud->preview->vsink = gst_element_factory_make("gdkpixbufsink", "pixsink"); if (ud->preview->play == NULL || ud->preview->vsink == NULL) { g_message("Couldn't initialize gstreamer. Disabling live preview."); GtkWidget *widget = GHB_WIDGET(ud->builder, "live_preview_box"); gtk_widget_hide (widget); widget = GHB_WIDGET(ud->builder, "live_preview_duration_box"); gtk_widget_hide (widget); return; } else { g_object_set(ud->preview->vsink, "qos", FALSE, "max-lateness", (gint64) - 1, NULL); g_object_set(ud->preview->play, "video-sink", ud->preview->vsink, NULL); g_object_set(ud->preview->play, "subtitle-font-desc", "sans bold 20", NULL); bus = gst_pipeline_get_bus(GST_PIPELINE(ud->preview->play)); gst_bus_add_watch(bus, live_preview_cb, ud); gst_object_unref(bus); ud->preview->live_enabled = 1; } #else widget = GHB_WIDGET(ud->builder, "live_preview_box"); gtk_widget_hide (widget); widget = GHB_WIDGET(ud->builder, "live_preview_duration_box"); gtk_widget_hide (widget); #endif } void ghb_preview_cleanup(signal_user_data_t *ud) { if (ud->preview->current) { g_free(ud->preview->current); ud->preview->current = NULL; } } #if defined(_ENABLE_GST) void live_preview_start(signal_user_data_t *ud) { GtkImage *img; gchar *uri; if (!ud->preview->live_enabled) return; img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image")); if (!ud->preview->encoded[ud->preview->frame]) { ghb_image_set_from_icon_name(img, GHB_PLAY_ICON, GHB_ICON_SIZE_BUTTON); gst_element_set_state(ud->preview->play, GST_STATE_NULL); ud->preview->pause = TRUE; return; } if (ud->preview->state != PREVIEW_STATE_LIVE) { #if defined(_WIN32) uri = g_strdup_printf("file:///%s", ud->preview->current); #else uri = g_strdup_printf("file://%s", ud->preview->current); #endif ghb_image_set_from_icon_name(img, GHB_PAUSE_ICON, GHB_ICON_SIZE_BUTTON); ud->preview->state = PREVIEW_STATE_LIVE; g_object_set(G_OBJECT(ud->preview->play), "uri", uri, NULL); g_free(uri); } gst_element_set_state(ud->preview->play, GST_STATE_PLAYING); ud->preview->pause = FALSE; } void live_preview_pause(signal_user_data_t *ud) { GtkImage *img; if (!ud->preview->live_enabled) return; img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image")); ghb_image_set_from_icon_name(img, GHB_PLAY_ICON, GHB_ICON_SIZE_BUTTON); gst_element_set_state(ud->preview->play, GST_STATE_PAUSED); ud->preview->pause = TRUE; } #endif void live_preview_stop(signal_user_data_t *ud) { GtkImage *img; GtkRange *progress; if (!ud->preview->live_enabled) return; img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image")); ghb_image_set_from_icon_name(img, GHB_PLAY_ICON, GHB_ICON_SIZE_BUTTON); #if defined(_ENABLE_GST) gst_element_set_state(ud->preview->play, GST_STATE_NULL); #endif ud->preview->pause = TRUE; ud->preview->state = PREVIEW_STATE_IMAGE; progress = GTK_RANGE(GHB_WIDGET(ud->builder, "live_preview_progress")); gtk_range_set_value(progress, 0); } void ghb_live_reset(signal_user_data_t *ud) { gboolean encoded; if (ud->preview->live_id >= 0) { ghb_stop_live_encode(); } ud->preview->live_id = -1; ud->preview->encode_frame = -1; if (!ud->preview->pause) live_preview_stop(ud); if (ud->preview->current) { g_free(ud->preview->current); ud->preview->current = NULL; } encoded = ud->preview->encoded[ud->preview->frame]; memset(ud->preview->encoded, 0, sizeof(gboolean) * GHB_PREVIEW_MAX); if (encoded) ghb_set_preview_image(ud); } #if defined(_ENABLE_GST) static void caps_set(GstCaps *caps, signal_user_data_t *ud) { GstStructure *ss; ss = gst_caps_get_structure(caps, 0); if (ss) { gint fps_n, fps_d, width, height; guint num, den, par_n, par_d; gint disp_par_n, disp_par_d; const GValue *par; gst_structure_get_fraction(ss, "framerate", &fps_n, &fps_d); gst_structure_get_int(ss, "width", &width); gst_structure_get_int(ss, "height", &height); par = gst_structure_get_value(ss, "pixel-aspect-ratio"); par_n = gst_value_get_fraction_numerator(par); par_d = gst_value_get_fraction_denominator(par); ghb_screen_par(ud, &disp_par_n, &disp_par_d); gst_video_calculate_display_ratio( &num, &den, width, height, par_n, par_d, disp_par_n, disp_par_d); if (par_n > par_d) width = gst_util_uint64_scale_int(height, num, den); else height = gst_util_uint64_scale_int(width, den, num); preview_set_size(ud, width, height); if (ghb_dict_get_bool(ud->prefs, "reduce_hd_preview")) { gint s_w, s_h; ghb_monitor_get_size(GHB_WIDGET(ud->builder, "preview_window"), &s_w, &s_h); if (s_w > 0 && s_h > 0) { if (width > s_w * 80 / 100) { width = s_w * 80 / 100; height = gst_util_uint64_scale_int(width, den, num); } if (height > s_h * 80 / 100) { height = s_h * 80 / 100; width = gst_util_uint64_scale_int(height, num, den); } } } } } static void update_stream_info(signal_user_data_t *ud) { GstPad *vpad = NULL; gint n_video; g_object_get(G_OBJECT(ud->preview->play), "n-video", &n_video, NULL); if (n_video > 0) { gint ii; for (ii = 0; ii < n_video && vpad == NULL; ii++) { g_signal_emit_by_name(ud->preview->play, "get-video-pad", ii, &vpad); } } if (vpad) { GstCaps *caps; caps = gst_pad_get_current_caps(vpad); if (caps) { caps_set(caps, ud); gst_caps_unref(caps); } gst_object_unref(vpad); } } G_MODULE_EXPORT gboolean live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data) { signal_user_data_t *ud = (signal_user_data_t*)data; switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_UNKNOWN: { //printf("unknown"); } break; case GST_MESSAGE_EOS: { // Done //printf("eos\n"); live_preview_stop(ud); gst_element_seek(ud->preview->play, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); } break; case GST_MESSAGE_ERROR: { //printf("error\n"); GError *err; gchar *debug; gst_message_parse_error(msg, &err, &debug); g_warning("Gstreamer Error: %s", err->message); g_error_free(err); g_free(debug); } break; case GST_MESSAGE_WARNING: case GST_MESSAGE_INFO: case GST_MESSAGE_TAG: case GST_MESSAGE_BUFFERING: case GST_MESSAGE_STATE_CHANGED: { //printf("state change %x\n", state); GstState state, pending; gst_element_get_state(ud->preview->play, &state, &pending, 0); if (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING) { update_stream_info(ud); } } break; case GST_MESSAGE_STATE_DIRTY: { //printf("state dirty\n"); } break; case GST_MESSAGE_STEP_DONE: { //printf("step done\n"); } break; case GST_MESSAGE_CLOCK_PROVIDE: { //printf("clock provide\n"); } break; case GST_MESSAGE_CLOCK_LOST: { //printf("clock lost\n"); } break; case GST_MESSAGE_NEW_CLOCK: { //printf("new clock\n"); } break; case GST_MESSAGE_STRUCTURE_CHANGE: { //printf("structure change\n"); } break; case GST_MESSAGE_STREAM_STATUS: { //printf("stream status\n"); } break; case GST_MESSAGE_APPLICATION: { //printf("application\n"); } break; case GST_MESSAGE_ELEMENT: { //printf("element\n"); if (gst_is_missing_plugin_message(msg)) { GtkWindow *hb_window; hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window")); gst_element_set_state(ud->preview->play, GST_STATE_PAUSED); gchar *message, *desc; desc = gst_missing_plugin_message_get_description(msg); message = g_strdup_printf( _("Missing GStreamer plugin\n" "Audio or Video may not play as expected\n\n%s"), desc); ghb_message_dialog(hb_window, GTK_MESSAGE_WARNING, message, "Ok", NULL); g_free(message); gst_element_set_state(ud->preview->play, GST_STATE_PLAYING); } else if (msg->src == GST_OBJECT_CAST(ud->preview->vsink)) { const GstStructure *gstStruct; const GValue *val; gstStruct = gst_message_get_structure(msg); if (gstStruct != NULL && (gst_structure_has_name(gstStruct, "preroll-pixbuf") || gst_structure_has_name(gstStruct, "pixbuf"))) { val = gst_structure_get_value(gstStruct, "pixbuf"); if (val != NULL) { GdkPixbuf * pix; GtkWidget *widget; int width, height; if (ud->preview->pix != NULL) g_object_unref(ud->preview->pix); if (ud->preview->scaled_pix != NULL) g_object_unref(ud->preview->scaled_pix); pix = GDK_PIXBUF(g_value_dup_object(val)); width = gdk_pixbuf_get_width(pix); height = gdk_pixbuf_get_height(pix); if (width != ud->preview->width || height != ud->preview->height || width != ud->preview->render_width || height != ud->preview->render_height) { double xscale, yscale; xscale = (double)ud->preview->render_width / ud->preview->width; yscale = (double)ud->preview->render_height / ud->preview->height; if (xscale <= yscale) { width = ud->preview->render_width; height = ud->preview->height * xscale; } else { width = ud->preview->width * yscale; height = ud->preview->render_height; } ud->preview->scaled_pix = gdk_pixbuf_scale_simple(pix, width, height, GDK_INTERP_BILINEAR); g_object_ref(pix); } else { ud->preview->scaled_pix = pix; } ud->preview->pix = ud->preview->scaled_pix; g_object_ref(ud->preview->pix); widget = GHB_WIDGET (ud->builder, "preview_image"); gtk_widget_queue_draw(widget); } } } } break; case GST_MESSAGE_SEGMENT_START: { //printf("segment start\n"); } break; case GST_MESSAGE_SEGMENT_DONE: { //printf("segment done\n"); } break; case GST_MESSAGE_DURATION_CHANGED: { //printf("duration change\n"); }; case GST_MESSAGE_LATENCY: { //printf("latency\n"); }; case GST_MESSAGE_ASYNC_START: { //printf("async start\n"); } break; case GST_MESSAGE_ASYNC_DONE: { //printf("async done\n"); } break; case GST_MESSAGE_REQUEST_STATE: { //printf("request state\n"); } break; case GST_MESSAGE_STEP_START: { //printf("step start\n"); } break; case GST_MESSAGE_QOS: { //printf("qos\n"); } break; case GST_MESSAGE_PROGRESS: { //printf("progress\n"); } break; case GST_MESSAGE_TOC: { //printf("toc\n"); } break; case GST_MESSAGE_RESET_TIME: { //printf("reset time\n"); } break; case GST_MESSAGE_STREAM_START: { //printf("stream start\n"); }; case GST_MESSAGE_ANY: { //printf("any\n"); } break; default: { // Ignore //printf("?msg? %x\n", GST_MESSAGE_TYPE(msg)); } } return TRUE; } #endif G_MODULE_EXPORT void live_preview_start_cb(GtkWidget *xwidget, signal_user_data_t *ud) { gchar *tmp_dir; gchar *name; gint frame = ud->preview->frame; tmp_dir = ghb_get_tmp_dir(); name = g_strdup_printf("%s/live%02d", tmp_dir, ud->preview->frame); free(tmp_dir); if (ud->preview->current) g_free(ud->preview->current); ud->preview->current = name; if (ud->preview->encoded[frame] && g_file_test(name, G_FILE_TEST_IS_REGULAR)) { #if defined(_ENABLE_GST) if (ud->preview->pause) live_preview_start(ud); else live_preview_pause(ud); #endif } else { GhbValue *js; GhbValue *range, *dest; ud->preview->encode_frame = frame; js = ghb_value_dup(ud->settings); ghb_finalize_job(js); range = ghb_get_job_range_settings(js); dest = ghb_get_job_dest_settings(js); ghb_dict_set_string(dest, "File", name); ghb_dict_set_string(range, "Type", "preview"); ghb_dict_set_int(range, "Start", ud->preview->frame + 1); ghb_dict_set_int(range, "End", ghb_dict_get_int(ud->prefs, "live_duration") * 90000); ghb_dict_set_int(range, "SeekPoints", ghb_dict_get_int(ud->prefs, "preview_count")); GhbValue *job_dict = ghb_dict_get(js, "Job"); ud->preview->live_id = ghb_add_job(ghb_live_handle(), job_dict); ghb_start_live_encode(); ghb_value_free(&js); } } void ghb_live_encode_done(signal_user_data_t *ud, gboolean success) { GtkWidget *widget; GtkWidget *prog; ud->preview->live_id = -1; prog = GHB_WIDGET(ud->builder, "live_encode_progress"); if (success && ud->preview->encode_frame == ud->preview->frame) { gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), "Done"); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 1); ud->preview->encoded[ud->preview->encode_frame] = TRUE; #if defined(_ENABLE_GST) live_preview_start(ud); #endif widget = GHB_WIDGET(ud->builder, "live_progress_box"); gtk_widget_hide (widget); widget = GHB_WIDGET(ud->builder, "live_preview_progress"); gtk_widget_show (widget); } else { gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), ""); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 0); ud->preview->encoded[ud->preview->encode_frame] = FALSE; } } #if defined(_ENABLE_GST) G_MODULE_EXPORT gboolean unlock_progress_cb(signal_user_data_t *ud) { ud->preview->progress_lock = FALSE; // This function is initiated by g_idle_add. Must return false // so that it is not called again return FALSE; } #endif void ghb_live_preview_progress(signal_user_data_t *ud) { #if defined(_ENABLE_GST) GstFormat fmt = GST_FORMAT_TIME; gint64 len = -1, pos = -1; if (!ud->preview->live_enabled) return; if (ud->preview->state != PREVIEW_STATE_LIVE || ud->preview->seek_lock) return; ud->preview->progress_lock = TRUE; if (gst_element_query_duration(ud->preview->play, fmt, &len)) { if (len != -1 && fmt == GST_FORMAT_TIME) { ud->preview->len = len / GST_MSECOND; } } if (gst_element_query_position(ud->preview->play, fmt, &pos)) { if (pos != -1 && fmt == GST_FORMAT_TIME) { ud->preview->pos = pos / GST_MSECOND; } } if (ud->preview->len > 0) { GtkRange *progress; gdouble percent; percent = (gdouble)ud->preview->pos * 100 / ud->preview->len; progress = GTK_RANGE(GHB_WIDGET(ud->builder, "live_preview_progress")); gtk_range_set_value(progress, percent); } g_idle_add((GSourceFunc)unlock_progress_cb, ud); #endif } #if defined(_ENABLE_GST) G_MODULE_EXPORT gboolean unlock_seek_cb(signal_user_data_t *ud) { ud->preview->seek_lock = FALSE; // This function is initiated by g_idle_add. Must return false // so that it is not called again return FALSE; } #endif G_MODULE_EXPORT void live_preview_seek_cb(GtkWidget *widget, signal_user_data_t *ud) { #if defined(_ENABLE_GST) gdouble dval; gint64 pos; if (!ud->preview->live_enabled) return; if (ud->preview->progress_lock) return; ud->preview->seek_lock = TRUE; dval = gtk_range_get_value(GTK_RANGE(widget)); pos = ((ud->preview->len * dval) / 100) * GST_MSECOND; gst_element_seek(ud->preview->play, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, pos, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); g_idle_add((GSourceFunc)unlock_seek_cb, ud); #endif } static void _draw_pixbuf(signal_user_data_t * ud, cairo_t *cr, GdkPixbuf *pix) { int pix_width, pix_height, hoff, voff; cairo_save(cr); cairo_rectangle(cr, 0, 0, ud->preview->render_width, ud->preview->render_height); cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_fill(cr); cairo_restore(cr); pix_width = gdk_pixbuf_get_width(pix); pix_height = gdk_pixbuf_get_height(pix); hoff = MAX((ud->preview->render_width - pix_width) / 2, 0); voff = MAX((ud->preview->render_height - pix_height) / 2, 0); if (voff > 0 || hoff > 0) { cairo_translate(cr, hoff, voff); } gdk_cairo_set_source_pixbuf(cr, pix, 0, 0); cairo_paint(cr); } static void set_mini_preview_image(signal_user_data_t *ud, GdkPixbuf * pix) { int preview_width, preview_height, width, height; GdkPixbuf * scaled_preview; if (pix == NULL) { return; } preview_width = gdk_pixbuf_get_width(pix); preview_height = gdk_pixbuf_get_height(pix); // Scale and display the mini-preview height = MIN(ud->preview->button_height - 32, preview_height); width = preview_width * height / preview_height; if (width > ud->preview->button_width - 32) { width = MIN(ud->preview->button_width - 32, preview_width); height = preview_height * width / preview_width; } if ((height >= 16) && (width >= 16)) { scaled_preview = gdk_pixbuf_scale_simple(pix, width, height, GDK_INTERP_NEAREST); if (scaled_preview != NULL) { GtkWidget * widget; widget = GHB_WIDGET (ud->builder, "preview_button_image"); #if GTK_CHECK_VERSION(3, 90, 0) gtk_picture_set_pixbuf(GTK_PICTURE(widget), scaled_preview); #else gtk_image_set_from_pixbuf(GTK_IMAGE(widget), scaled_preview); #endif g_object_unref(scaled_preview); } } } GdkPixbuf * do_preview_scaling(signal_user_data_t *ud, GdkPixbuf *pix) { int preview_width, preview_height, width, height; GdkPixbuf * scaled_preview; if (pix == NULL) { return NULL; } preview_width = gdk_pixbuf_get_width(pix); preview_height = gdk_pixbuf_get_height(pix); if (ud->preview->render_width <= 0 || ud->preview->render_height <= 0) { // resize preview window to fit preview preview_set_render_size(ud, preview_width, preview_height); g_object_ref(pix); return pix; } // Scale if necessary if (preview_width != ud->preview->render_width || preview_height != ud->preview->render_height) { double xscale, yscale; xscale = (double)ud->preview->render_width / preview_width; yscale = (double)ud->preview->render_height / preview_height; if (xscale <= yscale) { width = ud->preview->render_width; height = preview_height * xscale; } else { width = preview_width * yscale; height = ud->preview->render_height; } // Allow some slop in aspect ratio so that we fill the window int delta = ud->preview->render_width - width; if (delta > 0 && delta <= 16) width = ud->preview->render_width; delta = ud->preview->render_height - height; if (delta > 0 && delta <= 16) height = ud->preview->render_height; scaled_preview = gdk_pixbuf_scale_simple(pix, width, height, GDK_INTERP_BILINEAR); return scaled_preview; } else { g_object_ref(pix); } return pix; } void init_preview_image(signal_user_data_t *ud) { GtkWidget *widget; gint width, height; gint title_id, titleindex; const hb_title_t *title; live_preview_stop(ud); title_id = ghb_dict_get_int(ud->settings, "title"); title = ghb_lookup_title(title_id, &titleindex); if (title == NULL && ud->preview->pix != NULL) { g_object_unref(ud->preview->pix); ud->preview->pix = NULL; } widget = GHB_WIDGET (ud->builder, "preview_frame"); ud->preview->frame = ghb_widget_int(widget) - 1; if (ud->preview->encoded[ud->preview->frame]) { widget = GHB_WIDGET(ud->builder, "live_progress_box"); gtk_widget_hide (widget); widget = GHB_WIDGET(ud->builder, "live_preview_progress"); gtk_widget_show (widget); } else { widget = GHB_WIDGET(ud->builder, "live_preview_progress"); gtk_widget_hide (widget); widget = GHB_WIDGET(ud->builder, "live_progress_box"); gtk_widget_show (widget); widget = GHB_WIDGET(ud->builder, "live_encode_progress"); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(widget), ""); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(widget), 0); } if (ud->preview->pix != NULL) g_object_unref(ud->preview->pix); if (ud->preview->scaled_pix != NULL) g_object_unref(ud->preview->scaled_pix); ud->preview->pix = ghb_get_preview_image(title, ud->preview->frame, ud, &width, &height); if (ud->preview->pix == NULL) return; int pix_width, pix_height; pix_width = gdk_pixbuf_get_width(ud->preview->pix); pix_height = gdk_pixbuf_get_height(ud->preview->pix); preview_set_size(ud, pix_width, pix_height); } void ghb_set_preview_image(signal_user_data_t *ud) { init_preview_image(ud); // Scale and display the mini-preview set_mini_preview_image(ud, ud->preview->pix); // Scale the full size preview ud->preview->scaled_pix = do_preview_scaling(ud, ud->preview->pix); // Display full size preview GtkWidget *widget = GHB_WIDGET(ud->builder, "preview_image"); gtk_widget_queue_draw(widget); } void ghb_rescale_preview_image(signal_user_data_t *ud) { init_preview_image(ud); if (ud->preview->width <= 0 || ud->preview->height <= 0) { return; } double scale = (double)ud->preview->render_width / ud->preview->width; preview_set_render_size(ud, ud->preview->width * scale, ud->preview->height * scale); // Scale and display the mini-preview set_mini_preview_image(ud, ud->preview->pix); // Scale the full size preview ud->preview->scaled_pix = do_preview_scaling(ud, ud->preview->pix); // Display full size preview GtkWidget *widget = GHB_WIDGET(ud->builder, "preview_image"); gtk_widget_queue_draw(widget); } void ghb_reset_preview_image(signal_user_data_t *ud) { init_preview_image(ud); if (ud->preview->width <= 0 || ud->preview->height <= 0) { return; } preview_set_render_size(ud, ud->preview->width, ud->preview->height); // Scale and display the mini-preview set_mini_preview_image(ud, ud->preview->pix); // Scale the full size preview ud->preview->scaled_pix = do_preview_scaling(ud, ud->preview->pix); // Display full size preview GtkWidget *widget = GHB_WIDGET(ud->builder, "preview_image"); gtk_widget_queue_draw(widget); } #if GTK_CHECK_VERSION(3, 90, 0) G_MODULE_EXPORT void preview_draw_cb( GtkDrawingArea * da, cairo_t * cr, int width, int height, signal_user_data_t *ud) { if (ud->preview->scaled_pix != NULL) { _draw_pixbuf(ud, cr, ud->preview->scaled_pix); } } #else G_MODULE_EXPORT gboolean preview_draw_cb( GtkWidget *widget, cairo_t *cr, signal_user_data_t *ud) { if (ud->preview->scaled_pix != NULL) { _draw_pixbuf(ud, cr, ud->preview->scaled_pix); } return FALSE; } #endif G_MODULE_EXPORT void preview_button_size_allocate_cb( GtkWidget *widget, #if GTK_CHECK_VERSION(3, 90, 0) int width, int height, int baseline, #else GdkRectangle *rect, #endif signal_user_data_t *ud) { #if !GTK_CHECK_VERSION(3, 90, 0) int width = rect->width; int height = rect->height; #endif if (ud->preview->button_width == width && ud->preview->button_height == height) { // Nothing to do. Bug out. g_debug("nothing to do"); return; } ud->preview->button_width = width; ud->preview->button_height = height; set_mini_preview_image(ud, ud->preview->pix); } void ghb_preview_set_visible(signal_user_data_t *ud, gboolean visible) { GtkWidget *widget; #if 0 gint title_id, titleindex; const hb_title_t *title; title_id = ghb_dict_get_int(ud->settings, "title"); title = ghb_lookup_title(title_id, &titleindex); visible &= title != NULL; #endif widget = GHB_WIDGET(ud->builder, "preview_window"); if (visible) { #if !GTK_CHECK_VERSION(3, 90, 0) // TODO: can this be done in GTK4? gint x, y; x = ghb_dict_get_int(ud->prefs, "preview_x"); y = ghb_dict_get_int(ud->prefs, "preview_y"); if (x >= 0 && y >= 0) gtk_window_move(GTK_WINDOW(widget), x, y); #endif gtk_window_deiconify(GTK_WINDOW(widget)); } gtk_widget_set_visible(widget, visible); } static void update_preview_labels(signal_user_data_t *ud, gboolean active) { GtkToolButton *button; button = GTK_TOOL_BUTTON(GHB_WIDGET(ud->builder, "show_preview")); gtk_tool_button_set_label(button, "Preview"); } G_MODULE_EXPORT void show_preview_action_cb(GSimpleAction *action, GVariant *value, signal_user_data_t *ud) { gboolean state = g_variant_get_boolean(value); g_simple_action_set_state(action, value); ghb_preview_set_visible(ud, state); update_preview_labels(ud, state); } G_MODULE_EXPORT void preview_reset_clicked_cb(GtkWidget *toggle, signal_user_data_t *ud) { g_debug("preview_reset_clicked_cb()"); if (ud->preview->width <= 0 || ud->preview->height <= 0) { return; } preview_set_render_size(ud, ud->preview->width, ud->preview->height); // On windows, preview_resize_cb does not get called when the size // is reset above. So assume it got reset and disable the // "Source Resolution" button. GtkWidget * widget = GHB_WIDGET(ud->builder, "preview_reset"); gtk_widget_hide(widget); if (ud->preview->scaled_pix != NULL) g_object_unref(ud->preview->scaled_pix); ud->preview->scaled_pix = do_preview_scaling(ud, ud->preview->pix); widget = GHB_WIDGET(ud->builder, "preview_image"); gtk_widget_queue_draw(widget); } G_MODULE_EXPORT void preview_frame_value_changed_cb(GtkWidget *widget, signal_user_data_t *ud) { if (ud->preview->live_id >= 0) { ghb_stop_live_encode(); ud->preview->live_id = -1; ud->preview->encode_frame = -1; } ghb_set_preview_image(ud); } G_MODULE_EXPORT gboolean preview_window_delete_cb( GtkWidget *widget, #if !GTK_CHECK_VERSION(3, 90, 0) GdkEvent *event, #endif signal_user_data_t *ud) { live_preview_stop(ud); widget = GHB_WIDGET(ud->builder, "show_preview"); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE); return TRUE; } G_MODULE_EXPORT void preview_duration_changed_cb(GtkWidget *widget, signal_user_data_t *ud) { g_debug("preview_duration_changed_cb ()"); ghb_live_reset(ud); ghb_widget_to_setting (ud->prefs, widget); ghb_check_dependency(ud, widget, NULL); const gchar *name = ghb_get_setting_key(widget); ghb_pref_save(ud->prefs, name); } static guint hud_timeout_id = 0; static gboolean in_hud = FALSE; static gboolean hud_timeout(signal_user_data_t *ud) { GtkWidget *widget; g_debug("hud_timeout()"); widget = GHB_WIDGET(ud->builder, "preview_hud"); gtk_widget_hide(widget); hud_timeout_id = 0; return FALSE; } #if GTK_CHECK_VERSION(3, 90, 0) G_MODULE_EXPORT void hud_enter_cb( GtkEventControllerMotion * econ, double x, double y, GdkCrossingMode cross, GdkNotifyType notify, signal_user_data_t *ud) #else G_MODULE_EXPORT gboolean hud_enter_cb( GtkWidget *widget, GdkEventCrossing *event, signal_user_data_t *ud) #endif { GtkWidget * hud; if (hud_timeout_id != 0) { GMainContext *mc; GSource *source; mc = g_main_context_default(); source = g_main_context_find_source_by_id(mc, hud_timeout_id); if (source != NULL) g_source_destroy(source); } hud = GHB_WIDGET(ud->builder, "preview_hud"); if (!gtk_widget_get_visible(hud)) { gtk_widget_show(hud); } hud_timeout_id = 0; in_hud = TRUE; #if !GTK_CHECK_VERSION(3, 90, 0) return FALSE; #endif } #if GTK_CHECK_VERSION(3, 90, 0) G_MODULE_EXPORT void hud_leave_cb( GtkEventControllerMotion * econ, GdkCrossingMode cross, GdkNotifyType notify, signal_user_data_t *ud) #else G_MODULE_EXPORT gboolean hud_leave_cb( GtkWidget *widget, GdkEventCrossing *event, signal_user_data_t *ud) #endif { in_hud = FALSE; #if !GTK_CHECK_VERSION(3, 90, 0) return FALSE; #endif } #if GTK_CHECK_VERSION(3, 90, 0) G_MODULE_EXPORT void preview_leave_cb( GtkEventControllerMotion * econ, GdkCrossingMode cross, GdkNotifyType notify, signal_user_data_t *ud) #else G_MODULE_EXPORT gboolean preview_leave_cb( GtkWidget *widget, GdkEventCrossing *event, signal_user_data_t *ud) #endif { if (hud_timeout_id != 0) { GMainContext *mc; GSource *source; mc = g_main_context_default(); source = g_main_context_find_source_by_id(mc, hud_timeout_id); if (source != NULL) g_source_destroy(source); } hud_timeout_id = g_timeout_add(300, (GSourceFunc)hud_timeout, ud); #if !GTK_CHECK_VERSION(3, 90, 0) return FALSE; #endif } #if GTK_CHECK_VERSION(3, 90, 0) G_MODULE_EXPORT void preview_motion_cb( GtkEventControllerMotion * econ, gdouble x, gdouble y, signal_user_data_t *ud) #else G_MODULE_EXPORT gboolean preview_motion_cb( GtkWidget *widget, GdkEventMotion *event, signal_user_data_t *ud) #endif { GtkWidget * hud; if (hud_timeout_id != 0) { GMainContext *mc; GSource *source; mc = g_main_context_default(); source = g_main_context_find_source_by_id(mc, hud_timeout_id); if (source != NULL) g_source_destroy(source); } hud = GHB_WIDGET(ud->builder, "preview_hud"); if (!gtk_widget_get_visible(hud)) { gtk_widget_show(hud); } if (!in_hud) { hud_timeout_id = g_timeout_add_seconds(4, (GSourceFunc)hud_timeout, ud); } #if !GTK_CHECK_VERSION(3, 90, 0) return FALSE; #endif } // TODO: GTK4 eliminated "configure-event" signal. // From a read of the gtk4 code, it appears GDK_CONFIGURE does // not get propagated. And there is no way to set position of // a window :*( // // Hopefully they will fix this or provide a better alternative // before gtk4 is released. #if !GTK_CHECK_VERSION(3, 90, 0) G_MODULE_EXPORT gboolean preview_configure_cb( GtkWidget *widget, GdkEventConfigure *event, signal_user_data_t *ud) { if (gtk_widget_get_visible(widget)) { // TODO: can this be done in GTK4? gint x, y; gtk_window_get_position(GTK_WINDOW(widget), &x, &y); ghb_dict_set_int(ud->prefs, "preview_x", x); ghb_dict_set_int(ud->prefs, "preview_y", y); ghb_pref_set(ud->prefs, "preview_x"); ghb_pref_set(ud->prefs, "preview_y"); ghb_prefs_store(); } return FALSE; } #endif #if !GTK_CHECK_VERSION(3, 90, 0) // GTK4 no longer has GDK_WINDOW_STATE events :*( G_MODULE_EXPORT gboolean preview_state_cb( GtkWidget *widget, GdkEvent *event, signal_user_data_t *ud) { GdkEventType type = ghb_event_get_event_type(event); if (type == GDK_WINDOW_STATE) { // Look for transition to iconified state. // Toggle "Show Preview" button when iconified. // I only do this because there seems to be no // way to reliably disable the iconfy button without // also disabling the maximize button. GdkEventWindowState * wse = (GdkEventWindowState*)event; if (wse->changed_mask & wse->new_window_state & GDK_WINDOW_STATE_ICONIFIED) { live_preview_stop(ud); GtkWidget *widget = GHB_WIDGET(ud->builder, "show_preview"); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE); } } return FALSE; } #endif G_MODULE_EXPORT void preview_resize_cb( GtkWidget *widget, #if GTK_CHECK_VERSION(3, 90, 0) int width, int height, int baseline, #else GdkRectangle *rect, #endif signal_user_data_t *ud) { #if !GTK_CHECK_VERSION(3, 90, 0) int width = rect->width; int height = rect->height; #endif if (ud->preview->render_width != width || ud->preview->render_height != height) { ud->preview->render_width = width; ud->preview->render_height = height; if (ud->preview->scaled_pix != NULL) g_object_unref(ud->preview->scaled_pix); ud->preview->scaled_pix = do_preview_scaling(ud, ud->preview->pix); GtkWidget *widget = GHB_WIDGET(ud->builder, "preview_image"); gtk_widget_queue_draw(widget); if (ABS(ud->preview->render_width - ud->preview->width) <= 2 || ABS(ud->preview->render_height - ud->preview->height) <= 2) { GtkWidget * widget = GHB_WIDGET(ud->builder, "preview_reset"); gtk_widget_hide(widget); } else { GtkWidget * widget = GHB_WIDGET(ud->builder, "preview_reset"); gtk_widget_show(widget); } } } G_MODULE_EXPORT void show_crop_changed_cb(GtkWidget *widget, signal_user_data_t *ud) { g_debug("show_crop_changed_cb ()"); ghb_widget_to_setting(ud->prefs, widget); ghb_check_dependency(ud, widget, NULL); ghb_live_reset(ud); if (gtk_widget_is_sensitive(widget)) ghb_set_scale(ud, GHB_PIC_KEEP_PAR); ghb_pref_save(ud->prefs, "preview_show_crop"); ghb_rescale_preview_image(ud); }