2069 lines
72 KiB
C
2069 lines
72 KiB
C
/*
|
|
* photobooth.c
|
|
* Copyright 2016 Andreas Frisch <fraxinas@opendreambox.org>
|
|
*
|
|
* 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 <poll.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <gst/video/videooverlay.h>
|
|
#include <gst/video/gstvideosink.h>
|
|
#include <gst/app/app.h>
|
|
#include <curl/curl.h>
|
|
// #ifdef HAVE_LIBCANBERRA
|
|
#include <canberra-gtk.h>
|
|
// #endif
|
|
#include "photobooth.h"
|
|
#include "photoboothwin.h"
|
|
#include "photoboothled.h"
|
|
|
|
#define G_SETTINGS_ENABLE_BACKEND
|
|
#include <gio/gsettingsbackend.h>
|
|
|
|
#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 *save_path_template;
|
|
guint photos_taken, photos_printed;
|
|
guint save_filename_count;
|
|
|
|
gchar *printer_backend;
|
|
gint print_copies_min, print_copies_default, print_copies_max, print_copies;
|
|
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 *error_sound;
|
|
gchar *ack_sound;
|
|
|
|
gchar *screensaver_uri;
|
|
gint screensaver_timeout;
|
|
guint screensaver_timeout_id;
|
|
GstClockTime last_play_pos;
|
|
|
|
gchar *facebook_put_uri;
|
|
gint facebook_put_timeout;
|
|
GThread *upload_thread;
|
|
GMutex upload_mutex;
|
|
|
|
PhotoBoothLed *led;
|
|
};
|
|
|
|
#define MOVIEPIPE "moviepipe.mjpg"
|
|
#define DEFAULT_CONFIG "default.ini"
|
|
#define PREVIEW_FPS 24
|
|
#define DEFAULT_COUNTDOWN 5
|
|
#define DEFAULT_SAVE_PATH_TEMPLATE "./snapshot%03d.jpg"
|
|
#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
|
|
|
|
typedef enum { NONE, ACK_SOUND, ERROR_SOUND } sound_t;
|
|
|
|
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_cancel_clicked (GtkButton *button, PhotoBoothWindow *win);
|
|
void photo_booth_cancel (PhotoBooth *pb);
|
|
|
|
/* 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);
|
|
void photo_booth_button_print_clicked (GtkButton *button, PhotoBoothWindow *win);
|
|
static void photo_booth_print (PhotoBooth *pb);
|
|
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);
|
|
|
|
/* upload functions */
|
|
void photo_booth_button_upload_clicked (GtkButton *button, PhotoBoothWindow *win);
|
|
static void photo_booth_facebook_post_thread_func (PhotoBooth *pb);
|
|
static gboolean photo_booth_upload_timedout (PhotoBooth *pb);
|
|
|
|
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_copies_min = priv->print_copies_max = priv->print_copies_default = 1;
|
|
priv->print_copies = 1;
|
|
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->ack_sound = NULL;
|
|
priv->error_sound = NULL;
|
|
priv->screensaver_uri = NULL;
|
|
priv->screensaver_timeout = DEFAULT_SCREENSAVER_TIMEOUT;
|
|
priv->screensaver_timeout_id = 0;
|
|
priv->last_play_pos = GST_CLOCK_TIME_NONE;
|
|
priv->save_path_template = g_strdup (DEFAULT_SAVE_PATH_TEMPLATE);
|
|
priv->photos_taken = priv->photos_printed = 0;
|
|
priv->save_filename_count = 0;
|
|
priv->facebook_put_timeout = 0;
|
|
priv->facebook_put_uri = NULL;
|
|
priv->upload_thread = NULL;
|
|
|
|
priv->led = photo_booth_led_new ();
|
|
|
|
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);
|
|
g_mutex_init (&priv->upload_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);
|
|
}
|
|
if (priv->upload_thread)
|
|
g_thread_join (priv->upload_thread);
|
|
g_object_unref (priv->led);
|
|
}
|
|
|
|
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->ack_sound);
|
|
g_free (priv->error_sound);
|
|
g_free (priv->screensaver_uri);
|
|
g_free (priv->print_icc_profile);
|
|
g_free (priv->cam_icc_profile);
|
|
g_free (priv->overlay_image);
|
|
g_free (priv->save_path_template);
|
|
g_hash_table_destroy (G_strings_table);
|
|
G_strings_table = NULL;
|
|
g_mutex_clear (&priv->processing_mutex);
|
|
g_mutex_clear (&priv->upload_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_TRACE ("key %u/%u:\t'%s' => '%s'", keyidx, num_keys-1, key, value);
|
|
g_free (val);
|
|
}
|
|
if (error)
|
|
{
|
|
GST_INFO ( "can't read string: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
g_strfreev (keys);
|
|
}
|
|
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);
|
|
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 (!g_path_is_absolute (screensaverfile))
|
|
{
|
|
gchar *cdir = g_get_current_dir ();
|
|
screensaverabsfilename = g_strdup_printf ("%s/%s", cdir, screensaverfile);
|
|
g_free (cdir);
|
|
}
|
|
else
|
|
screensaverabsfilename = g_strdup (screensaverfile);
|
|
priv->screensaver_uri = g_filename_to_uri (screensaverabsfilename, NULL, NULL);
|
|
g_free (screensaverfile);
|
|
g_free (screensaverabsfilename);
|
|
}
|
|
gchar *save_path_template = g_key_file_get_string (gkf, "general", "save_path_template", NULL);
|
|
if (save_path_template)
|
|
{
|
|
gchar *cdir;
|
|
if (!g_path_is_absolute (save_path_template))
|
|
{
|
|
cdir = g_get_current_dir ();
|
|
priv->save_path_template = g_strdup_printf ("%s/%s", cdir, save_path_template);
|
|
g_free (cdir);
|
|
}
|
|
else
|
|
{
|
|
cdir = g_path_get_dirname (save_path_template);
|
|
priv->save_path_template = g_strdup (save_path_template);
|
|
}
|
|
g_free (save_path_template);
|
|
}
|
|
}
|
|
if (g_key_file_has_group (gkf, "sounds"))
|
|
{
|
|
gchar *countdownaudiofile = g_key_file_get_string (gkf, "sounds", "countdown_audio_file", NULL);
|
|
if (countdownaudiofile)
|
|
{
|
|
gchar *audioabsfilename;
|
|
if (!g_path_is_absolute (countdownaudiofile))
|
|
{
|
|
gchar *cdir = g_get_current_dir ();
|
|
audioabsfilename = g_strdup_printf ("%s/%s", cdir, countdownaudiofile);
|
|
g_free (cdir);
|
|
}
|
|
else
|
|
audioabsfilename = g_strdup (countdownaudiofile);
|
|
priv->countdown_audio_uri = g_filename_to_uri (audioabsfilename, NULL, NULL);
|
|
g_free (countdownaudiofile);
|
|
g_free (audioabsfilename);
|
|
}
|
|
priv->ack_sound = g_key_file_get_string (gkf, "sounds", "ack_sound", NULL);
|
|
priv->error_sound = g_key_file_get_string (gkf, "sounds", "error_sound", NULL);
|
|
}
|
|
if (g_key_file_has_group (gkf, "printer"))
|
|
{
|
|
priv->printer_backend = g_key_file_get_string (gkf, "printer", "backend", NULL);
|
|
priv->print_copies_min = g_key_file_get_integer (gkf, "printer", "copies_min", NULL);
|
|
priv->print_copies_max = g_key_file_get_integer (gkf, "printer", "copies_max", NULL);
|
|
priv->print_copies_default = g_key_file_get_integer (gkf, "printer", "copies_default", 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 (g_key_file_has_group (gkf, "upload"))
|
|
{
|
|
priv->facebook_put_uri = g_key_file_get_string (gkf, "upload", "facebook_put_uri", NULL);
|
|
priv->facebook_put_timeout = g_key_file_get_integer (gkf, "upload", "facebook_put_timeout", NULL);
|
|
}
|
|
}
|
|
|
|
gchar *save_path_basename = g_path_get_basename (priv->save_path_template);
|
|
gchar *pos = g_strstr_len ((const gchar*) save_path_basename, strlen (save_path_basename), "%");
|
|
if (pos)
|
|
{
|
|
gchar *filenameprefix = g_strndup (save_path_basename, pos-save_path_basename);
|
|
GDir *save_dir;
|
|
GError *error = NULL;
|
|
gchar *cdir = g_path_get_dirname (priv->save_path_template);
|
|
save_dir = g_dir_open ((const gchar*)cdir, 0, &error);
|
|
if (error) {
|
|
GST_WARNING ("couldn't open save directory '%s': %s", priv->save_path_template, error->message);
|
|
}
|
|
else if (save_dir)
|
|
{
|
|
const gchar *filename;
|
|
GMatchInfo *match_info;
|
|
GRegex *regex;
|
|
gchar *pattern = g_strdup_printf("(?<filename>%s)(?<number>\\d+)", filenameprefix);
|
|
GST_TRACE ("save_path_basename regex pattern = '%s'", pattern);
|
|
regex = g_regex_new (pattern, 0, 0, &error);
|
|
if (error) {
|
|
g_critical ("%s\n", error->message);
|
|
}
|
|
while ((filename = g_dir_read_name (save_dir)))
|
|
{
|
|
if (g_regex_match (regex, filename, 0, &match_info))
|
|
{
|
|
gint count = atoi(g_match_info_fetch_named (match_info, "number"));
|
|
gchar *name = g_match_info_fetch_named (match_info, "filename");
|
|
if (count > priv->save_filename_count)
|
|
priv->save_filename_count = count;
|
|
GST_TRACE ("save_path_template found matching file %s (prefix %s, count %d, highest %i)", filename, name, count, priv->save_filename_count);
|
|
g_free (name);
|
|
}
|
|
else
|
|
GST_TRACE ("save_path_template unmatched file %s", filename);
|
|
}
|
|
g_dir_close (save_dir);
|
|
}
|
|
}
|
|
g_free (save_path_basename);
|
|
|
|
g_key_file_free (gkf);
|
|
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_ERROR_IO_USB_CLAIM)
|
|
{
|
|
g_usleep (G_USEC_PER_SEC);
|
|
}
|
|
if (retval != GP_OK) {
|
|
photo_booth_cam_close (&(*cam_info));
|
|
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);
|
|
gp_camera_free ((*cam_info)->camera);
|
|
gp_context_unref ((*cam_info)->context);
|
|
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)
|
|
{
|
|
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));
|
|
}
|
|
fcntl (fd, F_SETFL, flags ^ O_NONBLOCK);
|
|
}
|
|
|
|
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_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..."));
|
|
photo_booth_led_flash (priv->led);
|
|
ret = photo_booth_take_photo (pb->cam_info);
|
|
photo_booth_led_black (priv->led);
|
|
if (ret && pb->cam_info->size)
|
|
{
|
|
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_INIT;
|
|
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_hide_cursor (priv->win);
|
|
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;
|
|
if (priv->state != PB_STATE_UPLOADING)
|
|
{
|
|
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);
|
|
if (priv->state == PB_STATE_UPLOADING)
|
|
{
|
|
GST_DEBUG_OBJECT (pb, "still uploading, wait another bit");
|
|
return TRUE;
|
|
}
|
|
|
|
photo_booth_change_state (pb, PB_STATE_PREVIEW);
|
|
gtk_label_set_text (priv->win->status, _("Touch screen to take a photo!"));
|
|
photo_booth_window_hide_cursor (priv->win);
|
|
|
|
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);
|
|
}
|
|
if (priv->sink_block_id)
|
|
{
|
|
pad = gst_element_get_static_pad (pb->video_sink, "sink");
|
|
GST_DEBUG_OBJECT (pad, "showing screensaver! unblock video_sink...");
|
|
gst_pad_remove_probe (pad, priv->sink_block_id);
|
|
gst_object_unref (pad);
|
|
gst_element_set_state (pb->video_sink, GST_STATE_PLAYING);
|
|
}
|
|
|
|
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);
|
|
photo_booth_change_state (pb, PB_STATE_NONE);
|
|
|
|
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);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void _play_event_sound (PhotoBoothPrivate *priv, sound_t sound)
|
|
{
|
|
gchar *soundfile = NULL;
|
|
switch (sound) {
|
|
case ACK_SOUND:
|
|
soundfile = priv->ack_sound;
|
|
break;
|
|
case ERROR_SOUND:
|
|
soundfile = priv->error_sound;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (soundfile)
|
|
ca_context_play (ca_gtk_context_get(), 0, CA_PROP_MEDIA_FILENAME, soundfile, NULL);
|
|
}
|
|
|
|
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!");
|
|
_play_event_sound (priv, ERROR_SOUND);
|
|
break;
|
|
}
|
|
case PB_STATE_ASK_PRINT:
|
|
case PB_STATE_ASK_UPLOAD:
|
|
{
|
|
// photo_booth_button_cancel_clicked (pb);
|
|
_play_event_sound (priv, ERROR_SOUND);
|
|
break;
|
|
}
|
|
case PB_STATE_SCREENSAVER:
|
|
{
|
|
photo_booth_screensaver_stop (pb);
|
|
_play_event_sound (priv, ACK_SOUND);
|
|
break;
|
|
}
|
|
case PB_STATE_NONE:
|
|
{
|
|
photo_booth_screensaver (pb);
|
|
_play_event_sound (priv, ACK_SOUND);
|
|
break;
|
|
}
|
|
default:
|
|
_play_event_sound (priv, ERROR_SOUND);
|
|
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.*?: (?<code>\\d+) \\((?<size>.*?)\\)\nINFO: Media remaining\\s.*?: (?<remain>\\d{3})/(?<total>\\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);
|
|
}
|
|
photo_booth_led_countdown (priv->led, priv->countdown);
|
|
}
|
|
|
|
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;
|
|
|
|
priv->photos_taken++;
|
|
GST_DEBUG_OBJECT (pb, "photo_booth_snapshot_taken size=%lu photos_taken=%i", pb->cam_info->size, priv->photos_taken);
|
|
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_print));
|
|
gtk_widget_show (GTK_WIDGET (priv->win->button_cancel));
|
|
photo_booth_window_show_cursor (priv->win);
|
|
gtk_label_set_text (priv->win->status, _("Print Photo?"));
|
|
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_ASK_PRINT);
|
|
GST_DEBUG_OBJECT (pb, "second buffer caught -> will be caught for printing. waiting for answer, hide spinner");
|
|
if (priv->print_copies_min != priv->print_copies_max)
|
|
photo_booth_window_set_copies_show (priv->win, priv->print_copies_min, priv->print_copies_max, priv->print_copies_default);
|
|
photo_booth_window_set_spinner (priv->win, FALSE);
|
|
break;
|
|
}
|
|
case PB_STATE_ASK_PRINT:
|
|
{
|
|
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");
|
|
priv->save_filename_count++;
|
|
gchar *filename = g_strdup_printf (priv->save_path_template, priv->save_filename_count);
|
|
GST_INFO_OBJECT (pb->photo_bin, "saving photo to '%s'", filename);
|
|
g_object_set (filesink, "location", filename, NULL);
|
|
g_free (filename);
|
|
|
|
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_print_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, "photo_booth_button_print_clicked");
|
|
if (priv->state == PB_STATE_ASK_PRINT)
|
|
{
|
|
_play_event_sound (priv, ACK_SOUND);
|
|
photo_booth_print (pb);
|
|
}
|
|
}
|
|
|
|
void photo_booth_button_upload_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, "photo_booth_button_upload_clicked");
|
|
if (priv->state == PB_STATE_ASK_UPLOAD)
|
|
{
|
|
_play_event_sound (priv, ACK_SOUND);
|
|
photo_booth_window_set_spinner (priv->win, TRUE);
|
|
gtk_label_set_text (priv->win->status, _("Uploading..."));
|
|
priv->upload_thread = g_thread_try_new ("upload", (GThreadFunc) photo_booth_facebook_post_thread_func, pb, NULL);
|
|
photo_booth_cancel (pb);
|
|
}
|
|
}
|
|
|
|
void photo_booth_button_cancel_clicked (GtkButton *button, PhotoBoothWindow *win)
|
|
{
|
|
PhotoBooth *pb = PHOTO_BOOTH_FROM_WINDOW (win);
|
|
GST_DEBUG_OBJECT (button, "photo_booth_button_cancel_clicked");
|
|
_play_event_sound (photo_booth_get_instance_private (pb), ACK_SOUND);
|
|
photo_booth_cancel (pb);
|
|
}
|
|
|
|
void photo_booth_cancel (PhotoBooth *pb)
|
|
{
|
|
PhotoBoothPrivate *priv;
|
|
priv = photo_booth_get_instance_private (pb);
|
|
switch (priv->state) {
|
|
case PB_STATE_ASK_PRINT:
|
|
{
|
|
gtk_widget_hide (GTK_WIDGET (priv->win->button_print));
|
|
photo_booth_window_get_copies_hide (priv->win);
|
|
break;
|
|
}
|
|
case PB_STATE_ASK_UPLOAD:
|
|
gtk_widget_hide (GTK_WIDGET (priv->win->button_upload));
|
|
break;
|
|
default: return;
|
|
}
|
|
gtk_widget_hide (GTK_WIDGET (priv->win->button_cancel));
|
|
SEND_COMMAND (pb, CONTROL_UNPAUSE);
|
|
}
|
|
|
|
void photo_booth_copies_value_changed (GtkRange *range, PhotoBoothWindow *win)
|
|
{
|
|
PhotoBooth *pb = PHOTO_BOOTH_FROM_WINDOW (win);
|
|
PhotoBoothPrivate *priv;
|
|
priv = photo_booth_get_instance_private (pb);
|
|
priv->print_copies = (int) gtk_range_get_value (range);
|
|
GST_DEBUG_OBJECT (range, "\n\nphoto_booth_copies_value_changed value=%d", priv->print_copies);
|
|
}
|
|
|
|
#define ALWAYS_PRINT_DIALOG 1
|
|
|
|
static void photo_booth_print (PhotoBooth *pb)
|
|
{
|
|
PhotoBoothPrivate *priv;
|
|
gint num_copies = 0;
|
|
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_print));
|
|
num_copies = photo_booth_window_get_copies_hide (priv->win);
|
|
|
|
#ifdef ALWAYS_PRINT_DIALOG
|
|
if (1)
|
|
#else
|
|
if (priv->prints_remaining > num_copies)
|
|
#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;
|
|
else
|
|
{
|
|
priv->printer_settings = gtk_print_settings_new ();
|
|
action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
|
|
}
|
|
|
|
gtk_print_operation_set_print_settings (printop, priv->printer_settings);
|
|
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);
|
|
gtk_print_settings_set_n_copies (priv->printer_settings, num_copies);
|
|
|
|
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_DEBUG_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)
|
|
{
|
|
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);
|
|
}
|
|
else if (result == GTK_PRINT_OPERATION_RESULT_APPLY)
|
|
{
|
|
gint copies = gtk_print_settings_get_n_copies (priv->printer_settings);
|
|
priv->photos_printed += copies;
|
|
GST_INFO_OBJECT (user_data, "print_done photos_printed copies=%i total=%i", copies, priv->photos_printed);
|
|
photo_booth_led_printer (priv->led, copies);
|
|
}
|
|
else
|
|
GST_INFO_OBJECT (user_data, "print_done photos_printed unhandled result %i", result);
|
|
|
|
g_timeout_add_seconds (15, (GSourceFunc) photo_booth_get_printer_status, pb);
|
|
|
|
if (priv->facebook_put_uri)
|
|
{
|
|
gtk_widget_show (GTK_WIDGET (priv->win->button_upload));
|
|
gtk_label_set_text (priv->win->status, _("Upload photo?"));
|
|
g_timeout_add_seconds (priv->facebook_put_timeout, (GSourceFunc) photo_booth_upload_timedout, pb);
|
|
photo_booth_change_state (pb, PB_STATE_ASK_UPLOAD);
|
|
}
|
|
else
|
|
photo_booth_preview (pb);
|
|
|
|
return;
|
|
}
|
|
|
|
size_t _curl_write_func (void *ptr, size_t size, size_t nmemb, void *buf)
|
|
{
|
|
int i;
|
|
for (i = 0; i < size*nmemb; i++)
|
|
g_string_append_c((GString *)buf, ((gchar *)ptr)[i]);
|
|
return i;
|
|
}
|
|
|
|
void photo_booth_facebook_post_thread_func (PhotoBooth* pb)
|
|
{
|
|
PhotoBoothPrivate *priv;
|
|
CURLcode res;
|
|
CURL *curl;
|
|
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_facebook_post");
|
|
g_mutex_lock (&priv->upload_mutex);
|
|
curl = curl_easy_init();
|
|
if (curl)
|
|
{
|
|
photo_booth_change_state (pb, PB_STATE_UPLOADING);
|
|
struct curl_httppost* post = NULL;
|
|
struct curl_httppost* last = NULL;
|
|
GString *buf = g_string_new("");
|
|
gchar *filename = g_strdup_printf (priv->save_path_template, priv->save_filename_count);
|
|
GST_INFO_OBJECT (pb, "photo_booth_facebook_post '%s' to '%s'...", filename, priv->facebook_put_uri);
|
|
curl_formadd (&post, &last, CURLFORM_COPYNAME, "image", CURLFORM_FILE, filename, CURLFORM_CONTENTTYPE, "image/jpeg", CURLFORM_END);
|
|
curl_easy_setopt (curl, CURLOPT_USERAGENT, "Schaffenburg Photobooth");
|
|
curl_easy_setopt (curl, CURLOPT_URL, priv->facebook_put_uri);
|
|
curl_easy_setopt (curl, CURLOPT_POST, 1L);
|
|
curl_easy_setopt (curl, CURLOPT_HTTPPOST, post);
|
|
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, _curl_write_func);
|
|
curl_easy_setopt (curl, CURLOPT_WRITEDATA, buf);
|
|
res = curl_easy_perform (curl);
|
|
if (res != CURLE_OK)
|
|
{
|
|
GST_WARNING ("curl_easy_perform() failed %s", curl_easy_strerror(res));
|
|
}
|
|
curl_easy_cleanup (curl);
|
|
curl_formfree (post);
|
|
g_free (filename);
|
|
GST_DEBUG ("curl_easy_perform() finished. response='%s'", buf->str);
|
|
g_string_free (buf, TRUE);
|
|
}
|
|
g_mutex_unlock (&priv->upload_mutex);
|
|
photo_booth_change_state (pb, PB_STATE_PREVIEW_COOLDOWN);
|
|
photo_booth_window_set_spinner (priv->win, FALSE);
|
|
return;
|
|
}
|
|
|
|
static gboolean photo_booth_upload_timedout (PhotoBooth *pb)
|
|
{
|
|
PhotoBoothPrivate *priv = photo_booth_get_instance_private (pb);
|
|
photo_booth_cancel (pb);
|
|
return FALSE;
|
|
}
|
|
|
|
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_ASK_PRINT: return "PB_STATE_ASK_PRINT";break;
|
|
case PB_STATE_PRINTING: return "PB_STATE_PRINTING";break;
|
|
case PB_STATE_ASK_UPLOAD: return "PB_STATE_ASK_UPLOAD";break;
|
|
case PB_STATE_UPLOADING: return "PB_STATE_UPLOADING";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;
|
|
}
|