[frogr] Allow automatic import from tags from XMP metadata.



commit 6efca9033c5c42d553b9f8e129c4af756ae0d787
Author: Mario Sanchez Prada <msanchez igalia com>
Date:   Tue Aug 16 09:39:40 2011 +0200

    Allow automatic import from tags from XMP metadata.
    
    This should enable frogr to import keywords and tags as set in other
    applications (e.g. shotwell, f-spot...) to use them rightaway when
    uploading to flickr.

 src/frogr-config.c          |   36 ++++++
 src/frogr-config.h          |    5 +
 src/frogr-picture-loader.c  |  275 ++++++++++++++++++++++++++++++-------------
 src/frogr-settings-dialog.c |   18 +++-
 4 files changed, 253 insertions(+), 81 deletions(-)
---
diff --git a/src/frogr-config.c b/src/frogr-config.c
index 64e661f..56bcdae 100644
--- a/src/frogr-config.c
+++ b/src/frogr-config.c
@@ -61,6 +61,8 @@ struct _FrogrConfigPrivate
 
   gboolean tags_autocompletion;
   gboolean keep_file_extensions;
+  gboolean import_tags_from_metadata;
+
   SortingCriteria mainview_sorting_criteria;
   gboolean mainview_sorting_reversed;
   gboolean mainview_enable_tooltips;
@@ -263,6 +265,16 @@ _load_settings (FrogrConfig *self, const gchar *config_dir)
               xmlFree (content);
             }
 
+          if (!xmlStrcmp (node->name, (const xmlChar*) "import-tags-from-metadata"))
+            {
+              xmlChar *content = NULL;
+
+              content = xmlNodeGetContent (node);
+              priv->import_tags_from_metadata = !xmlStrcmp (content, (const xmlChar*) "1");
+
+              xmlFree (content);
+            }
+
           if (!xmlStrcmp (node->name, (const xmlChar*) "mainview-options"))
             _load_mainview_options_xml (self, xml, node);
 
@@ -654,6 +666,7 @@ _save_settings (FrogrConfig *self)
   /* Other stuff */
   _xml_add_bool_child (root, "tags-autocompletion", priv->tags_autocompletion);
   _xml_add_bool_child (root, "keep-file-extensions", priv->keep_file_extensions);
+  _xml_add_bool_child (root, "import-tags-from-metadata", priv->import_tags_from_metadata);
   node = xmlNewNode (NULL, (const xmlChar*) "mainview-options");
   _xml_add_bool_child (node, "enable-tooltips", priv->mainview_enable_tooltips);
   _xml_add_int_child (node, "sorting-criteria", priv->mainview_sorting_criteria);
@@ -914,6 +927,7 @@ frogr_config_init (FrogrConfig *self)
   priv->content_type = FSP_CONTENT_TYPE_PHOTO;
   priv->tags_autocompletion = TRUE;
   priv->keep_file_extensions = FALSE;
+  priv->import_tags_from_metadata = TRUE;
   priv->mainview_sorting_criteria = SORT_AS_LOADED;
   priv->mainview_sorting_reversed = FALSE;
   priv->mainview_enable_tooltips = TRUE;
@@ -1308,6 +1322,28 @@ frogr_config_get_keep_file_extensions (FrogrConfig *self)
 }
 
 void
+frogr_config_set_import_tags_from_metadata (FrogrConfig *self, gboolean value)
+{
+  FrogrConfigPrivate * priv = NULL;
+
+  g_return_if_fail (FROGR_IS_CONFIG (self));
+
+  priv = FROGR_CONFIG_GET_PRIVATE (self);
+  priv->import_tags_from_metadata = value;
+}
+
+gboolean
+frogr_config_get_import_tags_from_metadata (FrogrConfig *self)
+{
+  FrogrConfigPrivate *priv = NULL;
+
+  g_return_val_if_fail (FROGR_IS_CONFIG (self), FALSE);
+
+  priv = FROGR_CONFIG_GET_PRIVATE (self);
+  return priv->import_tags_from_metadata;
+}
+
+void
 frogr_config_set_mainview_enable_tooltips (FrogrConfig *self, gboolean value)
 {
   FrogrConfigPrivate * priv = NULL;
diff --git a/src/frogr-config.h b/src/frogr-config.h
index 6f806e7..3efdfaa 100644
--- a/src/frogr-config.h
+++ b/src/frogr-config.h
@@ -26,6 +26,7 @@
 
 #include "frogr-account.h"
 
+#include <config.h>
 #include <glib.h>
 #include <glib-object.h>
 #include <flicksoup/flicksoup.h>
@@ -124,6 +125,10 @@ void frogr_config_set_keep_file_extensions (FrogrConfig *self, gboolean value);
 
 gboolean frogr_config_get_keep_file_extensions (FrogrConfig *self);
 
+void frogr_config_set_import_tags_from_metadata (FrogrConfig *self, gboolean value);
+
+gboolean frogr_config_get_import_tags_from_metadata (FrogrConfig *self);
+
 void frogr_config_set_mainview_enable_tooltips (FrogrConfig *self, gboolean value);
 
 gboolean frogr_config_get_mainview_enable_tooltips (FrogrConfig *self);
diff --git a/src/frogr-picture-loader.c b/src/frogr-picture-loader.c
index 94f12e8..dfe700f 100644
--- a/src/frogr-picture-loader.c
+++ b/src/frogr-picture-loader.c
@@ -61,6 +61,7 @@ struct _FrogrPictureLoaderPrivate
   guint n_pictures;
 
   gboolean keep_file_extensions;
+  gboolean import_tags;
   gboolean public_visibility;
   gboolean family_visibility;
   gboolean friend_visibility;
@@ -95,6 +96,15 @@ static void _load_next_picture_cb (GObject *object,
                                    GAsyncResult *res,
                                    gpointer data);
 
+static gboolean get_gps_coordinate (ExifData *exif,
+                                    ExifTag   tag,
+                                    ExifTag   reftag,
+                                    gdouble  *coordinate);
+static FspDataLocation *get_location_from_exif (ExifData *exif_data);
+
+static gchar *remove_spaces_from_keyword (const gchar *keyword);
+static gchar *import_tags_from_xmp_keywords (const char *buffer, size_t len);
+
 /* Private API */
 
 static void
@@ -197,86 +207,6 @@ _load_next_picture (FrogrPictureLoader *self)
     }
 }
 
-/* get_gps_coordinate is from
- * tracker/src/libtracker-extract//tracker-exif.c,
- * Copyright (C) 2009, Nokia <ivan frade nokia com>
- * Licensed under the GNU Lesser General Public License Version 2.1 or later
- */
-static gboolean
-get_gps_coordinate (ExifData *exif,
-                    ExifTag   tag,
-                    ExifTag   reftag,
-                    gdouble  *coordinate)
-{
-  ExifEntry *entry = exif_data_get_entry (exif, tag);
-  ExifEntry *refentry = exif_data_get_entry (exif, reftag);
-
-  g_return_val_if_fail (coordinate != NULL, FALSE);
-
-  if (entry && refentry)
-    {
-      ExifByteOrder order;
-      ExifRational c1,c2,c3;
-      gfloat f;
-      gchar ref;
-
-      order = exif_data_get_byte_order (exif);
-      c1 = exif_get_rational (entry->data, order);
-      c2 = exif_get_rational (entry->data+8, order);
-      c3 = exif_get_rational (entry->data+16, order);
-      ref = refentry->data[0];
-
-      /* Avoid ridiculous values */
-      if (c1.denominator == 0 ||
-          c2.denominator == 0 ||
-          c3.denominator == 0)
-        {
-          return FALSE;
-        }
-
-      f = (double)c1.numerator/c1.denominator+
-          (double)c2.numerator/(c2.denominator*60)+
-          (double)c3.numerator/(c3.denominator*60*60);
-
-      if (ref == 'S' || ref == 'W')
-        {
-          f = -1 * f;
-        }
-
-      *coordinate = f;
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-FspDataLocation *get_location_from_exif (ExifData *exif_data)
-{
-    FspDataLocation *location;
-    gdouble coordinate;
-    gboolean found;
-
-    if (!exif_data)
-      return NULL;
-    found = get_gps_coordinate (exif_data, EXIF_TAG_GPS_LATITUDE,
-                                EXIF_TAG_GPS_LATITUDE_REF, &coordinate);
-    if (!found)
-      return NULL;
-
-    location = FSP_DATA_LOCATION (fsp_data_new (FSP_LOCATION));
-    location->latitude = coordinate;
-
-    found = get_gps_coordinate (exif_data, EXIF_TAG_GPS_LONGITUDE,
-                                EXIF_TAG_GPS_LONGITUDE_REF, &location->longitude);
-    if (!found)
-      {
-        fsp_data_free (FSP_DATA (location));
-        return NULL;
-      }
-
-    return location;
-}
-
 static void
 _load_next_picture_cb (GObject *object,
                        GAsyncResult *res,
@@ -377,6 +307,7 @@ _load_next_picture_cb (GObject *object,
             {
               FspDataLocation *location;
 
+              /* Date and time for picture taken */
               exif_entry = exif_data_get_entry (exif_data, EXIF_TAG_DATE_TIME);
               if (exif_entry)
                 {
@@ -391,6 +322,21 @@ _load_next_picture_cb (GObject *object,
                   else
                     g_warning ("Found DateTime exif tag of invalid type");
                 }
+
+              /* Import tags from XMP metadata, if required */
+              if (priv->import_tags)
+                {
+                  gchar *imported_tags = NULL;
+
+                  imported_tags = import_tags_from_xmp_keywords (contents, length);
+                  if (imported_tags)
+                    {
+                      frogr_picture_set_tags (fpicture, imported_tags);
+                      g_free (imported_tags);
+                    }
+                }
+
+              /* GPS coordinates */
               location = get_location_from_exif (exif_data);
               if (location != NULL)
                 {
@@ -462,6 +408,174 @@ _load_next_picture_cb (GObject *object,
     }
 }
 
+
+/* This function was taken from tracker, licensed under the GNU Lesser
+ * General Public License Version 2.1 (Copyright 2009, Nokia Corp.) */
+static gboolean
+get_gps_coordinate (ExifData *exif,
+                    ExifTag   tag,
+                    ExifTag   reftag,
+                    gdouble  *coordinate)
+{
+  ExifEntry *entry = exif_data_get_entry (exif, tag);
+  ExifEntry *refentry = exif_data_get_entry (exif, reftag);
+
+  g_return_val_if_fail (coordinate != NULL, FALSE);
+
+  if (entry && refentry)
+    {
+      ExifByteOrder order;
+      ExifRational c1,c2,c3;
+      gfloat f;
+      gchar ref;
+
+      order = exif_data_get_byte_order (exif);
+      c1 = exif_get_rational (entry->data, order);
+      c2 = exif_get_rational (entry->data+8, order);
+      c3 = exif_get_rational (entry->data+16, order);
+      ref = refentry->data[0];
+
+      /* Avoid ridiculous values */
+      if (c1.denominator == 0 ||
+          c2.denominator == 0 ||
+          c3.denominator == 0)
+        {
+          return FALSE;
+        }
+
+      f = (double)c1.numerator/c1.denominator+
+          (double)c2.numerator/(c2.denominator*60)+
+          (double)c3.numerator/(c3.denominator*60*60);
+
+      if (ref == 'S' || ref == 'W')
+        {
+          f = -1 * f;
+        }
+
+      *coordinate = f;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static FspDataLocation *
+get_location_from_exif (ExifData *exif_data)
+{
+    FspDataLocation *location;
+    gdouble coordinate;
+    gboolean found;
+
+    if (!exif_data)
+      return NULL;
+    found = get_gps_coordinate (exif_data, EXIF_TAG_GPS_LATITUDE,
+                                EXIF_TAG_GPS_LATITUDE_REF, &coordinate);
+    if (!found)
+      return NULL;
+
+    location = FSP_DATA_LOCATION (fsp_data_new (FSP_LOCATION));
+    location->latitude = coordinate;
+
+    found = get_gps_coordinate (exif_data, EXIF_TAG_GPS_LONGITUDE,
+                                EXIF_TAG_GPS_LONGITUDE_REF, &location->longitude);
+    if (!found)
+      {
+        fsp_data_free (FSP_DATA (location));
+        return NULL;
+      }
+
+    return location;
+}
+
+static gchar *
+remove_spaces_from_keyword (const gchar *keyword)
+{
+  gchar *new_keyword = NULL;
+
+  if (keyword)
+    {
+      int i = 0;
+      int j = 0;
+
+      new_keyword = g_new0 (gchar, strlen(keyword) + 1);
+      for (i = 0; keyword[i] != '\0'; i++)
+        {
+          if (keyword[i] != ' ')
+            new_keyword[j++] = keyword[i];
+        }
+      new_keyword[j] = '\0';
+    }
+
+  return new_keyword;
+}
+
+static gchar *
+import_tags_from_xmp_keywords (const char *buffer, size_t len)
+{
+  gchar *keywords_start = NULL;
+  gchar *keywords_end = NULL;
+  gchar *result = NULL;
+  int i;
+
+  /* Look for the beginning of the XMP data interesting for as if
+     present, that is, the keywords (aka the 'tags') */
+  for (i = 0; i < len && !keywords_start; i++)
+    {
+      if (g_str_has_prefix (&buffer[i], "<dc:subject>"))
+        keywords_start = g_strdup(&buffer[i+12]);
+    }
+
+  /* Find the end of the interesting XMP data, if found */
+  if (keywords_start)
+    keywords_end = g_strrstr (keywords_start, "</dc:subject>");
+
+  if (keywords_end)
+    {
+      gchar *start = NULL;
+      gchar *end = NULL;
+
+      keywords_end[0] = '\0';
+
+      /* Remove extra not-needed stuff in the string */
+      start = g_strstr_len (keywords_start, -1, "<rdf:li>");
+      end = g_strrstr (keywords_start, "</rdf:li>");
+      if (start && end)
+        {
+          gchar **keywords = NULL;
+          gchar *keyword = NULL;
+          gchar *kw_end = NULL;
+
+          start = &start[8];
+          end[0] = '\0';
+
+          /* Get an array of strings with all the keywords */
+          keywords = g_regex_split_simple ("<rdf:li>", start, G_REGEX_DOTALL, 0);
+
+          /* Remove spaces and trailing '</rdf:li>' elements */
+          for (i = 0; keywords[i]; i++)
+            {
+              keyword = keywords[i];
+
+              /* Remove trailing '</rdf:li>' elements */
+              kw_end = g_strrstr (keyword, "</rdf:li>");
+              if (kw_end)
+                kw_end[0] = '\0';
+
+              /* Remove spaces to normalize to flickr tags */
+              keywords[i] = remove_spaces_from_keyword (keyword);
+              g_free (keyword);
+            }
+
+          result = g_strjoinv (" ", keywords);
+          g_strfreev (keywords);
+        }
+    }
+
+  g_free (keywords_start);
+
+  return result;
+}
+
 static void
 _frogr_picture_loader_dispose (GObject* object)
 {
@@ -523,6 +637,7 @@ frogr_picture_loader_init (FrogrPictureLoader *self)
 
   /* Initialize values from frogr configuration */
   priv->keep_file_extensions = frogr_config_get_keep_file_extensions (config);
+  priv->import_tags = frogr_config_get_import_tags_from_metadata (config);
   priv->public_visibility = frogr_config_get_default_public (config);
   priv->family_visibility = frogr_config_get_default_family (config);
   priv->friend_visibility = frogr_config_get_default_friend (config);
diff --git a/src/frogr-settings-dialog.c b/src/frogr-settings-dialog.c
index 767ad0c..2077f8a 100644
--- a/src/frogr-settings-dialog.c
+++ b/src/frogr-settings-dialog.c
@@ -67,6 +67,7 @@ typedef struct _FrogrSettingsDialogPrivate {
 
   GtkWidget *disable_tags_autocompletion_cb;
   GtkWidget *keep_file_extensions_cb;
+  GtkWidget *import_tags_cb;
 
   gboolean public_visibility;
   gboolean family_visibility;
@@ -75,6 +76,7 @@ typedef struct _FrogrSettingsDialogPrivate {
   gboolean send_geolocation_data;
   gboolean disable_tags_autocompletion;
   gboolean keep_file_extensions;
+  gboolean import_tags;
   FspLicense license;
   FspSafetyLevel safety_level;
   FspContentType content_type;
@@ -516,6 +518,9 @@ _add_misc_page (FrogrSettingsDialog *self, GtkNotebook *notebook)
   _add_toggleable_item (self, GTK_BOX (box), NULL, FALSE,
                         _("_Keep File Extensions in Titles when Loading Pictures"),
                         &priv->keep_file_extensions_cb);
+  _add_toggleable_item (self, GTK_BOX (box), NULL, FALSE,
+                        _("Don't _Import Tags from Pictures Metadata"),
+                        &priv->import_tags_cb);
 
   gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 0);
 
@@ -540,6 +545,7 @@ _fill_dialog_with_data (FrogrSettingsDialog *self)
   priv->safety_level = frogr_config_get_default_safety_level (priv->config);
   priv->disable_tags_autocompletion = !frogr_config_get_tags_autocompletion (priv->config);
   priv->keep_file_extensions = frogr_config_get_keep_file_extensions (priv->config);
+  priv->import_tags = frogr_config_get_import_tags_from_metadata (priv->config);
   priv->use_proxy = frogr_config_get_use_proxy (priv->config);
 #ifdef HAVE_LIBSOUP_GNOME
   priv->use_gnome_proxy = frogr_config_get_use_gnome_proxy (priv->config);
@@ -601,9 +607,10 @@ _fill_dialog_with_data (FrogrSettingsDialog *self)
 
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->disable_tags_autocompletion_cb),
                                 priv->disable_tags_autocompletion);
-
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->keep_file_extensions_cb),
                                 priv->keep_file_extensions);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->import_tags_cb),
+                                !priv->import_tags);
 
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->use_proxy_cb),
                                 priv->use_proxy);
@@ -647,6 +654,7 @@ _save_data (FrogrSettingsDialog *self)
 
   frogr_config_set_tags_autocompletion (priv->config, !priv->disable_tags_autocompletion);
   frogr_config_set_keep_file_extensions (priv->config, priv->keep_file_extensions);
+  frogr_config_set_import_tags_from_metadata (priv->config, priv->import_tags);
 
   frogr_config_set_use_proxy (priv->config, priv->use_proxy);
 #ifdef HAVE_LIBSOUP_GNOME
@@ -803,6 +811,12 @@ _on_button_toggled (GtkToggleButton *button, gpointer data)
       DEBUG ("Keep file extensions in title set to %s", active ? "TRUE" : "FALSE");
     }
 
+  if (GTK_WIDGET (button) == priv->import_tags_cb)
+    {
+      priv->import_tags = !active;
+      DEBUG ("Don't import tags from pictures metadata set to %s", active ? "TRUE" : "FALSE");
+    }
+
   if (GTK_WIDGET (button) == priv->use_proxy_cb)
     {
       priv->use_proxy = active;
@@ -956,6 +970,7 @@ frogr_settings_dialog_init (FrogrSettingsDialog *self)
   priv->restricted_rb = NULL;
   priv->disable_tags_autocompletion_cb = NULL;
   priv->keep_file_extensions_cb = NULL;
+  priv->import_tags_cb = NULL;
   priv->use_proxy_cb = NULL;
   priv->use_gnome_proxy_cb = NULL;
   priv->proxy_host_label = NULL;
@@ -976,6 +991,7 @@ frogr_settings_dialog_init (FrogrSettingsDialog *self)
   priv->content_type = FSP_CONTENT_TYPE_NONE;
   priv->disable_tags_autocompletion = FALSE;
   priv->keep_file_extensions = FALSE;
+  priv->import_tags = FALSE;
   priv->use_proxy = FALSE;
   priv->use_gnome_proxy = FALSE;
   priv->proxy_host = NULL;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]