Add -Wold-style-definition
[platform/upstream/gstreamer.git] / tests / icles / metadata_editor.c
1 /*
2  * GStreamer
3  * Copyright 2007 Edgard Lima <edgard.lima@indt.org.br>
4  * 
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Alternatively, the contents of this file may be used under the
24  * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
25  * which case the following provisions apply instead of the ones
26  * mentioned above:
27  *
28  * This library is free software; you can redistribute it and/or
29  * modify it under the terms of the GNU Library General Public
30  * License as published by the Free Software Foundation; either
31  * version 2 of the License, or (at your option) any later version.
32  *
33  * This library is distributed in the hope that it will be useful,
34  * but WITHOUT ANY WARRANTY; without even the implied warranty of
35  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
36  * Library General Public License for more details.
37  *
38  * You should have received a copy of the GNU Library General Public
39  * License along with this library; if not, write to the
40  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
41  * Boston, MA 02111-1307, USA.
42  */
43
44 #ifdef HAVE_CONFIG_H
45 #include <config.h>
46 #endif
47
48 #include "metadata_editor.h"
49
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54
55 #include <gst/gst.h>
56
57 /*
58  * Global constants
59  */
60
61 enum
62 {
63   COL_TAG = 0,
64   COL_VALUE,
65   NUM_COLS
66 };
67 /* *INDENT-OFF* */
68 typedef enum _AppOptions {
69   APP_OPT_DEMUX_EXIF = (1 << 0),
70   APP_OPT_DEMUX_IPTC = (1 << 1),
71   APP_OPT_DEMUX_XMP  = (1 << 2),
72   APP_OPT_MUX_EXIF   = (1 << 3),
73   APP_OPT_MUX_IPTC   = (1 << 4),
74   APP_OPT_MUX_XMP    = (1 << 5),
75   APP_OPT_ALL        = (1 << 6) - 1,
76 } AppOptions;
77
78 #define ENC_ERROR   (-1)
79 #define ENC_DONE    (0)
80 #define ENC_UNKNOWN (1)
81
82 /* *INDENT-OFF* */
83
84 /*
85  * functions prototypes
86  */
87
88 /* gstreamer related functions */
89
90 static void me_gst_cleanup_elements ();
91 static int me_gst_setup_view_pipeline (const gchar * filename);
92 static int
93 me_gst_setup_capture_pipeline (const gchar * src_file, const gchar * dest_file,
94     gint * encode_status, gboolean use_v4l2);
95 static int
96 me_gst_setup_encode_pipeline (const gchar * src_file, const gchar * dest_file,
97     gint * encode_status);
98
99 /* ui related functions */
100
101 static void ui_refresh (void);
102 static void process_file(void);
103
104 /*
105  * Global Vars 
106  */
107
108 GstElement *gst_source = NULL;
109 GstElement *gst_metadata_demux = NULL;
110 GstElement *gst_metadata_mux = NULL;
111 GstElement *gst_image_dec = NULL;
112 GstElement *gst_image_enc = NULL;
113 GstElement *gst_video_scale = NULL;
114 GstElement *gst_video_convert = NULL;
115 GstElement *gst_video_sink = NULL;
116 GstElement *gst_file_sink = NULL;
117 GstElement *gst_pipeline = NULL;
118
119 GdkPixbuf *last_pixbuf = NULL;  /* image as pixbuf at original size */
120 GdkPixbuf *draw_pixbuf = NULL;  /* pixbuf resized for drawing       */
121
122 AppOptions app_options = APP_OPT_ALL;
123
124 GstTagList *tag_list = NULL;
125
126 GtkBuilder *builder = NULL;
127 GtkWidget *ui_main_window = NULL;
128 GtkWidget *ui_drawing = NULL;
129 GtkWidget *ui_tree = NULL;
130
131 GtkEntry *ui_entry_insert_tag = NULL;
132 GtkEntry *ui_entry_insert_value = NULL;
133
134 GtkToggleButton *ui_chk_bnt_capture_v4l2 = NULL;
135 GtkToggleButton *ui_chk_bnt_capture_test = NULL;
136
137 GString *filename = NULL;
138
139 /*
140  * Helper functions
141  */
142
143 static void
144 dump_tag_buffer(const char *tag, guint8 * buf, guint32 size)
145 {
146   guint32 i;
147   printf("\nDumping %s (size = %u)\n\n", tag, size);
148
149   for(i=0; i<size; ++i) {
150
151     if (i % 16 == 0)
152       printf("%04x:%04x | ", i >> 16, i & 0xFFFF);
153
154     printf("%02x", buf[i]);
155
156     if (i % 16 != 15)
157       printf(" ");
158     else
159       printf("\n");
160
161   }
162
163   printf("\n\n");
164
165 }
166
167 static void
168 insert_tag_on_tree (const GstTagList * list, const gchar * tag,
169     gpointer user_data)
170 {
171   gchar *str = NULL;
172   GtkTreeView *tree_view = NULL;
173   GtkTreeStore *tree_store = NULL;
174   GtkTreeIter iter;
175
176   tree_view = GTK_TREE_VIEW (user_data);
177
178   if (gst_tag_get_type (tag) == G_TYPE_STRING) {
179     if (!gst_tag_list_get_string_index (list, tag, 0, &str))
180       g_assert_not_reached ();
181   } else if ( gst_tag_get_type (tag) == GST_TYPE_BUFFER ) {
182     const GValue *val = NULL;
183     GstBuffer *buf;
184     val = gst_tag_list_get_value_index (list, tag, 0);
185     buf = gst_value_get_buffer (val);
186     dump_tag_buffer(tag, GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf));
187     str = g_strdup("It has been printed to stdout");
188   } else {
189     str = g_strdup_value_contents (gst_tag_list_get_value_index (list, tag, 0));
190   }
191
192   tree_store = GTK_TREE_STORE (gtk_tree_view_get_model (tree_view));
193   gtk_tree_store_append (tree_store, &iter, NULL);
194   gtk_tree_store_set (tree_store, &iter, COL_TAG, tag, COL_VALUE, str, -1);
195
196   if (str)
197     g_free (str);
198
199 }
200
201 static gboolean
202 change_tag_list (GstTagList ** list, const gchar * tag, const gchar * value)
203 {
204   GType type;
205   gboolean ret = FALSE;
206
207   if (list == NULL || tag == NULL || value == NULL)
208     goto done;
209
210   if (!gst_tag_exists (tag)) {
211     fprintf (stderr, "%s is not a GStreamer registered tag\n", tag);
212     goto done;
213   }
214
215   if (*list == NULL)
216     *list = gst_tag_list_new ();
217
218   type = gst_tag_get_type (tag);
219
220   if (type == GST_TYPE_FRACTION) {
221     /* FIXME: Ask GStreamer guys to add GST_FRACTION support to TAGS */
222     /* Even better: ask GLib guys to add this type */
223     gint n, d;
224
225     sscanf (value, "%d/%d", &n, &d);
226     gst_tag_list_add (*list, GST_TAG_MERGE_REPLACE, tag, n, d, NULL);
227     ret = TRUE;
228   } else {
229     switch (type) {
230       case G_TYPE_STRING:
231         gst_tag_list_add (*list, GST_TAG_MERGE_REPLACE, tag, value, NULL);
232         ret = TRUE;
233         break;
234       case G_TYPE_FLOAT:
235       {
236         gfloat fv = (gfloat) g_strtod (value, NULL);
237
238         gst_tag_list_add (*list, GST_TAG_MERGE_REPLACE, tag, fv, NULL);
239         ret = TRUE;
240       }
241         break;
242       case G_TYPE_INT:
243         /* fall through */
244       case G_TYPE_UINT:
245       {
246         gint iv = atoi (value);
247
248         gst_tag_list_add (*list, GST_TAG_MERGE_REPLACE, tag, iv, NULL);
249         ret = TRUE;
250       }
251         break;
252       default:
253         g_printerr ("Tags of type '%s' are not supported yet for editing"
254                     " by this application.\n",
255             g_type_name (type));
256         break;
257     }
258   }
259
260 done:
261
262   return ret;
263
264 }
265
266 static void
267 update_draw_pixbuf (guint max_width, guint max_height)
268 {
269   gdouble wratio, hratio;
270   gint w = 0, h = 0;
271
272   if (last_pixbuf) {
273     w = gdk_pixbuf_get_width (last_pixbuf);
274     h = gdk_pixbuf_get_height (last_pixbuf);
275   }
276
277   g_print ("Allocation: %dx%d, pixbuf: %dx%d\n", max_width, max_height, w, h);
278
279   if (last_pixbuf == NULL)
280     return;
281
282   g_return_if_fail (max_width > 0 && max_height > 0);
283   wratio = w * 1.0 / max_width * 1.0;
284   hratio = h * 1.0 / max_height;
285   g_print ("ratios = %.2f / %.2f\n", wratio, hratio);
286
287   if (hratio > wratio) {
288     w = (gint) (w * 1.0 / hratio);
289     h = (gint) (h * 1.0 / hratio);
290   } else {
291     w = (gint) (w * 1.0 / wratio);
292     h = (gint) (h * 1.0 / wratio);
293   }
294
295   if (draw_pixbuf != NULL && gdk_pixbuf_get_width (draw_pixbuf) == w &&
296       gdk_pixbuf_get_height (last_pixbuf) == h) {
297     return; /* nothing to do */
298   }
299
300   g_print ("drawing pixbuf at %dx%d\n", w, h);
301
302   if (draw_pixbuf)
303     g_object_unref (draw_pixbuf);
304
305   draw_pixbuf =
306       gdk_pixbuf_scale_simple (last_pixbuf, w, h, GDK_INTERP_BILINEAR);
307   
308 }
309
310 static void
311 ui_drawing_size_allocate_cb (GtkWidget * drawing_area,
312     GtkAllocation * allocation, gpointer data)
313 {
314   update_draw_pixbuf (allocation->width, allocation->height);
315 }
316
317 /*
318  * UI handling functions (mapped by GtkBuilder)
319  */
320
321 gboolean
322 on_drawingMain_expose_event (GtkWidget * widget, GdkEventExpose * event,
323     gpointer data)
324 {
325   GtkAllocation a = widget->allocation;
326   gint w, h, x, y;
327
328   if (draw_pixbuf == NULL)
329     return FALSE;
330
331   w = gdk_pixbuf_get_width (draw_pixbuf);
332   h = gdk_pixbuf_get_height (draw_pixbuf);
333
334   /* center image */
335   x = (a.width - w) / 2;
336   y = (a.height - h) / 2;
337
338   /* sanity check, shouldn't happen */
339   if (x < 0)
340     x = 0;
341   if (y < 0)
342     y = 0;
343
344   gdk_draw_pixbuf (GDK_DRAWABLE (widget->window), widget->style->black_gc,
345       draw_pixbuf, 0, 0, x, y, w, h, GDK_RGB_DITHER_NONE, 0, 0);
346
347   return TRUE; /* handled expose event */
348 }
349
350 void
351 on_windowMain_delete_event (GtkWidget * widget, GdkEvent * event,
352     gpointer user_data)
353 {
354   gst_element_set_state (gst_pipeline, GST_STATE_NULL);
355   gst_element_get_state (gst_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
356   gtk_main_quit ();
357 }
358
359 void
360 on_buttonInsert_clicked (GtkButton * button, gpointer user_data)
361 {
362   GtkTreeStore *store = NULL;
363   GtkTreeIter iter;
364   const gchar *tag = gtk_entry_get_text (ui_entry_insert_tag);
365   const gchar *value = gtk_entry_get_text (ui_entry_insert_value);
366
367   if ( tag_list == NULL ) {
368     tag_list = gst_tag_list_new ();
369   }
370
371   if (tag && value && tag[0] != '\0') {
372
373     /* insert just new tags (the ones already in list should be modified) */
374     if (gst_tag_list_get_tag_size (tag_list, tag)) {
375       fprintf (stderr, "%s tag is already in the list try to modify it\n", tag);
376     } else {
377       if (change_tag_list (&tag_list, tag, value)) {
378         /* just add to ui_tree if it has been added to tag_list */
379         store =
380             GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (ui_tree)));
381         gtk_tree_store_append (store, &iter, NULL);
382         gtk_tree_store_set (store, &iter, COL_TAG, tag, COL_VALUE, value, -1);
383       }
384     }
385
386   }
387
388   return;
389
390 }
391
392 static void
393 setup_new_filename (GString * str, const gchar * ext)
394 {
395   int i = 0;
396
397   for (i = str->len - 1; i > 0; --i) {
398     if (str->str[i] == '/') {
399       ++i;
400       break;
401     }
402   }
403   g_string_insert (str, i, "_new_");
404   if (ext) {
405     int len = strlen (ext);
406
407     if (len > str->len)
408       g_string_append (str, ext);
409     else if (strcasecmp (ext, &str->str[str->len - len]))
410       g_string_append (str, ext);
411
412   }
413 }
414
415 void
416 on_buttonSaveFile_clicked (GtkButton * button, gpointer user_data)
417 {
418
419   GString *src_file = NULL;
420   gint enc_status = ENC_UNKNOWN;
421   const gboolean use_v4l2 =
422     gtk_toggle_button_get_active (ui_chk_bnt_capture_v4l2);
423   const gboolean use_test =
424     gtk_toggle_button_get_active (ui_chk_bnt_capture_test);
425
426   gst_element_set_state (gst_pipeline, GST_STATE_NULL);
427   gst_element_get_state (gst_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
428
429   src_file = g_string_new (filename->str);
430
431   if (use_v4l2 || use_test) {
432     setup_new_filename (filename, ".jpg");
433     if (me_gst_setup_capture_pipeline (src_file->str, filename->str,
434         &enc_status, use_v4l2)) {
435       goto done;
436     }
437   } else {
438     setup_new_filename (filename, NULL);
439     if (me_gst_setup_encode_pipeline (src_file->str, filename->str,
440             &enc_status)) {
441       goto done;
442     }
443   }
444
445   ui_refresh ();
446   remove (filename->str);
447
448   if (tag_list && gst_metadata_mux) {
449     GstTagSetter *setter = GST_TAG_SETTER (gst_metadata_mux);
450
451     if (setter) {
452       gst_element_set_state (gst_pipeline, GST_STATE_READY);
453       gst_element_get_state (gst_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
454
455       gst_tag_setter_merge_tags (setter, tag_list, GST_TAG_MERGE_REPLACE);
456
457     }
458   }
459
460   gst_element_set_state (gst_pipeline, GST_STATE_PLAYING);
461   gst_element_get_state (gst_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
462
463   /* wait until finished */
464   gtk_main ();
465
466   gst_element_set_state (gst_pipeline, GST_STATE_NULL);
467   gst_element_get_state (gst_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
468
469   if (enc_status == ENC_DONE) {
470
471     /* view new file */
472     if (tag_list) {
473       gst_tag_list_free (tag_list);
474       tag_list = NULL;
475     }
476
477     me_gst_setup_view_pipeline (filename->str);
478
479     gst_element_set_state (gst_pipeline, GST_STATE_PLAYING);
480     gst_element_get_state (gst_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
481
482   }
483
484 done:
485
486   if (src_file)
487     g_string_free (src_file, TRUE);
488
489 }
490
491 void
492 on_checkbuttonCaptureV4l2_toggled (GtkToggleButton * togglebutton,
493     gpointer user_data)
494 {
495   if (gtk_toggle_button_get_active (togglebutton))
496     gtk_toggle_button_set_active(ui_chk_bnt_capture_test, FALSE);
497 }
498
499 void
500 on_checkbuttonCaptureTest_toggled (GtkToggleButton * togglebutton,
501     gpointer user_data)
502 {
503   if (gtk_toggle_button_get_active (togglebutton))
504     gtk_toggle_button_set_active(ui_chk_bnt_capture_v4l2, FALSE);
505 }
506
507 void
508 on_checkbuttonOptionsDemuxExif_toggled (GtkToggleButton * togglebutton,
509     gpointer user_data)
510 {
511   if (gtk_toggle_button_get_active (togglebutton))
512     app_options |= APP_OPT_DEMUX_EXIF;
513   else
514     app_options &= ~APP_OPT_DEMUX_EXIF;
515 }
516
517 void
518 on_checkbuttonOptionsDemuxIptc_toggled (GtkToggleButton * togglebutton,
519     gpointer user_data)
520 {
521   if (gtk_toggle_button_get_active (togglebutton))
522     app_options |= APP_OPT_DEMUX_IPTC;
523   else
524     app_options &= ~APP_OPT_DEMUX_IPTC;
525 }
526
527 void
528 on_checkbuttonOptionsDemuxXmp_toggled (GtkToggleButton * togglebutton,
529     gpointer user_data)
530 {
531   if (gtk_toggle_button_get_active (togglebutton))
532     app_options |= APP_OPT_DEMUX_XMP;
533   else
534     app_options &= ~APP_OPT_DEMUX_XMP;
535 }
536
537 void
538 on_checkbuttonOptionsMuxExif_toggled (GtkToggleButton * togglebutton,
539     gpointer user_data)
540 {
541   if (gtk_toggle_button_get_active (togglebutton))
542     app_options |= APP_OPT_MUX_EXIF;
543   else
544     app_options &= ~APP_OPT_MUX_EXIF;
545 }
546
547 void
548 on_checkbuttonOptionsMuxIptc_toggled (GtkToggleButton * togglebutton,
549     gpointer user_data)
550 {
551   if (gtk_toggle_button_get_active (togglebutton))
552     app_options |= APP_OPT_MUX_IPTC;
553   else
554     app_options &= ~APP_OPT_MUX_IPTC;
555 }
556
557 void
558 on_checkbuttonOptionsMuxXmp_toggled (GtkToggleButton * togglebutton,
559     gpointer user_data)
560 {
561   if (gtk_toggle_button_get_active (togglebutton))
562     app_options |= APP_OPT_MUX_XMP;
563   else
564     app_options &= ~APP_OPT_MUX_XMP;
565 }
566
567 void
568 on_buttonOpenFile_clicked (GtkButton * button, gpointer user_data)
569 {
570   GtkWidget *dialog;
571   gboolean open = FALSE;
572
573   dialog = gtk_file_chooser_dialog_new ("Open File",
574                                         GTK_WINDOW(ui_main_window),
575                                         GTK_FILE_CHOOSER_ACTION_OPEN,
576                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
577                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
578                                         NULL);
579
580   if (filename) {
581     const char *p = filename->str;
582     char *q = filename->str + filename->len - 1;
583     for (;p != q; --q) {
584       if ( *q == '/' )
585         break;
586     }
587     if ( p != q )
588       *q = '\0';
589     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (dialog),
590         filename->str);
591     if ( p != q )
592       *q = '/';
593   }
594
595   open = gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT;
596
597   if (open) {
598     char *str;
599
600     str = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
601     if (filename)
602       g_string_free (filename, TRUE);
603     filename = g_string_new(str);
604     g_free (str);
605   }
606
607   gtk_widget_destroy (dialog);
608
609   if (open) {
610     process_file();
611   }
612
613 }
614
615
616 /*
617  * UI handling functions
618  */
619
620 void
621 on_cell_edited (GtkCellRendererText * renderer, gchar * str_path,
622     gchar * new_text, gpointer user_data)
623 {
624   GtkTreePath *path = NULL;
625   GtkTreeIter iter;
626   GtkTreeModel *model = NULL;
627   const gint col_index = GPOINTER_TO_INT (user_data);
628   const gchar *tag = gtk_entry_get_text (ui_entry_insert_tag);
629
630   path = gtk_tree_path_new_from_string (str_path);
631   model = gtk_tree_view_get_model (GTK_TREE_VIEW (ui_tree));
632
633   if (change_tag_list (&tag_list, tag, new_text)) {
634
635     if (gtk_tree_model_get_iter (model, &iter, path)) {
636       gtk_tree_store_set (GTK_TREE_STORE (model), &iter, col_index, new_text,
637           -1);
638       gtk_entry_set_text (ui_entry_insert_value, new_text);
639     }
640
641   }
642
643   if (path)
644     gtk_tree_path_free (path);
645
646 }
647
648 static void
649 on_tree_selection_changed (GtkTreeSelection * selection, gpointer data)
650 {
651   GtkTreeIter iter;
652   GtkTreeModel *model;
653
654   if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
655     gchar *tag;
656     gchar *value;
657
658     gtk_tree_model_get (model, &iter, COL_TAG, &tag, -1);
659     gtk_tree_model_get (model, &iter, COL_VALUE, &value, -1);
660
661     gtk_entry_set_text (ui_entry_insert_tag, tag);
662     gtk_entry_set_text (ui_entry_insert_value, value);
663
664     g_free (value);
665     g_free (tag);
666
667   }
668
669 }
670
671 /*
672  * UI helper functions
673  */
674
675 static int
676 ui_add_columns (GtkTreeView * tree_view, const gchar * title, gint col_index,
677     gboolean editable)
678 {
679   GtkCellRenderer *renderer;
680   GtkTreeViewColumn *tree_col;
681   int ret = 0;
682
683   renderer = gtk_cell_renderer_text_new ();
684
685   if (editable) {
686     g_object_set (renderer, "editable", TRUE, NULL);
687     g_signal_connect (G_OBJECT (renderer), "edited",
688         G_CALLBACK (on_cell_edited), GINT_TO_POINTER (col_index));
689   }
690
691   if ((tree_col = gtk_tree_view_column_new_with_attributes (title, renderer,
692               "text", col_index, NULL))) {
693     gtk_tree_view_append_column (tree_view, tree_col);
694   } else {
695     fprintf (stderr, "UI: could not create column %s\n", title);
696     ret = -201;
697     goto done;
698   }
699
700 done:
701
702   return ret;
703
704 }
705
706
707 static int
708 ui_setup_tree_view (GtkTreeView * tree_view)
709 {
710   int ret = 0;
711   GtkTreeStore *tree_store = NULL;
712   GtkTreeSelection *select;
713
714   if ((ret = ui_add_columns (tree_view, "tag", COL_TAG, FALSE)))
715     goto done;
716
717   if ((ret = ui_add_columns (tree_view, "value", COL_VALUE, TRUE)))
718     goto done;
719
720   tree_store = gtk_tree_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_STRING);
721
722   gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (tree_store));
723
724   select = gtk_tree_view_get_selection (tree_view);
725   gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
726   g_signal_connect (G_OBJECT (select), "changed",
727       G_CALLBACK (on_tree_selection_changed), NULL);
728
729 done:
730
731   if (tree_store)
732     g_object_unref (tree_store);
733
734   return ret;
735 }
736
737 static void
738 ui_refresh (void)
739 {
740   GtkTreeStore *store =
741       GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (ui_tree)));
742   gtk_tree_store_clear (store);
743   if (filename)
744     gtk_window_set_title (GTK_WINDOW (ui_main_window), filename->str);
745 }
746
747 static int
748 ui_create (void)
749 {
750   GError *error = NULL;
751   int ret = 0;
752
753   builder = gtk_builder_new ();
754   if (!gtk_builder_add_from_file (builder, "metadata_editor.ui", &error))
755   {
756     g_warning ("Couldn't load builder file: %s", error->message);
757     g_error_free (error);
758     ret = -101;
759     goto done;
760   }
761
762   ui_main_window = GTK_WIDGET (gtk_builder_get_object (builder, "windowMain"));
763
764   ui_drawing = GTK_WIDGET (gtk_builder_get_object (builder, "drawingMain"));
765
766   ui_tree = GTK_WIDGET (gtk_builder_get_object (builder, "treeMain"));
767
768   ui_entry_insert_tag =
769           GTK_ENTRY (gtk_builder_get_object (builder, "entryTag"));
770
771   ui_entry_insert_value =
772           GTK_ENTRY (gtk_builder_get_object (builder, "entryValue"));
773
774   ui_chk_bnt_capture_v4l2 =
775           GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder,
776                                                      "checkbuttonCaptureV4l2"));
777
778   ui_chk_bnt_capture_test =
779           GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder,
780                                                      "checkbuttonCaptureTest"));
781
782   if (!(ui_main_window && ui_drawing && ui_tree
783           && ui_entry_insert_tag && ui_entry_insert_value
784           && ui_chk_bnt_capture_v4l2 && ui_chk_bnt_capture_test)) {
785     fprintf (stderr, "Some widgets couldn't be created\n");
786     ret = -105;
787     goto done;
788   }
789
790   g_signal_connect_after (ui_drawing, "size-allocate",
791       G_CALLBACK (ui_drawing_size_allocate_cb), NULL);
792
793   gtk_builder_connect_signals (builder, NULL);
794
795   ui_setup_tree_view (GTK_TREE_VIEW (ui_tree));
796
797   ui_refresh ();
798
799   gtk_widget_show_all (ui_main_window);
800
801 done:
802
803   return ret;
804
805 }
806
807 /*
808  * GStreamer functions
809  */
810
811
812 static gboolean
813 me_gst_bus_callback_encode (GstBus * bus, GstMessage * message, gpointer data)
814 {
815   gint *encode_status = (gint *) data;
816
817   fflush (stdout);
818
819   switch (GST_MESSAGE_TYPE (message)) {
820     case GST_MESSAGE_ERROR:
821     {
822       GError *err;
823       gchar *debug;
824
825       gst_message_parse_error (message, &err, &debug);
826       fprintf (stderr, "Error: %s\n", err->message);
827       g_error_free (err);
828       g_free (debug);
829
830       *encode_status = ENC_ERROR;
831       gtk_main_quit ();
832     }
833       break;
834     case GST_MESSAGE_TAG:
835     {
836       /* ignore, we alredy have the tag list */
837     }
838       break;
839     case GST_MESSAGE_EOS:
840     {
841       *encode_status = ENC_DONE;
842       gtk_main_quit ();
843     }
844       break;
845     default:
846       /* unhandled message */
847       break;
848   }
849
850   /* we want to be notified again the next time there is a message
851    * on the bus, so returning TRUE (FALSE means we want to stop watching
852    * for messages on the bus and our callback should not be called again)
853    */
854   return TRUE;
855 }
856
857 static gboolean
858 me_gst_bus_callback_view (GstBus * bus, GstMessage * message, gpointer data)
859 {
860   switch (GST_MESSAGE_TYPE (message)) {
861     case GST_MESSAGE_ERROR:
862     {
863       GError *err;
864       gchar *debug;
865
866       gst_message_parse_error (message, &err, &debug);
867       fprintf (stderr, "Error: %s\n", err->message);
868       g_error_free (err);
869       g_free (debug);
870
871       gtk_main_quit ();
872     }
873       break;
874     case GST_MESSAGE_TAG:
875     {
876       if (tag_list == NULL)
877         gst_message_parse_tag (message, &tag_list);
878       else {
879         GstTagList *tl = NULL;
880         GstTagList *ntl = NULL;
881
882         gst_message_parse_tag (message, &tl);
883         if (tl) {
884           ntl = gst_tag_list_merge (tag_list, tl, GST_TAG_MERGE_PREPEND);
885           if (ntl) {
886             gst_tag_list_free (tag_list);
887             tag_list = ntl;
888           }
889           gst_tag_list_free (tl);
890         }
891       }
892       /* remove whole chunk tags */
893       gst_tag_list_remove_tag (tag_list, "exif");
894       gst_tag_list_remove_tag (tag_list, "iptc");
895       gst_tag_list_remove_tag (tag_list, "xmp");
896     }
897       break;
898     case GST_MESSAGE_EOS:
899       if (tag_list) {
900         gst_tag_list_foreach (tag_list, insert_tag_on_tree, ui_tree);
901       }
902       break;
903     case GST_MESSAGE_ELEMENT: {
904       const GValue *val;
905
906       /* only interested in element messages from our gdkpixbufsink */
907       if (message->src != GST_OBJECT_CAST (gst_video_sink))
908         break;
909
910       /* only interested in the first image (not any smaller previews) */
911       if (gst_structure_has_name (message->structure, "pixbuf"))
912         break;
913
914       if (!gst_structure_has_name (message->structure, "preroll-pixbuf"))
915         break;
916
917       val = gst_structure_get_value (message->structure, "pixbuf");
918       g_return_val_if_fail (val != NULL, TRUE);
919
920       if (last_pixbuf)
921         g_object_unref (last_pixbuf);
922
923       last_pixbuf = GDK_PIXBUF (g_value_dup_object (val));
924
925       g_print ("Got image pixbuf: %dx%d\n", gdk_pixbuf_get_width (last_pixbuf),
926           gdk_pixbuf_get_height (last_pixbuf));
927
928       update_draw_pixbuf (GTK_WIDGET (ui_drawing)->allocation.width,
929           GTK_WIDGET (ui_drawing)->allocation.height);
930
931       gtk_widget_queue_draw (ui_drawing);
932       break;
933     }
934     default:
935       /* unhandled message */
936       break;
937   }
938
939   /* we want to be notified again the next time there is a message
940    * on the bus, so returning TRUE (FALSE means we want to stop watching
941    * for messages on the bus and our callback should not be called again)
942    */
943   return TRUE;
944 }
945
946 static void
947 me_gst_cleanup_elements (void)
948 {
949   /* when adding an element to pipeline rember to set it to NULL or add extra ref */
950
951   if (gst_source) {
952     gst_object_unref (gst_source);
953     gst_source = NULL;
954   }
955   if (gst_metadata_demux) {
956     gst_object_unref (gst_metadata_demux);
957     gst_metadata_demux = NULL;
958   }
959   if (gst_metadata_mux) {
960     gst_object_unref (gst_metadata_mux);
961     gst_metadata_mux = NULL;
962   }
963   if (gst_image_dec) {
964     gst_object_unref (gst_image_dec);
965     gst_image_dec = NULL;
966   }
967   if (gst_image_enc) {
968     gst_object_unref (gst_image_enc);
969     gst_image_enc = NULL;
970   }
971   if (gst_video_scale) {
972     gst_object_unref (gst_video_scale);
973     gst_video_scale = NULL;
974   }
975   if (gst_video_convert) {
976     gst_object_unref (gst_video_convert);
977     gst_video_convert = NULL;
978   }
979   if (gst_video_sink) {
980     gst_object_unref (gst_video_sink);
981     gst_video_sink = NULL;
982   }
983   if (gst_file_sink) {
984     gst_object_unref (gst_file_sink);
985     gst_file_sink = NULL;
986   }
987
988   if (gst_pipeline) {
989     gst_element_set_state (gst_pipeline, GST_STATE_NULL);
990     gst_element_get_state (gst_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
991     gst_object_unref (gst_pipeline);
992     gst_pipeline = NULL;
993   }
994
995 }
996
997 /* dummy function that looks the file extension */
998 static gboolean
999 is_png (const gchar * filename)
1000 {
1001   gboolean ret = FALSE;
1002   guint32 len;
1003
1004   if (!filename)
1005     goto done;
1006
1007   if ((len = strlen (filename)) < 4)    /* at least ".png" */
1008     goto done;
1009
1010   if (0 == strcasecmp (filename + (len - 4), ".png"))
1011     ret = TRUE;
1012
1013 done:
1014
1015   return ret;
1016
1017 }
1018
1019 static int
1020 me_gst_setup_capture_pipeline (const gchar * src_file, const gchar * dest_file,
1021     gint * encode_status, gboolean use_v4l2)
1022 {
1023   int ret = 0;
1024   GstBus *bus = NULL;
1025   gboolean linked;
1026
1027   *encode_status = ENC_ERROR;
1028
1029   me_gst_cleanup_elements ();
1030
1031   /* create elements */
1032   if ( use_v4l2 )
1033     gst_source = gst_element_factory_make ("v4l2src", NULL);
1034   else
1035     gst_source = gst_element_factory_make ("videotestsrc", NULL);
1036   gst_video_convert = gst_element_factory_make ("ffmpegcolorspace", NULL);
1037   gst_image_enc = gst_element_factory_make ("jpegenc", NULL);
1038   gst_metadata_mux = gst_element_factory_make ("metadatamux", NULL);
1039   gst_file_sink = gst_element_factory_make ("filesink", NULL);
1040
1041   if (!(gst_source && gst_video_convert && gst_image_enc && gst_metadata_mux
1042           && gst_file_sink)) {
1043     fprintf (stderr, "An element couldn't be created for ecoding\n");
1044     ret = -300;
1045     goto done;
1046   }
1047
1048   /* create gst_pipeline */
1049   gst_pipeline = gst_pipeline_new (NULL);
1050
1051   if (NULL == gst_pipeline) {
1052     fprintf (stderr, "Pipeline couldn't be created\n");
1053     ret = -305;
1054     goto done;
1055   }
1056
1057   /* set elements's properties */
1058   g_object_set (gst_source, "num-buffers", 1, NULL);
1059   g_object_set (gst_file_sink, "location", dest_file, NULL);
1060   if ( app_options & APP_OPT_MUX_EXIF )
1061     g_object_set (gst_metadata_mux, "exif", TRUE, NULL);
1062   else
1063     g_object_set (gst_metadata_mux, "exif", FALSE, NULL);
1064
1065   if ( app_options & APP_OPT_MUX_IPTC )
1066     g_object_set (gst_metadata_mux, "iptc", TRUE, NULL);
1067   else
1068     g_object_set (gst_metadata_mux, "iptc", FALSE, NULL);
1069
1070   if ( app_options & APP_OPT_MUX_XMP )
1071     g_object_set (gst_metadata_mux, "xmp", TRUE, NULL);
1072   else
1073     g_object_set (gst_metadata_mux, "xmp", FALSE, NULL);
1074
1075   /* adding and linking elements */
1076   gst_bin_add_many (GST_BIN (gst_pipeline), gst_source, gst_video_convert,
1077       gst_image_enc, gst_metadata_mux, gst_file_sink, NULL);
1078
1079   linked =
1080       gst_element_link_many (gst_source, gst_video_convert, gst_image_enc,
1081       gst_metadata_mux, gst_file_sink, NULL);
1082
1083   /* now element are owned by pipeline (for videosink we keep a extra ref) */
1084   gst_source = gst_video_convert = gst_image_enc = gst_file_sink = NULL;
1085   gst_object_ref (gst_metadata_mux);
1086
1087   if (!linked) {
1088     fprintf (stderr, "Elements couldn't be linked\n");
1089     ret = -310;
1090     goto done;
1091   }
1092
1093   *encode_status = ENC_UNKNOWN;
1094
1095   /* adding message bus */
1096   bus = gst_pipeline_get_bus (GST_PIPELINE (gst_pipeline));
1097   gst_bus_add_watch (bus, me_gst_bus_callback_encode, encode_status);
1098   gst_object_unref (bus);
1099
1100 done:
1101
1102   return ret;
1103
1104 }
1105
1106 static int
1107 me_gst_setup_encode_pipeline (const gchar * src_file, const gchar * dest_file,
1108     gint * encode_status)
1109 {
1110   int ret = 0;
1111   GstBus *bus = NULL;
1112   gboolean linked;
1113
1114   *encode_status = ENC_ERROR;
1115
1116   me_gst_cleanup_elements ();
1117
1118   /* create elements */
1119   gst_source = gst_element_factory_make ("filesrc", NULL);
1120   gst_metadata_demux = gst_element_factory_make ("metadatademux", NULL);
1121   gst_metadata_mux = gst_element_factory_make ("metadatamux", NULL);
1122   gst_file_sink = gst_element_factory_make ("filesink", NULL);
1123
1124
1125   if (!(gst_source && gst_metadata_demux && gst_metadata_mux && gst_file_sink)) {
1126     fprintf (stderr, "An element couldn't be created for ecoding\n");
1127     ret = -300;
1128     goto done;
1129   }
1130
1131
1132   /* create gst_pipeline */
1133   gst_pipeline = gst_pipeline_new (NULL);
1134
1135   if (NULL == gst_pipeline) {
1136     fprintf (stderr, "Pipeline couldn't be created\n");
1137     ret = -305;
1138     goto done;
1139   }
1140
1141   /* set elements's properties */
1142   g_object_set (gst_source, "location", src_file, NULL);
1143   g_object_set (gst_file_sink, "location", dest_file, NULL);
1144
1145   if ( app_options & APP_OPT_DEMUX_EXIF )
1146     g_object_set (gst_metadata_demux, "exif", TRUE, NULL);
1147   else
1148     g_object_set (gst_metadata_demux, "exif", FALSE, NULL);
1149
1150   if ( app_options & APP_OPT_DEMUX_IPTC )
1151     g_object_set (gst_metadata_demux, "iptc", TRUE, NULL);
1152   else
1153     g_object_set (gst_metadata_demux, "iptc", FALSE, NULL);
1154
1155   if ( app_options & APP_OPT_DEMUX_XMP )
1156     g_object_set (gst_metadata_demux, "xmp", TRUE, NULL);
1157   else
1158     g_object_set (gst_metadata_demux, "xmp", FALSE, NULL);
1159
1160   if ( app_options & APP_OPT_MUX_EXIF )
1161     g_object_set (gst_metadata_mux, "exif", TRUE, NULL);
1162   else
1163     g_object_set (gst_metadata_mux, "exif", FALSE, NULL);
1164
1165   if ( app_options & APP_OPT_MUX_IPTC )
1166     g_object_set (gst_metadata_mux, "iptc", TRUE, NULL);
1167   else
1168     g_object_set (gst_metadata_mux, "iptc", FALSE, NULL);
1169
1170   if ( app_options & APP_OPT_MUX_XMP )
1171     g_object_set (gst_metadata_mux, "xmp", TRUE, NULL);
1172   else
1173     g_object_set (gst_metadata_mux, "xmp", FALSE, NULL);
1174
1175   /* adding and linking elements */
1176   gst_bin_add_many (GST_BIN (gst_pipeline), gst_source, gst_metadata_demux,
1177       gst_metadata_mux, gst_file_sink, NULL);
1178
1179   linked =
1180       gst_element_link_many (gst_source, gst_metadata_demux, gst_metadata_mux,
1181       gst_file_sink, NULL);
1182
1183   /* now element are owned by pipeline (for videosink we keep a extra ref) */
1184   gst_source = gst_metadata_demux = gst_file_sink = NULL;
1185   gst_object_ref (gst_metadata_mux);
1186
1187   if (!linked) {
1188     fprintf (stderr, "Elements couldn't be linked\n");
1189     ret = -310;
1190     goto done;
1191   }
1192
1193   *encode_status = ENC_UNKNOWN;
1194
1195   /* adding message bus */
1196   bus = gst_pipeline_get_bus (GST_PIPELINE (gst_pipeline));
1197   gst_bus_add_watch (bus, me_gst_bus_callback_encode, encode_status);
1198   gst_object_unref (bus);
1199
1200 done:
1201
1202   return ret;
1203
1204 }
1205
1206 static int
1207 me_gst_setup_view_pipeline (const gchar * filename)
1208 {
1209   int ret = 0;
1210   GstBus *bus = NULL;
1211   gboolean linked;
1212
1213   me_gst_cleanup_elements ();
1214
1215   /* create elements */
1216   gst_source = gst_element_factory_make ("filesrc", NULL);
1217   gst_metadata_demux = gst_element_factory_make ("metadatademux", NULL);
1218   /* let's do a dummy stuff to avoid decodebin */
1219   if (is_png (filename))
1220     gst_image_dec = gst_element_factory_make ("pngdec", NULL);
1221   else
1222     gst_image_dec = gst_element_factory_make ("jpegdec", NULL);
1223   gst_video_scale = gst_element_factory_make ("videoscale", NULL);
1224   gst_video_convert = gst_element_factory_make ("ffmpegcolorspace", NULL);
1225   gst_video_sink = gst_element_factory_make ("gdkpixbufsink", NULL);
1226
1227   if (gst_video_sink == NULL) {
1228     if (!gst_default_registry_check_feature_version ("gdkpixbufdec", 0, 10, 0))
1229       g_warning ("Could not create 'gdkpixbufsink' element");
1230     else {
1231       g_warning ("Could not create 'gdkpixbufsink' element. "
1232           "(May be your gst-plugins-good is too old?)");
1233     ret = -400;
1234     }
1235     goto done;
1236   }
1237   if (!(gst_source && gst_metadata_demux && gst_image_dec && gst_video_scale
1238           && gst_video_convert && gst_video_sink)) {
1239     fprintf (stderr, "An element couldn't be created for viewing\n");
1240     ret = -400;
1241     goto done;
1242   }
1243
1244   /* create gst_pipeline */
1245   gst_pipeline = gst_pipeline_new (NULL);
1246
1247   if (NULL == gst_pipeline) {
1248     fprintf (stderr, "Pipeline couldn't be created\n");
1249     ret = -405;
1250     goto done;
1251   }
1252
1253   /* set elements's properties */
1254   g_object_set (gst_source, "location", filename, NULL);
1255   g_object_set (gst_metadata_demux, "parse-only", TRUE, NULL);
1256
1257
1258   /* adding and linking elements */
1259   gst_bin_add_many (GST_BIN (gst_pipeline), gst_source, gst_metadata_demux,
1260       gst_image_dec, gst_video_scale, gst_video_convert, gst_video_sink, NULL);
1261
1262   linked = gst_element_link_many (gst_source, gst_metadata_demux, gst_image_dec,
1263       gst_video_scale, gst_video_convert, gst_video_sink, NULL);
1264
1265   /* now element are owned by pipeline (for videosink we keep a extra ref) */
1266   gst_source = gst_metadata_demux = gst_image_dec = gst_video_scale =
1267       gst_video_convert = NULL;
1268   gst_object_ref (gst_video_sink);
1269
1270   if (last_pixbuf) {
1271     g_object_unref (last_pixbuf);
1272     last_pixbuf = NULL;
1273   }
1274
1275   if (!linked) {
1276     fprintf (stderr, "Elements couldn't be linked\n");
1277     ret = -410;
1278     goto done;
1279   }
1280
1281   /* adding message bus */
1282   bus = gst_pipeline_get_bus (GST_PIPELINE (gst_pipeline));
1283   gst_bus_add_watch (bus, me_gst_bus_callback_view, NULL);
1284   gst_object_unref (bus);
1285
1286
1287 done:
1288
1289   return ret;
1290
1291 }
1292
1293 static void
1294 process_file(void)
1295 {
1296   /* filename for future usage (title and file name to be created) */
1297   me_gst_cleanup_elements ();
1298
1299   if (tag_list) {
1300     gst_tag_list_free (tag_list);
1301     tag_list = NULL;
1302   }
1303
1304   /* create pipeline */
1305   me_gst_setup_view_pipeline (filename->str);
1306
1307   gst_element_set_state (gst_pipeline, GST_STATE_PLAYING);
1308
1309   ui_refresh ();
1310
1311 }
1312
1313 int
1314 main (int argc, char *argv[])
1315 {
1316   int ret = 0;
1317
1318   if (argc >= 2) {
1319     if (filename)
1320       g_string_free (filename, TRUE);
1321     filename = g_string_new (argv[1]);
1322   }
1323
1324   gst_init (&argc, &argv);
1325   gtk_init (&argc, &argv);
1326
1327   /* create UI */
1328   if ((ret = ui_create ())) {
1329     goto done;
1330   }
1331
1332   if (argc >= 2) {
1333     process_file();
1334   }
1335
1336   gtk_main ();
1337
1338 done:
1339
1340   me_gst_cleanup_elements ();
1341
1342   if (tag_list) {
1343     gst_tag_list_free (tag_list);
1344     tag_list = NULL;
1345   }
1346
1347   if (filename) {
1348     g_string_free (filename, TRUE);
1349     filename = NULL;
1350   }
1351
1352   return ret;
1353 }