/* * photobooth.c * Copyright 2016 Andreas Frisch * * This program is licensed under the Creative Commons * Attribution-NonCommercial-ShareAlike 3.0 Unported * License. To view a copy of this license, visit * http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to * Creative Commons,559 Nathan Abbott Way,Stanford,California 94305,USA. * * This program is NOT free software. It is open source, you are allowed * to modify it (if you keep the license), but it may not be commercially * distributed other than under the conditions noted above. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "photobooth.h" #include "photoboothwin.h" #define G_SETTINGS_ENABLE_BACKEND #include #define photo_booth_parent_class parent_class typedef struct _PhotoBoothPrivate PhotoBoothPrivate; struct _PhotoBoothPrivate { PhotoboothState state; PhotoBoothWindow *win; GstVideoRectangle video_size; GThread *capture_thread; gulong video_block_id, photo_block_id, sink_block_id; guint32 countdown; gchar *overlay_image; gchar *printer_backend; gint print_dpi, print_width, print_height; gdouble print_x_offset, print_y_offset; gchar *print_icc_profile; gint prints_remaining; GstBuffer *print_buffer; GtkPrintSettings *printer_settings; GMutex processing_mutex; gint preview_fps, preview_width, preview_height; gboolean cam_reeinit_before_snapshot, cam_reeinit_after_snapshot; gchar *cam_icc_profile; GstElement *audio_pipeline; GstElement *audio_playbin; GstElement *screensaver_playbin; gchar *countdown_audio_uri; gchar *screensaver_uri; gint screensaver_timeout; guint screensaver_timeout_id; GstClockTime last_play_pos; }; #define MOVIEPIPE "moviepipe.mjpg" #define DEFAULT_CONFIG "default.ini" #define PREVIEW_FPS 24 #define DEFAULT_COUNTDOWN 5 #define DEFAULT_SCREENSAVER_TIMEOUT -1 #define PRINT_DPI 346 #define PRINT_WIDTH 2076 #define PRINT_HEIGHT 1384 #define PREVIEW_WIDTH 640 #define PREVIEW_HEIGHT 424 #define PT_PER_IN 72 G_DEFINE_TYPE_WITH_PRIVATE (PhotoBooth, photo_booth, GTK_TYPE_APPLICATION); GST_DEBUG_CATEGORY_STATIC (photo_booth_debug); #define GST_CAT_DEFAULT photo_booth_debug /* GObject / GApplication */ static void photo_booth_activate (GApplication *app); static void photo_booth_open (GApplication *app, GFile **files, gint n_files, const gchar *hint); static void photo_booth_dispose (GObject *object); static void photo_booth_finalize (GObject *object); PhotoBooth *photo_booth_new (void); void photo_booth_background_clicked (GtkWidget *widget, GdkEventButton *event, PhotoBoothWindow *win); void photo_booth_button_yes_clicked (GtkButton *button, PhotoBoothWindow *win); /* general private functions */ const gchar* photo_booth_state_get_name (PhotoboothState state); static void photo_booth_change_state (PhotoBooth *pb, PhotoboothState state); static void photo_booth_quit_signal (PhotoBooth *pb); static void photo_booth_window_destroyed_signal (PhotoBoothWindow *win, PhotoBooth *pb); static void photo_booth_setup_window (PhotoBooth *pb); static void photo_booth_video_widget_ready (PhotoBooth *pb); static gboolean photo_booth_preview (PhotoBooth *pb); static gboolean photo_booth_preview_ready (PhotoBooth *pb); static void photo_booth_snapshot_start (PhotoBooth *pb); static gboolean photo_booth_snapshot_prepare (PhotoBooth *pb); static gboolean photo_booth_snapshot_trigger (PhotoBooth *pb); static gboolean photo_booth_snapshot_taken (PhotoBooth *pb); static gboolean photo_booth_screensaver (PhotoBooth *pb); static gboolean photo_booth_screensaver_stop (PhotoBooth *pb); /* libgphoto2 */ static gboolean photo_booth_cam_init (CameraInfo **cam_info); static gboolean photo_booth_cam_close (CameraInfo **cam_info); static gboolean photo_booth_focus (CameraInfo *cam_info); static gboolean photo_booth_take_photo (CameraInfo *cam_info); static void photo_booth_flush_pipe (int fd); static void photo_booth_capture_thread_func (PhotoBooth *pb); static void _gphoto_err(GPLogLevel level, const char *domain, const char *str, void *data); /* gstreamer functions */ static GstElement *build_video_bin (PhotoBooth *pb); static GstElement *build_photo_bin (PhotoBooth *pb); static gboolean photo_booth_setup_gstreamer (PhotoBooth *pb); static gboolean photo_booth_bus_callback (GstBus *bus, GstMessage *message, PhotoBooth *pb); static GstPadProbeReturn photo_booth_catch_photo_buffer (GstPad * pad, GstPadProbeInfo * info, gpointer user_data); static gboolean photo_booth_process_photo_plug_elements (PhotoBooth *pb); static GstFlowReturn photo_booth_catch_print_buffer (GstElement * appsink, gpointer user_data); static gboolean photo_booth_process_photo_remove_elements (PhotoBooth *pb); static void photo_booth_free_print_buffer (PhotoBooth *pb); /* printing functions */ static gboolean photo_booth_get_printer_status (PhotoBooth *pb); static void photo_booth_print (PhotoBooth *pb); void photo_booth_button_yes_clicked (GtkButton *button, PhotoBoothWindow *win); static void photo_booth_begin_print (GtkPrintOperation *operation, GtkPrintContext *context, gpointer user_data); static void photo_booth_draw_page (GtkPrintOperation *operation, GtkPrintContext *context, int page_nr, gpointer user_data); static void photo_booth_print_done (GtkPrintOperation *operation, GtkPrintOperationResult result, gpointer user_data); static void photo_booth_printing_error_dialog (PhotoBoothWindow *window, GError *print_error); static void photo_booth_class_init (PhotoBoothClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GApplicationClass *gapplication_class = G_APPLICATION_CLASS (klass); GST_DEBUG_CATEGORY_INIT (photo_booth_debug, "photobooth", GST_DEBUG_BOLD | GST_DEBUG_FG_YELLOW | GST_DEBUG_BG_BLUE, "PhotoBooth"); GST_DEBUG ("photo_booth_class_init"); gp_log_add_func(GP_LOG_ERROR, _gphoto_err, NULL); gobject_class->finalize = photo_booth_finalize; gobject_class->dispose = photo_booth_dispose; gapplication_class->activate = photo_booth_activate; gapplication_class->open = photo_booth_open; } static void photo_booth_init (PhotoBooth *pb) { PhotoBoothPrivate *priv; priv = photo_booth_get_instance_private (pb); GST_DEBUG_OBJECT (pb, "photo_booth_init init object!"); int control_sock[2]; if (socketpair (PF_UNIX, SOCK_STREAM, 0, control_sock) < 0) { GST_ERROR_OBJECT (pb, "cannot create control sockets: %s (%i)", strerror(errno), errno); g_application_quit (G_APPLICATION (pb)); } READ_SOCKET (pb) = control_sock[0]; WRITE_SOCKET (pb) = control_sock[1]; fcntl (READ_SOCKET (pb), F_SETFL, O_NONBLOCK); fcntl (WRITE_SOCKET (pb), F_SETFL, O_NONBLOCK); pb->cam_info = NULL; pb->pipeline = NULL; priv->state = PB_STATE_NONE; priv->video_block_id = 0; priv->photo_block_id = 0; priv->sink_block_id = 0; if (mkfifo(MOVIEPIPE, 0666) == -1 && errno != EEXIST) { GST_ERROR_OBJECT (pb, "cannot create moviepipe file %s: %s (%i)", MOVIEPIPE, strerror(errno), errno); g_application_quit (G_APPLICATION (pb)); } pb->video_fd = open(MOVIEPIPE, O_RDWR); if (pb->video_fd == -1) { GST_ERROR_OBJECT (pb, "cannot open moviepipe file %s: %s (%i)", MOVIEPIPE, strerror(errno), errno); g_application_quit (G_APPLICATION (pb)); } priv->capture_thread = NULL; priv->countdown = DEFAULT_COUNTDOWN; priv->preview_fps = PREVIEW_FPS; priv->preview_width = PREVIEW_WIDTH; priv->preview_height = PREVIEW_HEIGHT; priv->print_dpi = PRINT_DPI; priv->print_width = PRINT_WIDTH; priv->print_height = PRINT_HEIGHT; priv->print_x_offset = priv->print_y_offset = 0; priv->print_buffer = NULL; priv->print_icc_profile = NULL; priv->cam_icc_profile = NULL; priv->printer_backend = NULL; priv->printer_settings = NULL; priv->overlay_image = NULL; priv->countdown_audio_uri = NULL; priv->screensaver_uri = NULL; priv->screensaver_timeout = DEFAULT_SCREENSAVER_TIMEOUT; priv->screensaver_timeout_id = 0; priv->last_play_pos = GST_CLOCK_TIME_NONE; G_stylesheet_filename = NULL; G_template_filename = NULL; G_strings_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_mutex_init (&priv->processing_mutex); } static void photo_booth_change_state (PhotoBooth *pb, PhotoboothState newstate) { PhotoBoothPrivate *priv; priv = photo_booth_get_instance_private (pb); GST_DEBUG_OBJECT (pb, "change state %s -> %s", photo_booth_state_get_name (priv->state), photo_booth_state_get_name (newstate)); gchar *dot_filename = g_strdup_printf ("state_change_%s_to_%s", photo_booth_state_get_name (priv->state), photo_booth_state_get_name (newstate)); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, dot_filename); g_free (dot_filename); priv->state = newstate; } static void photo_booth_setup_window (PhotoBooth *pb) { PhotoBoothPrivate *priv; priv = photo_booth_get_instance_private (pb); priv->win = photo_booth_window_new (pb); gtk_window_present (GTK_WINDOW (priv->win)); g_signal_connect (G_OBJECT (priv->win), "destroy", G_CALLBACK (photo_booth_window_destroyed_signal), pb); priv->capture_thread = g_thread_try_new ("gphoto-capture", (GThreadFunc) photo_booth_capture_thread_func, pb, NULL); photo_booth_setup_gstreamer (pb); photo_booth_get_printer_status (pb); } static void photo_booth_activate (GApplication *app) { GST_DEBUG_OBJECT (app, "photo_booth_activate"); photo_booth_setup_window (PHOTO_BOOTH (app)); } static void photo_booth_open (GApplication *app, GFile **files, gint n_files, const gchar *hint) { GST_DEBUG_OBJECT (app, "photo_booth_open"); photo_booth_setup_window (PHOTO_BOOTH (app)); } static void photo_booth_finalize (GObject *object) { PhotoBooth *pb = PHOTO_BOOTH (object); PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); GST_INFO_OBJECT (pb, "finalize"); SEND_COMMAND (pb, CONTROL_QUIT); photo_booth_flush_pipe (pb->video_fd); g_thread_join (priv->capture_thread); if (pb->cam_info) photo_booth_cam_close (&pb->cam_info); if (pb->video_fd) { close (pb->video_fd); unlink (MOVIEPIPE); } } static void photo_booth_dispose (GObject *object) { PhotoBoothPrivate *priv; priv = photo_booth_get_instance_private (PHOTO_BOOTH (object)); g_free (priv->printer_backend); if (priv->printer_settings != NULL) g_object_unref (priv->printer_settings); g_free (priv->countdown_audio_uri); g_free (priv->screensaver_uri); g_free (priv->print_icc_profile); g_free (priv->cam_icc_profile); g_free (priv->overlay_image); g_hash_table_destroy (G_strings_table); g_mutex_clear (&priv->processing_mutex); G_OBJECT_CLASS (photo_booth_parent_class)->dispose (object); g_free (G_stylesheet_filename); g_free (G_template_filename); } void photo_booth_load_settings (PhotoBooth *pb, const gchar *filename) { GKeyFile* gkf; GError *error = NULL; guint keyidx; gsize num_keys; gchar **keys, *val; gchar *key; gchar *value; PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); GST_DEBUG_OBJECT (pb, "loading settings from file %s", filename); gkf = g_key_file_new(); if (g_key_file_load_from_file (gkf, filename, G_KEY_FILE_NONE, &error)) { if (g_key_file_has_group (gkf, "strings")) { keys = g_key_file_get_keys (gkf, "strings", &num_keys, &error); for (keyidx = 0; keyidx < num_keys; keyidx++) { val = g_key_file_get_value(gkf, "strings", keys[keyidx], &error); key = g_strdup(keys[keyidx]); value = g_strdup(val); g_hash_table_insert (G_strings_table, key, value); GST_LOG ("key %u/%u:\t'%s' => '%s'", keyidx, num_keys-1, key, value); } if (error) { GST_INFO ( "can't read string: %s", error->message); g_error_free (error); } } if (g_key_file_has_group (gkf, "general")) { G_template_filename = g_key_file_get_string (gkf, "general", "template", NULL); G_stylesheet_filename = g_key_file_get_string (gkf, "general", "stylesheet", NULL); priv->countdown = g_key_file_get_integer (gkf, "general", "countdown", NULL); gchar *audiofile = g_key_file_get_string (gkf, "general", "countdown_audio_file", NULL); if (audiofile) { gchar *audioabsfilename; if (audiofile[0] != '/') { gchar *cur = g_get_current_dir (); audioabsfilename = g_strdup_printf ("%s/%s", cur, audiofile); } else audioabsfilename = g_strdup (audiofile); priv->countdown_audio_uri = g_filename_to_uri (audioabsfilename, NULL, NULL); g_free (audiofile); g_free (audioabsfilename); } priv->overlay_image = g_key_file_get_string (gkf, "general", "overlay_image", NULL); priv->screensaver_timeout = g_key_file_get_integer (gkf, "general", "screensaver_timeout", NULL); gchar *screensaverfile = g_key_file_get_string (gkf, "general", "screensaver_file", NULL); if (screensaverfile) { gchar *screensaverabsfilename; if (screensaverfile[0] != '/') { gchar *cur = g_get_current_dir (); screensaverabsfilename = g_strdup_printf ("%s/%s", cur, screensaverfile); } else screensaverabsfilename = g_strdup (screensaverfile); priv->screensaver_uri = g_filename_to_uri (screensaverabsfilename, NULL, NULL); g_free (screensaverfile); g_free (screensaverabsfilename); } } if (g_key_file_has_group (gkf, "printer")) { priv->printer_backend = g_key_file_get_string (gkf, "printer", "backend", NULL); priv->print_dpi = g_key_file_get_integer (gkf, "printer", "dpi", NULL); priv->print_width = g_key_file_get_integer (gkf, "printer", "width", NULL); priv->print_height = g_key_file_get_integer (gkf, "printer", "height", NULL); priv->print_icc_profile = g_key_file_get_string (gkf, "printer", "icc_profile", NULL); priv->print_x_offset = g_key_file_get_double (gkf, "printer", "offset_x", NULL); priv->print_y_offset = g_key_file_get_double (gkf, "printer", "offset_y", NULL); } if (g_key_file_has_group (gkf, "camera")) { priv->preview_fps = g_key_file_get_integer (gkf, "camera", "fps", NULL); priv->preview_width = g_key_file_get_integer (gkf, "camera", "preview_width", NULL); priv->preview_height = g_key_file_get_integer (gkf, "camera", "preview_height", NULL); priv->cam_reeinit_before_snapshot = g_key_file_get_boolean (gkf, "camera", "cam_reeinit_before_snapshot", NULL); priv->cam_reeinit_after_snapshot = g_key_file_get_boolean (gkf, "camera", "cam_reeinit_after_snapshot", NULL); priv->cam_icc_profile = g_key_file_get_string (gkf, "camera", "icc_profile", NULL); } } if (error) { GST_INFO ( "can't open settings file %s: %s", filename, error->message); g_error_free (error); } } static void _gphoto_err(GPLogLevel level, const char *domain, const char *str, void *data) { GST_DEBUG ("GPhoto %d, %s:%s", (int) level, domain, str); } static GstPadProbeReturn _gst_photo_probecb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) { GST_DEBUG_OBJECT (pad, "drop photo"); return GST_PAD_PROBE_DROP; } static GstPadProbeReturn _gst_video_probecb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) { GST_DEBUG_OBJECT (pad, "drop video"); return GST_PAD_PROBE_DROP; } static gboolean photo_booth_cam_init (CameraInfo **cam_info) { int retval; *cam_info = (CameraInfo*)malloc(sizeof(struct _CameraInfo)); if (!cam_info) return FALSE; g_mutex_init (&(*cam_info)->mutex); g_mutex_lock (&(*cam_info)->mutex); (*cam_info)->preview_capture_count = 0; (*cam_info)->size = 0; (*cam_info)->data = NULL; (*cam_info)->context = gp_context_new(); gp_camera_new(&(*cam_info)->camera); retval = gp_camera_init((*cam_info)->camera, (*cam_info)->context); GST_DEBUG ("gp_camera_init returned %d cam_info@%p camera@%p", retval, *cam_info, (*cam_info)->camera); g_mutex_unlock (&(*cam_info)->mutex); if (retval != GP_OK) { g_mutex_clear (&(*cam_info)->mutex); free (*cam_info); *cam_info = NULL; return FALSE; } return TRUE; } static gboolean photo_booth_cam_close (CameraInfo **cam_info) { int retval; g_mutex_lock (&(*cam_info)->mutex); retval = gp_camera_exit((*cam_info)->camera, (*cam_info)->context); GST_DEBUG ("gp_camera_exit returned %i", retval); g_mutex_unlock (&(*cam_info)->mutex); g_mutex_clear (&(*cam_info)->mutex); free (*cam_info); *cam_info = NULL; return GP_OK ? TRUE : FALSE; } static void photo_booth_flush_pipe (int fd) { GST_DEBUG ("flushing"); int rlen = 0; unsigned char buf[1024]; const int flags = fcntl(fd, F_GETFL, 0); fcntl (fd, F_SETFL, flags | O_NONBLOCK); while (rlen != -1) { rlen = read (fd, buf, sizeof(buf)); GST_DEBUG ("flushed %i", rlen); } fcntl (fd, F_SETFL, flags ^ O_NONBLOCK); GST_DEBUG ("finished flushing"); } static void photo_booth_quit_signal (PhotoBooth *pb) { GST_INFO_OBJECT (pb, "caught SIGINT! exit..."); g_application_quit (G_APPLICATION (pb)); } static void photo_booth_window_destroyed_signal (PhotoBoothWindow *win, PhotoBooth *pb) { GST_INFO_OBJECT (pb, "main window closed! exit..."); g_application_quit (G_APPLICATION (pb)); } static void photo_booth_capture_thread_func (PhotoBooth *pb) { PhotoboothCaptureThreadState state = CAPTURE_INIT; PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); CameraFile *gp_file = NULL; int gpret, captured_frames = 0; GST_DEBUG_OBJECT (pb, "enter capture thread fd = %d", pb->video_fd); if (gp_file_new_from_fd (&gp_file, pb->video_fd) != GP_OK) { GST_ERROR_OBJECT (pb, "couldn't start capture thread because gp_file_new_from_fd (%d) failed!", pb->video_fd); goto quit_thread; } while (TRUE) { if (state == CAPTURE_QUIT) goto quit_thread; struct pollfd rfd[2]; int timeout = 0; rfd[0].fd = READ_SOCKET (pb); rfd[0].events = POLLIN | POLLERR | POLLHUP | POLLPRI; if (state == CAPTURE_INIT || state == CAPTURE_FAILED && !pb->cam_info) { if (photo_booth_cam_init (&pb->cam_info)) { GST_INFO_OBJECT (pb, "photo_booth_cam_inited @ %p", pb->cam_info); if (state == CAPTURE_FAILED) photo_booth_window_set_spinner (priv->win, FALSE); state = CAPTURE_VIDEO; g_main_context_invoke (NULL, (GSourceFunc) photo_booth_preview, pb); } else { gtk_label_set_text (priv->win->status, _("No camera connected!")); GST_INFO_OBJECT (pb, "no camera info."); } timeout = 5000; } else if (state == CAPTURE_UNPAUSE && !pb->cam_info) { if (photo_booth_cam_init (&pb->cam_info)) state = CAPTURE_VIDEO; else state = CAPTURE_INIT; } else if (state == CAPTURE_PAUSED) timeout = 1000; else timeout = 1000 / PREVIEW_FPS; int ret = poll(rfd, 1, timeout); GST_TRACE_OBJECT (pb, "poll ret=%i, state=%i, cam_info@%p", ret, state, pb->cam_info); if (G_UNLIKELY (ret == -1)) { GST_ERROR_OBJECT (pb, "SELECT ERROR!"); goto quit_thread; } else if (ret == 0 && state == CAPTURE_VIDEO) { const char *mime; if (pb->cam_info) { g_mutex_lock (&pb->cam_info->mutex); gpret = gp_camera_capture_preview (pb->cam_info->camera, gp_file, pb->cam_info->context); g_mutex_unlock (&pb->cam_info->mutex); if (gpret < 0) { GST_ERROR_OBJECT (pb, "Movie capture error %d", gpret); if (gpret == -7) { state = CAPTURE_FAILED; photo_booth_change_state (pb, PB_STATE_NONE); photo_booth_cam_close (&pb->cam_info); } continue; } else { gp_file_get_mime_type (gp_file, &mime); if (strcmp (mime, GP_MIME_JPEG)) { GST_ERROR_OBJECT ("Movie capture error... Unhandled MIME type '%s'.", mime); continue; } captured_frames++; GST_LOG_OBJECT (pb, "captured frame (%d frames total)", captured_frames); } } } else if (ret == 0 && state == CAPTURE_PRETRIGGER) { gtk_label_set_text (priv->win->status, _("Focussing...")); // photo_booth_focus (pb->cam_info); if (priv->cam_reeinit_before_snapshot) { photo_booth_cam_close (&pb->cam_info); photo_booth_cam_init (&pb->cam_info); } } else if (ret == 0 && state == CAPTURE_PHOTO) { if (pb->cam_info) { gtk_label_set_text (priv->win->status, _("Taking photo...")); ret = photo_booth_take_photo (pb->cam_info); if (ret) { g_main_context_invoke (NULL, (GSourceFunc) photo_booth_snapshot_taken, pb); if (priv->cam_reeinit_after_snapshot) { photo_booth_cam_close (&pb->cam_info); photo_booth_cam_init (&pb->cam_info); } } else { gtk_label_set_text (priv->win->status, _("Taking photo failed!")); GST_ERROR_OBJECT (pb, "Taking photo failed!"); photo_booth_cam_close (&pb->cam_info); photo_booth_change_state (pb, PB_STATE_NONE); state = CAPTURE_FAILED; } } } else if (rfd[0].revents) { char command; READ_COMMAND (pb, command, ret); switch (command) { case CONTROL_PAUSE: GST_DEBUG_OBJECT (pb, "CONTROL_PAUSE!"); state = CAPTURE_PAUSED; break; case CONTROL_UNPAUSE: GST_DEBUG_OBJECT (pb, "CONTROL_UNPAUSE!"); state = CAPTURE_UNPAUSE; break; case CONTROL_VIDEO: GST_DEBUG_OBJECT (pb, "CONTROL_VIDEO"); state = CAPTURE_VIDEO; break; case CONTROL_PRETRIGGER: GST_DEBUG_OBJECT (pb, "CONTROL_PRETRIGGER"); state = CAPTURE_PRETRIGGER; break; case CONTROL_PHOTO: GST_DEBUG_OBJECT (pb, "CONTROL_PHOTO"); state = CAPTURE_PHOTO; break; case CONTROL_QUIT: GST_DEBUG_OBJECT (pb, "CONTROL_QUIT!"); state = CAPTURE_QUIT; break; default: GST_ERROR_OBJECT (pb, "illegal control socket command %c received!", command); } continue; } else if (state == CAPTURE_PAUSED) { if (pb->cam_info) { GST_LOG_OBJECT (pb, "captured thread paused... close camera! %s", photo_booth_state_get_name (priv->state)); photo_booth_cam_close (&pb->cam_info); // photo_booth_flush_pipe (pb->video_fd); } else GST_LOG_OBJECT (pb, "captured thread paused... timeout. %s", photo_booth_state_get_name (priv->state)); } } g_assert_not_reached (); return; quit_thread: { if (gp_file) gp_file_unref (gp_file); GST_DEBUG ("stop running, exit thread, %d frames captured", captured_frames); return; } } static GstElement *build_video_bin (PhotoBooth *pb) { PhotoBoothPrivate *priv; GstElement *video_bin; GstElement *mjpeg_source, *mjpeg_decoder, *mjpeg_filter, *video_filter, *video_scale, *video_convert, *video_overlay; GstCaps *caps; GstPad *ghost, *pad; priv = photo_booth_get_instance_private (pb); video_bin = gst_element_factory_make ("bin", "video-bin"); mjpeg_source = gst_element_factory_make ("fdsrc", "mjpeg-fdsrc"); g_object_set (mjpeg_source, "fd", pb->video_fd, NULL); g_object_set (mjpeg_source, "do-timestamp", TRUE, NULL); g_object_set (mjpeg_source, "blocksize", 65536, NULL); mjpeg_filter = gst_element_factory_make ("capsfilter", "mjpeg-capsfilter"); caps = gst_caps_new_simple ("image/jpeg", "width", G_TYPE_INT, priv->preview_width, "height", G_TYPE_INT, priv->preview_height, "framerate", GST_TYPE_FRACTION, PREVIEW_FPS, 1, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL); g_object_set (G_OBJECT (mjpeg_filter), "caps", caps, NULL); gst_caps_unref (caps); mjpeg_decoder = gst_element_factory_make ("jpegdec", "mjpeg-decoder"); video_scale = gst_element_factory_make ("videoscale", "mjpeg-videoscale"); video_convert = gst_element_factory_make ("videoconvert", "mjpeg-videoconvert"); video_filter = gst_element_factory_make ("capsfilter", "video-capsfilter"); caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, priv->preview_width, "height", G_TYPE_INT, priv->preview_height, NULL); g_object_set (G_OBJECT (video_filter), "caps", caps, NULL); gst_caps_unref (caps); video_overlay = gst_element_factory_make ("gdkpixbufoverlay", "video-overlay"); if (priv->overlay_image) g_object_set (video_overlay, "location", priv->overlay_image, NULL); if (!(mjpeg_source && mjpeg_filter && mjpeg_decoder && video_scale && video_convert && video_filter && video_overlay)) { GST_ERROR_OBJECT (video_bin, "Failed to make videobin pipeline element(s):%s%s%s%s%s%s%s", mjpeg_source?"":" fdsrc", mjpeg_filter?"":" capsfilter", mjpeg_decoder?"":" jpegdec", video_scale?"":" videoscale", video_convert?"":" videoconvert", video_filter?"":" capsfilter", video_overlay?"":" gdkpixbufoverlay"); return FALSE; } gst_bin_add_many (GST_BIN (video_bin), mjpeg_source, mjpeg_filter, mjpeg_decoder, video_scale, video_convert, video_filter, video_overlay, NULL); if (!gst_element_link_many (mjpeg_source, mjpeg_filter, mjpeg_decoder, video_scale, video_convert, video_filter, video_overlay, NULL)) { GST_ERROR_OBJECT (video_bin, "couldn't link videobin elements!"); return FALSE; } pad = gst_element_get_static_pad (video_overlay, "src"); ghost = gst_ghost_pad_new ("src", pad); gst_object_unref (pad); gst_pad_set_active (ghost, TRUE); gst_element_add_pad (video_bin, ghost); return video_bin; } static GstElement *build_photo_bin (PhotoBooth *pb) { PhotoBoothPrivate *priv; GstElement *photo_bin; GstElement *photo_source, *photo_decoder, *photo_freeze, *photo_scale, *photo_filter, *photo_overlay, *photo_convert, *photo_gamma, *photo_tee; GstCaps *caps; GstPad *ghost, *pad; priv = photo_booth_get_instance_private (pb); photo_bin = gst_element_factory_make ("bin", "photo-bin"); photo_source = gst_element_factory_make ("appsrc", "photo-appsrc"); photo_decoder = gst_element_factory_make ("jpegdec", "photo-decoder"); photo_freeze = gst_element_factory_make ("imagefreeze", "photo-freeze"); photo_scale = gst_element_factory_make ("videoscale", "photo-scale"); photo_filter = gst_element_factory_make ("capsfilter", "photo-capsfilter"); caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, priv->print_width, "height", G_TYPE_INT, priv->print_height, "framerate", GST_TYPE_FRACTION, 10, 1, NULL); g_object_set (G_OBJECT (photo_filter), "caps", caps, NULL); gst_caps_unref (caps); photo_overlay = gst_element_factory_make ("gdkpixbufoverlay", "photo-overlay"); if (priv->overlay_image) g_object_set (photo_overlay, "location", priv->overlay_image, NULL); g_object_set (photo_overlay, "overlay-width", priv->print_width, NULL); g_object_set (photo_overlay, "overlay-height", priv->print_height, NULL); photo_convert = gst_element_factory_make ("videoconvert", "photo-convert"); photo_gamma = gst_element_factory_make ("gamma", "photo-gamma"); g_object_set (photo_gamma, "gamma", 1.0, NULL); photo_tee = gst_element_factory_make ("tee", "photo-tee"); if (!(photo_bin && photo_source && photo_decoder && photo_freeze && photo_scale && photo_filter, photo_overlay && photo_convert && photo_tee)) { GST_ERROR_OBJECT (photo_bin, "Failed to make photobin pipeline element(s)"); return FALSE; } gst_bin_add_many (GST_BIN (photo_bin), photo_source, photo_decoder, photo_freeze, photo_scale, photo_filter, photo_overlay, photo_convert, photo_gamma, photo_tee, NULL); if (!gst_element_link_many (photo_source, photo_decoder, photo_freeze, photo_scale, photo_filter, photo_overlay, photo_convert, photo_gamma, photo_tee, NULL)) { GST_ERROR_OBJECT (photo_bin, "couldn't link photobin elements!"); return FALSE; } pad = gst_element_get_request_pad (photo_tee, "src_%u"); ghost = gst_ghost_pad_new ("src", pad); gst_object_unref (pad); gst_pad_set_active (ghost, TRUE); gst_element_add_pad (photo_bin, ghost); return photo_bin; } static gboolean photo_booth_setup_gstreamer (PhotoBooth *pb) { PhotoBoothPrivate *priv; GstBus *bus; GtkWidget *gtkgstwidget; GstPad *ghost, *pad; priv = photo_booth_get_instance_private (pb); pb->video_bin = build_video_bin (pb); pb->photo_bin = build_photo_bin (pb); pb->pipeline = gst_pipeline_new ("photobooth-pipeline"); pb->video_sink = gst_element_factory_make ("gtksink", "video-sink"); // g_object_set (pb->video_sink, "sync", FALSE, NULL); if (!(pb->video_sink)) { GST_ERROR_OBJECT (pb, "Failed to create gtksink"); return FALSE; } g_object_get (pb->video_sink, "widget", >kgstwidget, NULL); photo_booth_window_add_gtkgstwidget (priv->win, gtkgstwidget); g_object_unref (gtkgstwidget); gst_element_set_state (pb->pipeline, GST_STATE_PLAYING); gst_element_set_state (pb->video_sink, GST_STATE_PLAYING); gst_bin_add_many (GST_BIN (pb->pipeline), pb->video_bin, pb->photo_bin, pb->video_sink, NULL); /* add watch for messages */ bus = gst_pipeline_get_bus (GST_PIPELINE (pb->pipeline)); gst_bus_add_watch (bus, (GstBusFunc) photo_booth_bus_callback, pb); gst_object_unref (GST_OBJECT (bus)); priv->audio_pipeline = gst_pipeline_new ("audio-pipeline"); priv->audio_playbin = gst_element_factory_make ("playbin", "audio-playbin"); gst_bin_add (GST_BIN (priv->audio_pipeline), priv->audio_playbin); return TRUE; } static gboolean photo_booth_bus_callback (GstBus *bus, GstMessage *message, PhotoBooth *pb) { GstObject *src = GST_MESSAGE_SRC (message); PhotoBoothPrivate *priv; priv = photo_booth_get_instance_private (pb); switch (GST_MESSAGE_TYPE (message)) { case GST_MESSAGE_WARNING: { GError *err = NULL; gchar *debug = NULL; gst_message_parse_warning (message, &err, &debug); GST_WARNING ("Warning: %s\n", err->message); g_error_free (err); g_free (debug); break; } case GST_MESSAGE_ERROR: { GError *err = NULL; gchar *debug = NULL; gst_message_parse_error (message, &err, &debug); GST_ERROR ("Error: %s : %s", err->message, debug); g_error_free (err); g_free (debug); gtk_main_quit (); break; } case GST_MESSAGE_EOS: { if (src == GST_OBJECT (priv->screensaver_playbin)) { GST_DEBUG ("screensaver EOS, replay"); gst_element_seek (priv->screensaver_playbin, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); } break; } case GST_MESSAGE_STATE_CHANGED: { GstState old_state, new_state; gst_message_parse_state_changed (message, &old_state, &new_state, NULL); GstStateChange transition = (GstStateChange)GST_STATE_TRANSITION (old_state, new_state); GST_LOG ("gst %" GST_PTR_FORMAT " state transition %s -> %s. %s", src, gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT(transition)), gst_element_state_get_name(GST_STATE_TRANSITION_NEXT(transition)), photo_booth_state_get_name (priv->state)); if (src == GST_OBJECT (pb->video_sink) && transition == GST_STATE_CHANGE_READY_TO_PAUSED) { photo_booth_video_widget_ready (pb); } if (src == GST_OBJECT (pb->video_sink) && transition == GST_STATE_CHANGE_PAUSED_TO_PLAYING) { GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "photo_booth_video_start"); GST_DEBUG ("video_bin GST_STATE_CHANGE_PAUSED_TO_PLAYING -> hide spinner!"); photo_booth_window_set_spinner (priv->win, FALSE); } if (src == GST_OBJECT (priv->screensaver_playbin) && transition == GST_STATE_CHANGE_READY_TO_PAUSED) { GST_DEBUG ("screensaver_playbin GST_STATE_CHANGE_READY_TO_PAUSED last_play_pos=%" GST_TIME_FORMAT "", GST_TIME_ARGS (priv->last_play_pos)); if (priv->last_play_pos != GST_CLOCK_TIME_NONE) gst_element_seek (priv->screensaver_playbin, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, priv->last_play_pos, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); } break; } case GST_MESSAGE_STREAM_START: { GST_DEBUG ("GST_MESSAGE_STREAM_START! state=%s", photo_booth_state_get_name (priv->state)); } default: { // GST_DEBUG ("gst_message from %" GST_PTR_FORMAT ": %" GST_PTR_FORMAT "", GST_MESSAGE_SRC(message), message); } } return TRUE; } static void photo_booth_video_widget_ready (PhotoBooth *pb) { PhotoBoothPrivate *priv; GtkRequisition size; GtkAllocation size2; GstVideoRectangle s1, s2, rect; GstElement *element; GstCaps *caps; priv = photo_booth_get_instance_private (pb); gtk_widget_get_preferred_size (priv->win->gtkgstwidget, NULL, &size); gtk_widget_get_allocated_size (priv->win->gtkgstwidget, &size2, NULL); s1.w = size.width; s1.h = size.height; s2.w = size2.width; s2.h = size2.height; gst_video_sink_center_rect (s1, s2, &rect, TRUE); GST_DEBUG_OBJECT (pb, "gtksink widget is ready. preferred dimensions: %dx%d allocated %dx%d", size.width, size.height, size2.width, size2.height); element = gst_bin_get_by_name (GST_BIN (pb->video_bin), "video-capsfilter"); caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, rect.w, "height", G_TYPE_INT, rect.h, NULL); g_object_set (G_OBJECT (element), "caps", caps, NULL); gst_caps_unref (caps); gst_object_unref (element); element = gst_bin_get_by_name (GST_BIN (pb->video_bin), "video-overlay"); g_object_set (element, "overlay-width", rect.w, NULL); g_object_set (element, "overlay-height", rect.h, NULL); gst_object_unref (element); GST_DEBUG_OBJECT (pb, "gtksink widget is ready. output dimensions: %dx%d", rect.w, rect.h); priv->video_size = rect; } static gboolean photo_booth_preview (PhotoBooth *pb) { PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); GstPad *pad; if (!priv->photo_block_id) { gst_element_set_state (pb->photo_bin, GST_STATE_READY); pad = gst_element_get_static_pad (pb->photo_bin, "src"); GST_DEBUG_OBJECT (pad, "photo_booth_preview! halt photo_bin..."); priv->photo_block_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, _gst_photo_probecb, pb, NULL); gst_object_unref (pad); gst_element_unlink (pb->photo_bin, pb->video_sink); } if (priv->video_block_id) { pad = gst_element_get_static_pad (pb->video_bin, "src"); GST_DEBUG_OBJECT (pad, "photo_booth_preview! unblock video_bin pad@%p...", pad); gst_pad_remove_probe (pad, priv->video_block_id); gst_object_unref (pad); } if (priv->sink_block_id) { pad = gst_element_get_static_pad (pb->video_sink, "sink"); GST_DEBUG_OBJECT (pad, "photo_booth_preview! unblock video_sink pad@%p...", pad); gst_pad_remove_probe (pad, priv->sink_block_id); gst_object_unref (pad); gst_element_set_state (pb->video_sink, GST_STATE_PLAYING); } int ret = gst_element_link (pb->video_bin, pb->video_sink); GST_LOG_OBJECT (pb, "linked video-bin ! video-sink ret=%i", ret); gst_element_set_state (pb->video_bin, GST_STATE_PLAYING); int cooldown_delay = 2000; if (priv->state == PB_STATE_NONE) cooldown_delay = 10; photo_booth_change_state (pb, PB_STATE_PREVIEW_COOLDOWN); gtk_label_set_text (priv->win->status, _("Please wait...")); g_timeout_add (cooldown_delay, (GSourceFunc) photo_booth_preview_ready, pb); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "photo_booth_preview"); SEND_COMMAND (pb, CONTROL_VIDEO); return FALSE; } static gboolean photo_booth_preview_ready (PhotoBooth *pb) { PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); photo_booth_change_state (pb, PB_STATE_PREVIEW); gtk_label_set_text (priv->win->status, _("Touch screen to take a photo!")); if (priv->screensaver_timeout > 0) priv->screensaver_timeout_id = g_timeout_add_seconds (priv->screensaver_timeout, (GSourceFunc) photo_booth_screensaver, pb); return FALSE; } static gboolean photo_booth_screensaver (PhotoBooth *pb) { PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); photo_booth_change_state (pb, PB_STATE_SCREENSAVER); priv->screensaver_timeout_id = 0; GstPad *pad; if (!priv->photo_block_id) { gst_element_set_state (pb->photo_bin, GST_STATE_READY); pad = gst_element_get_static_pad (pb->photo_bin, "src"); GST_DEBUG_OBJECT (pad, "showing screensaver! halt photo_bin..."); priv->photo_block_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, _gst_photo_probecb, pb, NULL); gst_object_unref (pad); gst_element_unlink (pb->photo_bin, pb->video_sink); } if (!priv->video_block_id) { pad = gst_element_get_static_pad (pb->video_bin, "src"); gst_element_set_state (pb->video_bin, GST_STATE_READY); GST_DEBUG_OBJECT (pad, "showing screensaver! halt video_bin..."); priv->video_block_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, _gst_video_probecb, pb, NULL); GST_DEBUG_OBJECT (pad, "pad@%p id = %i", pad, priv->video_block_id); gst_object_unref (pad); gst_element_unlink (pb->video_bin, pb->video_sink); } SEND_COMMAND (pb, CONTROL_PAUSE); priv->screensaver_playbin = gst_element_factory_make ("playbin", "screensaver-playbin"); gst_object_ref (pb->video_sink); gst_bin_remove (GST_BIN (pb->pipeline), pb->video_sink); g_object_set (priv->screensaver_playbin, "video-sink", pb->video_sink, NULL); if (priv->screensaver_uri) g_object_set (priv->screensaver_playbin, "uri", priv->screensaver_uri, NULL); gst_element_set_state (priv->screensaver_playbin, GST_STATE_PLAYING); GstBus *bus; bus = gst_pipeline_get_bus (GST_PIPELINE (priv->screensaver_playbin)); gst_bus_add_watch (bus, (GstBusFunc) photo_booth_bus_callback, pb); gst_object_unref (GST_OBJECT (bus)); gtk_label_set_text (priv->win->status, _("Touch screen to take a photo!")); return FALSE; } static gboolean photo_booth_screensaver_stop (PhotoBooth *pb) { PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); GstPad *pad; pad = gst_element_get_static_pad (pb->video_sink, "sink"); priv->sink_block_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, _gst_video_probecb, pb, NULL); gst_element_query_position (priv->screensaver_playbin, GST_FORMAT_TIME, &priv->last_play_pos); GST_DEBUG ("stop screensaver @ %" GST_TIME_FORMAT "", GST_TIME_ARGS (priv->last_play_pos)); gst_element_set_state (priv->screensaver_playbin, GST_STATE_NULL); gst_element_set_state (pb->pipeline, GST_STATE_READY); gst_bin_add (GST_BIN (pb->pipeline), pb->video_sink); gst_object_unref (pb->video_sink); SEND_COMMAND (pb, CONTROL_UNPAUSE); photo_booth_preview (pb); return FALSE; } void photo_booth_background_clicked (GtkWidget *widget, GdkEventButton *event, PhotoBoothWindow *win) { PhotoBooth *pb = PHOTO_BOOTH_FROM_WINDOW (win); PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); priv = photo_booth_get_instance_private (pb); GST_INFO_OBJECT (pb, "background clicked in state %s", photo_booth_state_get_name (priv->state)); if (priv->screensaver_timeout_id) { int ret = g_source_remove (priv->screensaver_timeout_id); GST_DEBUG_OBJECT (pb, "removing screensaver_timeout"); priv->screensaver_timeout_id = 0; } switch (priv->state) { case PB_STATE_PREVIEW: { photo_booth_snapshot_start (pb); break; } case PB_STATE_COUNTDOWN: case PB_STATE_TAKING_PHOTO: case PB_STATE_PROCESS_PHOTO: case PB_STATE_PRINTING: case PB_STATE_PREVIEW_COOLDOWN: GST_DEBUG_OBJECT (pb, "BUSY... ignore event!"); break; case PB_STATE_WAITING_FOR_ANSWER: { gtk_widget_hide (GTK_WIDGET (priv->win->button_yes)); SEND_COMMAND (pb, CONTROL_UNPAUSE); photo_booth_preview (pb); break; } case PB_STATE_SCREENSAVER: { photo_booth_screensaver_stop (pb); break; } default: break; } // if (priv->prints_remaining < 1) // photo_booth_get_printer_status (pb); } static gboolean photo_booth_get_printer_status (PhotoBooth *pb) { PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); gchar *label_string; gchar *backend_environment = g_strdup_printf ("BACKEND=%s", priv->printer_backend); gchar *argv[] = { "/usr/lib/cups/backend/gutenprint52+usb", "-m", NULL }; gchar *envp[] = { backend_environment, NULL }; gchar *output = NULL; GError *error = NULL; gint remain = -1; gint ret = 0; if (!priv->printer_backend) { label_string = g_strdup_printf(_("No printer configured!")); } else if (g_spawn_sync (NULL, argv, envp, G_SPAWN_DEFAULT, NULL, NULL, NULL, &output, &ret, &error)) { GMatchInfo *match_info; GRegex *regex; if (ret == 0) { regex = g_regex_new ("INFO: Media type\\s.*?: (?\\d+) \\((?.*?)\\)\nINFO: Media remaining\\s.*?: (?\\d{3})/(?\\d{3})\n", G_REGEX_MULTILINE|G_REGEX_DOTALL, 0, &error); if (error) { g_critical ("%s\n", error->message); return FALSE; } if (g_regex_match (regex, output, 0, &match_info)) { guint code = atoi(g_match_info_fetch_named(match_info, "code")); gchar *size = g_match_info_fetch_named(match_info, "size"); remain = atoi(g_match_info_fetch_named(match_info, "remain")); guint total = atoi(g_match_info_fetch_named(match_info, "total")); label_string = g_strdup_printf(_("Printer %s online. %i prints (%s) remaining"), priv->printer_backend, remain, size); GST_INFO_OBJECT (pb, "printer %s status: media code %i (%s) prints remaining %i of %i", priv->printer_backend, code, size, remain, total); } else { label_string = g_strdup_printf(_("Can't parse printer backend output")); GST_ERROR_OBJECT (pb, "%s: '%s'", label_string, output); } } else { regex = g_regex_new ("ERROR: Printer open failure", G_REGEX_MULTILINE|G_REGEX_DOTALL, 0, &error); if (g_regex_match (regex, output, 0, &match_info)) { label_string = g_strdup_printf(_("Printer %s off-line"), priv->printer_backend); GST_WARNING_OBJECT (pb, "%s", label_string); } else { label_string = g_strdup_printf(_("can't parse printer backend output")); GST_ERROR_OBJECT (pb, "%s: '%s'", label_string, output); } } g_free (output); g_match_info_free (match_info); g_regex_unref (regex); } else { label_string = g_strdup_printf(_("Can't spawn %s"), argv[0]); GST_ERROR_OBJECT (pb, "%s %s %s (%s)", label_string, argv[1], envp[0], error->message); g_error_free (error); } priv->prints_remaining = remain; gtk_label_set_text (priv->win->status_printer, label_string); g_free (label_string); g_free (backend_environment); return FALSE; } static void photo_booth_snapshot_start (PhotoBooth *pb) { PhotoBoothPrivate *priv; guint pretrigger_delay = 1; guint snapshot_delay = 2; priv = photo_booth_get_instance_private (pb); photo_booth_change_state (pb, PB_STATE_COUNTDOWN); photo_booth_window_start_countdown (priv->win, priv->countdown); if (priv->countdown > 1) { pretrigger_delay = (priv->countdown*1000)-1500; snapshot_delay = (priv->countdown*1000)-100; } GST_DEBUG_OBJECT (pb, "started countdown of %d seconds, pretrigger in %d ms, snapshot in %d ms", priv->countdown, pretrigger_delay, snapshot_delay); g_timeout_add (pretrigger_delay, (GSourceFunc) photo_booth_snapshot_prepare, pb); g_timeout_add (snapshot_delay, (GSourceFunc) photo_booth_snapshot_trigger, pb); if (priv->countdown_audio_uri) { g_object_set (priv->audio_playbin, "uri", priv->countdown_audio_uri, NULL); gst_element_set_state (priv->audio_pipeline, GST_STATE_PLAYING); } } static gboolean photo_booth_snapshot_prepare (PhotoBooth *pb) { PhotoBoothPrivate *priv; GstPad *pad; gboolean ret; GST_DEBUG_OBJECT (pb, "photo_booth_snapshot_prepare!"); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "photo_booth_pre_snapshot"); photo_booth_change_state (pb, PB_STATE_TAKING_PHOTO); priv = photo_booth_get_instance_private (pb); photo_booth_window_set_spinner (priv->win, TRUE); SEND_COMMAND (pb, CONTROL_PRETRIGGER); return FALSE; } static gboolean photo_booth_snapshot_trigger (PhotoBooth *pb) { PhotoBoothPrivate *priv; GstPad *pad; gboolean ret; GST_DEBUG_OBJECT (pb, "photo_booth_snapshot_trigger"); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "photo_booth_snapshot_trigger"); priv = photo_booth_get_instance_private (pb); gst_element_set_state ((priv->audio_pipeline), GST_STATE_READY); SEND_COMMAND (pb, CONTROL_PHOTO); gst_element_set_state (pb->video_bin, GST_STATE_READY); GST_DEBUG_OBJECT (pb, "preparing for snapshot! halt video_bin..."); pad = gst_element_get_static_pad (pb->video_bin, "src"); priv->video_block_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, _gst_video_probecb, pb, NULL); gst_object_unref (pad); gst_element_unlink (pb->video_bin, pb->video_sink); if (priv->photo_block_id) { GST_DEBUG_OBJECT (pb, "preparing for snapshot! unblock photo_bin..."); pad = gst_element_get_static_pad (pb->photo_bin, "src"); gst_pad_remove_probe (pad, priv->photo_block_id); gst_object_unref (pad); } ret = gst_element_link (pb->photo_bin, pb->video_sink); gst_element_set_state (pb->photo_bin, GST_STATE_PLAYING); return FALSE; } extern int camera_auto_focus (Camera *list, GPContext *context, int onoff); static gboolean photo_booth_focus (CameraInfo *cam_info) { int gpret; CameraEventType evttype; void *evtdata; do { g_mutex_lock (&cam_info->mutex); gpret = gp_camera_wait_for_event (cam_info->camera, 10, &evttype, &evtdata, cam_info->context); g_mutex_unlock (&cam_info->mutex); GST_DEBUG ("gp_camera_wait_for_event gpret=%i", gpret); } while ((gpret == GP_OK) && (evttype != GP_EVENT_TIMEOUT)); g_mutex_lock (&cam_info->mutex); gpret = camera_auto_focus (cam_info->camera, cam_info->context, 1); g_mutex_unlock (&cam_info->mutex); if (gpret != GP_OK) { GST_WARNING ("gphoto error: %s\n", gp_result_as_string(gpret)); return FALSE; } do { GST_DEBUG ("gp_camera_wait_for_event gpret=%i", gpret); g_mutex_lock (&cam_info->mutex); gpret = gp_camera_wait_for_event (cam_info->camera, 10, &evttype, &evtdata, cam_info->context); g_mutex_unlock (&cam_info->mutex); } while ((gpret == GP_OK) && (evttype != GP_EVENT_TIMEOUT)); g_mutex_lock (&cam_info->mutex); gpret = camera_auto_focus (cam_info->camera, cam_info->context, 0); g_mutex_unlock (&cam_info->mutex); if (gpret != GP_OK) { GST_WARNING ("gphoto error: %s\n", gp_result_as_string(gpret)); } return TRUE; } static gboolean photo_booth_take_photo (CameraInfo *cam_info) { int gpret; CameraFile *file; CameraFilePath camera_file_path; g_mutex_lock (&cam_info->mutex); strcpy (camera_file_path.folder, "/"); strcpy (camera_file_path.name, "foo.jpg"); // snprintf (camera_file_path.name, 128, "pb_capt_%04d", cam_info->preview_capture_count); gpret = gp_camera_capture (cam_info->camera, GP_CAPTURE_IMAGE, &camera_file_path, cam_info->context); GST_DEBUG ("gp_camera_capture gpret=%i Pathname on the camera: %s/%s", gpret, camera_file_path.folder, camera_file_path.name); if (gpret < 0) goto fail; gpret = gp_file_new (&file); GST_DEBUG ("gp_file_new gpret=%i", gpret); gpret = gp_camera_file_get (cam_info->camera, camera_file_path.folder, camera_file_path.name, GP_FILE_TYPE_NORMAL, file, cam_info->context); GST_DEBUG ("gp_camera_file_get gpret=%i", gpret); if (gpret < 0) goto fail; gp_file_get_data_and_size (file, (const char**)&(cam_info->data), &(cam_info->size)); if (gpret < 0) goto fail; gpret = gp_camera_file_delete (cam_info->camera, camera_file_path.folder, camera_file_path.name, cam_info->context); GST_DEBUG ("gp_camera_file_delete gpret=%i", gpret); // gp_file_free(file); if (cam_info->size <= 0) goto fail; g_mutex_unlock (&cam_info->mutex); return TRUE; fail: g_mutex_unlock (&cam_info->mutex); return FALSE; } static gboolean photo_booth_snapshot_taken (PhotoBooth *pb) { PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb); GstElement *appsrc; GstBuffer *buffer; GstFlowReturn flowret; GstPad *pad; GST_DEBUG_OBJECT (pb, "photo_booth_snapshot_taken size=%lu", pb->cam_info->size); gtk_label_set_text (priv->win->status, _("Processing photo...")); appsrc = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-appsrc"); buffer = gst_buffer_new_wrapped (pb->cam_info->data, pb->cam_info->size); g_signal_emit_by_name (appsrc, "push-buffer", buffer, &flowret); if (flowret != GST_FLOW_OK) GST_ERROR_OBJECT (appsrc, "couldn't push %" GST_PTR_FORMAT " to appsrc", buffer); gst_object_unref (appsrc); SEND_COMMAND (pb, CONTROL_PAUSE); gst_element_set_state (pb->photo_bin, GST_STATE_PLAYING); pad = gst_element_get_static_pad (pb->photo_bin, "src"); priv->photo_block_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, photo_booth_catch_photo_buffer, pb, NULL); return FALSE; } static GstPadProbeReturn photo_booth_catch_photo_buffer (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) { PhotoBooth *pb = PHOTO_BOOTH (user_data); PhotoBoothPrivate *priv; GstPadProbeReturn ret = GST_PAD_PROBE_PASS; priv = photo_booth_get_instance_private (pb); GST_LOG_OBJECT (pb, "probe function in state %s... locking", photo_booth_state_get_name (priv->state)); g_mutex_lock (&priv->processing_mutex); switch (priv->state) { case PB_STATE_TAKING_PHOTO: { photo_booth_change_state (pb, PB_STATE_PROCESS_PHOTO); GST_DEBUG_OBJECT (pb, "first buffer caught -> display in sink, invoke processing"); gtk_widget_show (GTK_WIDGET (priv->win->button_yes)); gtk_label_set_text (priv->win->status, _("Print Photo? Touch background to cancel!")); g_main_context_invoke (NULL, (GSourceFunc) photo_booth_process_photo_plug_elements, pb); break; } case PB_STATE_PROCESS_PHOTO: { photo_booth_change_state (pb, PB_STATE_WAITING_FOR_ANSWER); GST_DEBUG_OBJECT (pb, "second buffer caught -> will be caught for printing. waiting for answer, hide spinner"); photo_booth_window_set_spinner (priv->win, FALSE); break; } case PB_STATE_WAITING_FOR_ANSWER: { GST_DEBUG_OBJECT (pb, "third buffer caught -> okay this is enough, remove processing elements and probe"); g_main_context_invoke (NULL, (GSourceFunc) photo_booth_process_photo_remove_elements, pb); ret = GST_PAD_PROBE_REMOVE; break; } default: break; } g_mutex_unlock (&priv->processing_mutex); GST_LOG_OBJECT (pb, "probe function in state %s... unlocked", photo_booth_state_get_name (priv->state)); return ret; } static gboolean photo_booth_process_photo_plug_elements (PhotoBooth *pb) { PhotoBoothPrivate *priv; GstElement *tee, *encoder, *filesink, *lcms, *appsink; priv = photo_booth_get_instance_private (pb); GST_DEBUG_OBJECT (pb, "plugging photo processing elements. locking..."); g_mutex_lock (&priv->processing_mutex); encoder = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-encoder"); tee = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-tee"); encoder = gst_element_factory_make ("jpegenc", "photo-encoder"); filesink = gst_element_factory_make ("filesink", "photo-filesink"); if (!encoder || !filesink) GST_ERROR_OBJECT (pb->photo_bin, "Failed to make photo encoder"); g_object_set (filesink, "location", "PHOTOBOOTH-PRINT.JPG", NULL); gst_bin_add_many (GST_BIN (pb->photo_bin), encoder, filesink, NULL); tee = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-tee"); if (!gst_element_link_many (tee, encoder, filesink, NULL)) GST_ERROR_OBJECT (pb->photo_bin, "couldn't link photobin filewrite elements!"); lcms = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "print-lcms"); if (!lcms) { lcms = gst_element_factory_make ("lcms", "print-lcms"); if (lcms) { g_object_set (G_OBJECT (lcms), "intent", 0, NULL); g_object_set (G_OBJECT (lcms), "lookup", 2, NULL); if (priv->cam_icc_profile) g_object_set (G_OBJECT (lcms), "input-profile", priv->cam_icc_profile, NULL); if (priv->print_icc_profile) g_object_set (G_OBJECT (lcms), "dest-profile", priv->print_icc_profile, NULL); g_object_set (G_OBJECT (lcms), "preserve-black", TRUE, NULL); gst_bin_add (GST_BIN (pb->photo_bin), lcms); } else GST_WARNING_OBJECT (pb->photo_bin, "no lcms pluing found, ICC color correction unavailable!"); } appsink = gst_element_factory_make ("appsink", "print-appsink"); if (!appsink ) GST_ERROR_OBJECT (pb->photo_bin, "Failed to make photo print processing element(s): %s", appsink?"":" appsink"); gst_bin_add (GST_BIN (pb->photo_bin), appsink); if (lcms) { if (!gst_element_link_many (tee, lcms, appsink, NULL)) GST_ERROR_OBJECT (pb->photo_bin, "couldn't link tee ! lcms ! appsink!"); } else { if (!gst_element_link (tee, appsink)) GST_ERROR_OBJECT (pb->photo_bin, "couldn't link tee ! appsink!"); } g_object_set (G_OBJECT (appsink), "emit-signals", TRUE, NULL); g_object_set (G_OBJECT (appsink), "enable-last-sample", FALSE, NULL); g_signal_connect (appsink, "new-sample", G_CALLBACK (photo_booth_catch_print_buffer), pb); gst_object_unref (tee); gst_element_set_state (pb->photo_bin, GST_STATE_PLAYING); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "photo_booth_process_photo_plug_elements"); g_mutex_unlock (&priv->processing_mutex); GST_DEBUG_OBJECT (pb, "plugged photo processing elements and unlocked."); return FALSE; } static GstFlowReturn photo_booth_catch_print_buffer (GstElement * appsink, gpointer user_data) { PhotoBooth *pb; PhotoBoothPrivate *priv; GstSample *sample; GstMapInfo map; GstPad *pad; pb = PHOTO_BOOTH (user_data); priv = photo_booth_get_instance_private (pb); g_mutex_lock (&priv->processing_mutex); sample = gst_app_sink_pull_sample (GST_APP_SINK (appsink)); priv->print_buffer = gst_buffer_ref( gst_sample_get_buffer (sample)); pad = gst_element_get_static_pad (appsink, "sink"); GstCaps *caps = gst_pad_get_current_caps (pad); GST_DEBUG_OBJECT (pb, "got photo for printer: %" GST_PTR_FORMAT ". caps = %" GST_PTR_FORMAT "", priv->print_buffer, caps); gst_caps_unref (caps); gst_sample_unref (sample); g_mutex_unlock (&priv->processing_mutex); return GST_FLOW_OK; } static gboolean photo_booth_process_photo_remove_elements (PhotoBooth *pb) { PhotoBoothPrivate *priv; GstElement *tee, *encoder, *filesink, *appsink, *lcms; priv = photo_booth_get_instance_private (pb); GST_DEBUG_OBJECT (pb, "remove output file encoder and writer elements and pause. locking..."); g_mutex_lock (&priv->processing_mutex); gst_element_set_state (pb->photo_bin, GST_STATE_READY); tee = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-tee"); encoder = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-encoder"); filesink = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "photo-filesink"); gst_element_unlink_many (tee, encoder, filesink, NULL); appsink = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "print-appsink"); lcms = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "print-lcms"); if (lcms) { gst_element_unlink_many (tee, lcms, appsink, NULL); gst_element_set_state (lcms, GST_STATE_READY); } else gst_element_unlink (tee, appsink); gst_bin_remove_many (GST_BIN (pb->photo_bin), encoder, filesink, appsink, NULL); gst_element_set_state (filesink, GST_STATE_NULL); gst_element_set_state (encoder, GST_STATE_NULL); gst_element_set_state (appsink, GST_STATE_NULL); gst_object_unref (tee); gst_object_unref (encoder); gst_object_unref (filesink); priv->photo_block_id = 0; g_mutex_unlock (&priv->processing_mutex); GST_DEBUG_OBJECT (pb, "removed output file encoder and writer elements and paused and unlocked."); return FALSE; } static void photo_booth_free_print_buffer (PhotoBooth *pb) { PhotoBoothPrivate *priv; GstElement *appsink; priv = photo_booth_get_instance_private (pb); GST_DEBUG_OBJECT (pb, "freeing buffer"); if (GST_IS_BUFFER (priv->print_buffer)) gst_buffer_unref (priv->print_buffer); appsink = gst_bin_get_by_name (GST_BIN (pb->photo_bin), "print-appsink"); if (GST_IS_ELEMENT (appsink)) gst_object_unref (appsink); } void photo_booth_button_yes_clicked (GtkButton *button, PhotoBoothWindow *win) { PhotoBooth *pb = PHOTO_BOOTH_FROM_WINDOW (win); PhotoBoothPrivate *priv; priv = photo_booth_get_instance_private (pb); GST_DEBUG_OBJECT (pb, "on_button_yes_clicked"); if (priv->state == PB_STATE_WAITING_FOR_ANSWER) { photo_booth_print (pb); } } #define ALWAYS_PRINT_DIALOG 1 static void photo_booth_print (PhotoBooth *pb) { PhotoBoothPrivate *priv; priv = photo_booth_get_instance_private (pb); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pb->pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "photo_booth_photo_print"); photo_booth_get_printer_status (pb); GST_INFO_OBJECT (pb, "PRINT! prints_remaining=%i", priv->prints_remaining); gtk_widget_hide (GTK_WIDGET (priv->win->button_yes)); #ifdef ALWAYS_PRINT_DIALOG if (1) #else if (priv->prints_remaining > 1) #endif { gtk_label_set_text (priv->win->status, _("Printing...")); photo_booth_change_state (pb, PB_STATE_PRINTING); PhotoBoothPrivate *priv; GtkPrintOperation *printop; GtkPrintOperationResult res; GtkPageSetup *page_setup; GtkPaperSize *paper_size; GError *print_error; GtkPrintOperationAction action; priv = photo_booth_get_instance_private (pb); printop = gtk_print_operation_new (); if (priv->printer_settings != NULL) { // action = GTK_PRINT_OPERATION_ACTION_PRINT; gtk_print_operation_set_print_settings (printop, priv->printer_settings); } else action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG; g_signal_connect (printop, "begin_print", G_CALLBACK (photo_booth_begin_print), NULL); g_signal_connect (printop, "draw_page", G_CALLBACK (photo_booth_draw_page), pb); g_signal_connect (printop, "done", G_CALLBACK (photo_booth_print_done), pb); page_setup = gtk_page_setup_new(); paper_size = gtk_paper_size_new_custom("custom", "custom", PT_PER_IN*4.0, PT_PER_IN*6.0, GTK_UNIT_POINTS); gtk_page_setup_set_orientation (page_setup, GTK_PAGE_ORIENTATION_LANDSCAPE); gtk_page_setup_set_paper_size (page_setup, paper_size); gtk_print_operation_set_default_page_setup (printop, page_setup); gtk_print_operation_set_use_full_page (printop, TRUE); gtk_print_operation_set_unit (printop, GTK_UNIT_POINTS); res = gtk_print_operation_run (printop, action, GTK_WINDOW (priv->win), &print_error); if (res == GTK_PRINT_OPERATION_RESULT_ERROR) { photo_booth_printing_error_dialog (priv->win, print_error); g_error_free (print_error); } else if (res == GTK_PRINT_OPERATION_RESULT_CANCEL) { gtk_label_set_text (priv->win->status, _("Printing cancelled")); GST_INFO_OBJECT (pb, "print cancelled"); } else if (res == GTK_PRINT_OPERATION_RESULT_APPLY) { if (priv->printer_settings != NULL) g_object_unref (priv->printer_settings); priv->printer_settings = g_object_ref (gtk_print_operation_get_print_settings (printop)); } g_object_unref (printop); } else if (priv->prints_remaining == -1) { gtk_label_set_text (priv->win->status, _("Can't print, no printer connected!")); } else gtk_label_set_text (priv->win->status, _("Can't print, out of paper!")); } static void photo_booth_begin_print (GtkPrintOperation *operation, GtkPrintContext *context, gpointer user_data) { gtk_print_operation_set_n_pages (operation, 1); } static void photo_booth_draw_page (GtkPrintOperation *operation, GtkPrintContext *context, int page_nr, gpointer user_data) { PhotoBooth *pb; PhotoBoothPrivate *priv; GstMapInfo map; pb = PHOTO_BOOTH (user_data); priv = photo_booth_get_instance_private (pb); if (!GST_IS_BUFFER (priv->print_buffer)) { GST_ERROR_OBJECT (context, "can't draw because we have no photo buffer!"); return; } GST_INFO_OBJECT (context, "draw_page no. %i . %" GST_PTR_FORMAT " size %dx%x, %i dpi, offsets (%.2f, %.2f)", page_nr, priv->print_buffer, priv->print_width, priv->print_height, priv->print_dpi, priv->print_x_offset, priv->print_y_offset); gst_buffer_map(priv->print_buffer, &map, GST_MAP_READ); guint8 *h = map.data; guint l = map.size; int stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, priv->print_width); cairo_surface_t *cairosurface = cairo_image_surface_create_for_data (map.data, CAIRO_FORMAT_RGB24, priv->print_width, priv->print_height, stride); cairo_t *cr = gtk_print_context_get_cairo_context (context); cairo_matrix_t m; cairo_get_matrix(cr, &m); float scale = (float) PT_PER_IN / (float) priv->print_dpi; cairo_scale(cr, scale, scale); cairo_set_source_surface(cr, cairosurface, priv->print_x_offset, priv->print_y_offset); cairo_paint(cr); cairo_set_matrix(cr, &m); gst_buffer_unmap (priv->print_buffer, &map); } static void photo_booth_printing_error_dialog (PhotoBoothWindow *window, GError *print_error) { GtkWidget *error_dialog; gchar *error_string; error_string = g_strdup_printf(_("Failed to print! Error message: %s"), print_error->message); GST_ERROR_OBJECT (window, error_string); error_dialog = gtk_message_dialog_new (GTK_WINDOW (window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, error_string); g_signal_connect (error_dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL); gtk_widget_show (error_dialog); g_free (error_string); } static void photo_booth_print_done (GtkPrintOperation *operation, GtkPrintOperationResult result, gpointer user_data) { GST_DEBUG_OBJECT (user_data, "print_done!"); PhotoBooth *pb; PhotoBoothPrivate *priv; GstMapInfo map; pb = PHOTO_BOOTH (user_data); priv = photo_booth_get_instance_private (pb); GError *print_error; if (result == GTK_PRINT_OPERATION_RESULT_ERROR) { gtk_print_operation_get_error (operation, &print_error); photo_booth_printing_error_dialog (priv->win, print_error); g_error_free (print_error); } g_timeout_add_seconds (15, (GSourceFunc) photo_booth_get_printer_status, pb); photo_booth_preview (pb); return; } const gchar* photo_booth_state_get_name (PhotoboothState state) { switch (state) { case PB_STATE_NONE: return "PB_STATE_NONE";break; case PB_STATE_PREVIEW: return "PB_STATE_PREVIEW";break; case PB_STATE_PREVIEW_COOLDOWN: return "PB_STATE_PREVIEW_COOLDOWN";break; case PB_STATE_COUNTDOWN: return "PB_STATE_COUNTDOWN";break; case PB_STATE_TAKING_PHOTO: return "PB_STATE_TAKING_PHOTO";break; case PB_STATE_PROCESS_PHOTO: return "PB_STATE_PROCESS_PHOTO";break; case PB_STATE_WAITING_FOR_ANSWER: return "PB_STATE_WAITING_FOR_ANSWER";break; case PB_STATE_PRINTING: return "PB_STATE_PRINTING";break; case PB_STATE_SCREENSAVER: return "PB_STATE_SCREENSAVER";break; default: return "STATE UNKOWN!";break; } return ""; } PhotoBooth *photo_booth_new (void) { return g_object_new (PHOTO_BOOTH_TYPE, "application-id", "org.schaffenburg.photobooth", "flags", G_APPLICATION_HANDLES_OPEN, NULL); } int main (int argc, char *argv[]) { PhotoBooth *pb; int ret; gst_init (0, NULL); pb = photo_booth_new (); if (argc == 2) photo_booth_load_settings (pb, argv[1]); else photo_booth_load_settings (pb, DEFAULT_CONFIG); g_unix_signal_add (SIGINT, (GSourceFunc) photo_booth_quit_signal, pb); ret = g_application_run (G_APPLICATION (pb), argc, argv); g_object_unref (pb); return ret; }