[gimp/metadata-wip-rebased] Metadata WIP



commit 217f45a6626b6d27b27b463ab46508c76bc00735
Author: Michael Natterer <mitch gimp org>
Date:   Sat Oct 19 18:38:01 2013 +0200

    Metadata WIP

 app/Makefile.am                        |    1 +
 app/core/Makefile.am                   |    3 +
 app/core/gimpimage-metadata.c          |   69 +++
 app/core/gimpimage-metadata.h          |   27 +
 app/core/gimpimage-private.h           |    2 +
 app/core/gimpimage.c                   |   63 +++-
 app/pdb/image-cmds.c                   |  122 +++++
 app/pdb/internal-procs.c               |    2 +-
 app/xcf/xcf-load.c                     |   23 +
 app/xcf/xcf-save.c                     |   43 ++-
 configure.ac                           |   30 +-
 libgimp/Makefile.am                    |    5 +
 libgimp/gimp.def                       |    2 +
 libgimp/gimpimage.c                    |   60 +++
 libgimp/gimpimage.h                    |    4 +
 libgimp/gimpimage_pdb.c                |   62 +++
 libgimp/gimpimage_pdb.h                |    3 +
 libgimp/gimpmetadata.c                 |  612 +++++++++++++++++++++
 libgimp/gimpmetadata.h                 |   48 ++
 libgimp/gimpui.def                     |    3 +
 libgimp/gimpui.h                       |    1 +
 libgimpbase/Makefile.am                |    7 +-
 libgimpbase/gimpbase.def               |   11 +-
 libgimpbase/gimpbase.h                 |    1 +
 libgimpbase/gimpbasetypes.h            |    2 +
 libgimpbase/gimpmetadata.c             |  653 +++++++++++++++++++++++
 libgimpbase/gimpmetadata.h             |   72 +++
 plug-ins/Makefile.am                   |    6 -
 plug-ins/common/.gitignore             |    2 +
 plug-ins/common/Makefile.am            |   21 +
 plug-ins/common/file-png.c             |  103 ++++-
 plug-ins/common/file-tiff-load.c       |    7 +
 plug-ins/common/file-tiff-save.c       |  154 ++++--
 plug-ins/common/gimprc.common          |    1 +
 plug-ins/common/metadata.c             |  382 ++++++++++++++
 plug-ins/common/plugin-defs.pl         |    1 +
 plug-ins/file-jpeg/Makefile.am         |   25 +-
 plug-ins/file-jpeg/gimpexif.c          |  160 ------
 plug-ins/file-jpeg/gimpexif.h          |   39 --
 plug-ins/file-jpeg/jpeg-exif.c         |  458 ----------------
 plug-ins/file-jpeg/jpeg-exif.h         |   41 --
 plug-ins/file-jpeg/jpeg-load.c         |  219 ++-------
 plug-ins/file-jpeg/jpeg-load.h         |    7 +-
 plug-ins/file-jpeg/jpeg-save.c         |  402 ++-------------
 plug-ins/file-jpeg/jpeg-save.h         |    1 +
 plug-ins/file-jpeg/jpeg-settings.c     |    4 -
 plug-ins/file-jpeg/jpeg.c              |   74 ++--
 plug-ins/file-psd/psd-image-res-load.c |  131 -----
 plug-ins/ui/Makefile.am                |    4 +-
 plug-ins/ui/plug-in-file-png.ui        |  106 ++++-
 plug-ins/ui/plug-in-file-tiff.ui       |  404 ++++++++++++++
 plug-ins/ui/plug-in-metadata.ui        |  908 ++++++++++++++++++++++++++++++++
 po-libgimp/POTFILES.in                 |    1 +
 po-plug-ins/POTFILES.in                |    3 +
 tools/pdbgen/pdb/image.pdb             |   59 ++-
 55 files changed, 4100 insertions(+), 1554 deletions(-)
---
diff --git a/app/Makefile.am b/app/Makefile.am
index e559433..80eaac0 100644
--- a/app/Makefile.am
+++ b/app/Makefile.am
@@ -177,6 +177,7 @@ gimpconsoleldadd = \
        $(CAIRO_LIBS)                   \
        $(GEGL_LIBS)                    \
        $(GLIB_LIBS)                    \
+       $(GEXIV2_LIBS)                  \
        $(INTLLIBS)                     \
        $(RT_LIBS)
 
diff --git a/app/core/Makefile.am b/app/core/Makefile.am
index 764a798..cd94de7 100644
--- a/app/core/Makefile.am
+++ b/app/core/Makefile.am
@@ -10,6 +10,7 @@ AM_CPPFLAGS = \
        $(CAIRO_CFLAGS)                                 \
        $(GEGL_CFLAGS)                                  \
        $(GDK_PIXBUF_CFLAGS)                            \
+       $(GEXIV2_CFLAGS)                                \
        -I$(includedir)
 
 noinst_LIBRARIES = libappcore.a
@@ -219,6 +220,8 @@ libappcore_a_sources = \
        gimpimage-item-list.h                   \
        gimpimage-merge.c                       \
        gimpimage-merge.h                       \
+       gimpimage-metadata.c                    \
+       gimpimage-metadata.h                    \
        gimpimage-new.c                         \
        gimpimage-new.h                         \
        gimpimage-pick-color.c                  \
diff --git a/app/core/gimpimage-metadata.c b/app/core/gimpimage-metadata.c
new file mode 100644
index 0000000..24c6a56
--- /dev/null
+++ b/app/core/gimpimage-metadata.c
@@ -0,0 +1,69 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpimage-metadata.h"
+#include "gimpimage-private.h"
+
+
+/* public functions */
+
+
+GimpMetadata *
+gimp_image_get_metadata (GimpImage *image)
+{
+  GimpImagePrivate *private;
+
+  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
+
+  return private->metadata;
+}
+
+void
+gimp_image_set_metadata (GimpImage    *image,
+                         GimpMetadata *metadata)
+{
+  GimpImagePrivate *private;
+
+  g_return_if_fail (GIMP_IS_IMAGE (image));
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
+
+  if (metadata != private->metadata)
+    {
+      if (private->metadata)
+        g_object_unref (private->metadata);
+
+      private->metadata = metadata;
+
+      if (private->metadata)
+        g_object_ref (private->metadata);
+
+      g_object_notify (G_OBJECT (image), "metadata");
+    }
+}
diff --git a/app/core/gimpimage-metadata.h b/app/core/gimpimage-metadata.h
new file mode 100644
index 0000000..feccd95
--- /dev/null
+++ b/app/core/gimpimage-metadata.h
@@ -0,0 +1,27 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_METADATA_H__
+#define __GIMP_IMAGE_METADATA_H__
+
+
+GimpMetadata * gimp_image_get_metadata (GimpImage    *image);
+void           gimp_image_set_metadata (GimpImage    *image,
+                                        GimpMetadata *metadata);
+
+
+#endif /* __GIMP_IMAGE_METADATA_H__ */
diff --git a/app/core/gimpimage-private.h b/app/core/gimpimage-private.h
index 5dee180..a4ae2f1 100644
--- a/app/core/gimpimage-private.h
+++ b/app/core/gimpimage-private.h
@@ -55,6 +55,8 @@ struct _GimpImagePrivate
   const Babl        *babl_palette_rgb;      /*  palette's RGB Babl format    */
   const Babl        *babl_palette_rgba;     /*  palette's RGBA Babl format   */
 
+  GimpMetadata      *metadata;              /*  image's metadata             */
+
   gint               dirty;                 /*  dirty flag -- # of ops       */
   guint              dirty_time;            /*  time when image became dirty */
   gint               export_dirty;          /*  'dirty' but for export       */
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
index 191239c..c20552a 100644
--- a/app/core/gimpimage.c
+++ b/app/core/gimpimage.c
@@ -23,6 +23,7 @@
 #include <cairo.h>
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <gegl.h>
+#include <gexiv2/gexiv2.h>
 
 #include "libgimpcolor/gimpcolor.h"
 #include "libgimpmath/gimpmath.h"
@@ -47,6 +48,7 @@
 #include "gimpimage.h"
 #include "gimpimage-colormap.h"
 #include "gimpimage-guides.h"
+#include "gimpimage-metadata.h"
 #include "gimpimage-sample-points.h"
 #include "gimpimage-preview.h"
 #include "gimpimage-private.h"
@@ -130,6 +132,7 @@ enum
   PROP_HEIGHT,
   PROP_BASE_TYPE,
   PROP_PRECISION,
+  PROP_METADATA,
   PROP_BUFFER
 };
 
@@ -610,6 +613,12 @@ gimp_image_class_init (GimpImageClass *klass)
                                                       GIMP_PARAM_READWRITE |
                                                       G_PARAM_CONSTRUCT));
 
+  g_object_class_install_property (object_class, PROP_METADATA,
+                                   g_param_spec_object ("metadata", NULL, NULL,
+                                                        GEXIV2_TYPE_METADATA,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
   g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
 
   g_type_class_add_private (klass, sizeof (GimpImagePrivate));
@@ -667,6 +676,8 @@ gimp_image_init (GimpImage *image)
   private->n_colors            = 0;
   private->palette             = NULL;
 
+  private->metadata            = NULL;
+
   private->dirty               = 1;
   private->dirty_time          = 0;
   private->undo_freeze_count   = 0;
@@ -837,6 +848,9 @@ gimp_image_set_property (GObject      *object,
     case PROP_PRECISION:
       private->precision = g_value_get_enum (value);
       break;
+    case PROP_METADATA:
+      gimp_image_set_metadata (image, g_value_get_object (value));
+      break;
     case PROP_BUFFER:
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -873,6 +887,9 @@ gimp_image_get_property (GObject    *object,
     case PROP_PRECISION:
       g_value_set_enum (value, private->precision);
       break;
+    case PROP_METADATA:
+      g_value_set_object (value, gimp_image_get_metadata (image));
+      break;
     case PROP_BUFFER:
       g_value_set_object (value, gimp_image_get_buffer (GIMP_PICKABLE (image)));
       break;
@@ -948,6 +965,12 @@ gimp_image_finalize (GObject *object)
   if (private->colormap)
     gimp_image_colormap_free (image);
 
+  if (private->metadata)
+    {
+      g_object_unref (private->metadata);
+      private->metadata = NULL;
+    }
+
   if (private->layers)
     {
       g_object_unref (private->layers);
@@ -1128,9 +1151,10 @@ gimp_image_get_size (GimpViewable *viewable,
 static void
 gimp_image_size_changed (GimpViewable *viewable)
 {
-  GimpImage *image = GIMP_IMAGE (viewable);
-  GList     *all_items;
-  GList     *list;
+  GimpImage    *image = GIMP_IMAGE (viewable);
+  GimpMetadata *metadata;
+  GList        *all_items;
+  GList        *list;
 
   if (GIMP_VIEWABLE_CLASS (parent_class)->size_changed)
     GIMP_VIEWABLE_CLASS (parent_class)->size_changed (viewable);
@@ -1155,6 +1179,12 @@ gimp_image_size_changed (GimpViewable *viewable)
 
   gimp_viewable_size_changed (GIMP_VIEWABLE (gimp_image_get_mask (image)));
 
+  metadata = gimp_image_get_metadata (image);
+  if (metadata)
+    gimp_metadata_set_pixel_size (metadata,
+                                  gimp_image_get_width  (image),
+                                  gimp_image_get_height (image));
+
   gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
 }
 
@@ -1181,6 +1211,33 @@ gimp_image_real_mode_changed (GimpImage *image)
 static void
 gimp_image_real_precision_changed (GimpImage *image)
 {
+  GimpMetadata *metadata;
+
+  metadata = gimp_image_get_metadata (image);
+  if (metadata)
+    {
+      gint bps = 8;
+
+      switch (gimp_image_get_component_type (image))
+        {
+        case GIMP_COMPONENT_TYPE_U8:
+          bps = 8;
+          break;
+
+        case GIMP_COMPONENT_TYPE_U16:
+        case GIMP_COMPONENT_TYPE_HALF:
+          bps = 16;
+          break;
+
+        case GIMP_COMPONENT_TYPE_U32:
+        case GIMP_COMPONENT_TYPE_FLOAT:
+          bps = 32;
+          break;
+        }
+
+      gimp_metadata_set_bits_per_sample (metadata, bps);
+    }
+
   gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
 }
 
diff --git a/app/pdb/image-cmds.c b/app/pdb/image-cmds.c
index 78c9a3e..f2e822e 100644
--- a/app/pdb/image-cmds.c
+++ b/app/pdb/image-cmds.c
@@ -42,6 +42,7 @@
 #include "core/gimpimage-duplicate.h"
 #include "core/gimpimage-flip.h"
 #include "core/gimpimage-merge.h"
+#include "core/gimpimage-metadata.h"
 #include "core/gimpimage-pick-color.h"
 #include "core/gimpimage-pick-layer.h"
 #include "core/gimpimage-resize.h"
@@ -1707,6 +1708,67 @@ image_set_colormap_invoker (GimpProcedure         *procedure,
 }
 
 static GimpValueArray *
+image_get_metadata_invoker (GimpProcedure         *procedure,
+                            Gimp                  *gimp,
+                            GimpContext           *context,
+                            GimpProgress          *progress,
+                            const GimpValueArray  *args,
+                            GError               **error)
+{
+  gboolean success = TRUE;
+  GimpValueArray *return_vals;
+  GimpImage *image;
+  gchar *metadata_string = NULL;
+
+  image = gimp_value_get_image (gimp_value_array_index (args, 0), gimp);
+
+  if (success)
+    {
+      GimpMetadata *metadata = gimp_image_get_metadata (image);
+
+      if (metadata)
+        metadata_string = gimp_metadata_serialize (metadata);
+    }
+
+  return_vals = gimp_procedure_get_return_values (procedure, success,
+                                                  error ? *error : NULL);
+
+  if (success)
+    g_value_take_string (gimp_value_array_index (return_vals, 1), metadata_string);
+
+  return return_vals;
+}
+
+static GimpValueArray *
+image_set_metadata_invoker (GimpProcedure         *procedure,
+                            Gimp                  *gimp,
+                            GimpContext           *context,
+                            GimpProgress          *progress,
+                            const GimpValueArray  *args,
+                            GError               **error)
+{
+  gboolean success = TRUE;
+  GimpImage *image;
+  const gchar *metadata_string;
+
+  image = gimp_value_get_image (gimp_value_array_index (args, 0), gimp);
+  metadata_string = g_value_get_string (gimp_value_array_index (args, 1));
+
+  if (success)
+    {
+      GimpMetadata *metadata = gimp_metadata_deserialize (metadata_string);
+
+      gimp_image_set_metadata (image, metadata);
+
+      if (metadata)
+        g_object_unref (metadata);
+    }
+
+  return gimp_procedure_get_return_values (procedure, success,
+                                           error ? *error : NULL);
+}
+
+static GimpValueArray *
 image_clean_all_invoker (GimpProcedure         *procedure,
                          Gimp                  *gimp,
                          GimpContext           *context,
@@ -4554,6 +4616,66 @@ register_image_procs (GimpPDB *pdb)
   g_object_unref (procedure);
 
   /*
+   * gimp-image-get-metadata
+   */
+  procedure = gimp_procedure_new (image_get_metadata_invoker);
+  gimp_object_set_static_name (GIMP_OBJECT (procedure),
+                               "gimp-image-get-metadata");
+  gimp_procedure_set_static_strings (procedure,
+                                     "gimp-image-get-metadata",
+                                     "Returns the image's metadata.",
+                                     "Returns exif/iptc/xmp metadata from the image.",
+                                     "Spencer Kimball & Peter Mattis",
+                                     "Spencer Kimball & Peter Mattis",
+                                     "1995-1996",
+                                     NULL);
+  gimp_procedure_add_argument (procedure,
+                               gimp_param_spec_image_id ("image",
+                                                         "image",
+                                                         "The image",
+                                                         pdb->gimp, FALSE,
+                                                         GIMP_PARAM_READWRITE));
+  gimp_procedure_add_return_value (procedure,
+                                   gimp_param_spec_string ("metadata-string",
+                                                           "metadata string",
+                                                           "The exif/ptc/xmp metadata as a string",
+                                                           FALSE, FALSE, FALSE,
+                                                           NULL,
+                                                           GIMP_PARAM_READWRITE));
+  gimp_pdb_register_procedure (pdb, procedure);
+  g_object_unref (procedure);
+
+  /*
+   * gimp-image-set-metadata
+   */
+  procedure = gimp_procedure_new (image_set_metadata_invoker);
+  gimp_object_set_static_name (GIMP_OBJECT (procedure),
+                               "gimp-image-set-metadata");
+  gimp_procedure_set_static_strings (procedure,
+                                     "gimp-image-set-metadata",
+                                     "Set the image's metadata.",
+                                     "Sets exif/iptc/xmp metadata on the image.",
+                                     "Spencer Kimball & Peter Mattis",
+                                     "Spencer Kimball & Peter Mattis",
+                                     "1995-1996",
+                                     NULL);
+  gimp_procedure_add_argument (procedure,
+                               gimp_param_spec_image_id ("image",
+                                                         "image",
+                                                         "The image",
+                                                         pdb->gimp, FALSE,
+                                                         GIMP_PARAM_READWRITE));
+  gimp_procedure_add_argument (procedure,
+                               gimp_param_spec_string ("metadata-string",
+                                                       "metadata string",
+                                                       "The exif/ptc/xmp metadata as a string",
+                                                       FALSE, FALSE, FALSE,
+                                                       NULL,
+                                                       GIMP_PARAM_READWRITE));
+  gimp_pdb_register_procedure (pdb, procedure);
+  g_object_unref (procedure);
+
+  /*
    * gimp-image-clean-all
    */
   procedure = gimp_procedure_new (image_clean_all_invoker);
diff --git a/app/pdb/internal-procs.c b/app/pdb/internal-procs.c
index 790cb86..5dada82 100644
--- a/app/pdb/internal-procs.c
+++ b/app/pdb/internal-procs.c
@@ -28,7 +28,7 @@
 #include "internal-procs.h"
 
 
-/* 701 procedures registered total */
+/* 703 procedures registered total */
 
 void
 internal_procs_init (GimpPDB *pdb)
diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c
index f3c89ab..bfa77dc 100644
--- a/app/xcf/xcf-load.c
+++ b/app/xcf/xcf-load.c
@@ -41,6 +41,7 @@
 #include "core/gimpimage-colormap.h"
 #include "core/gimpimage-grid.h"
 #include "core/gimpimage-guides.h"
+#include "core/gimpimage-metadata.h"
 #include "core/gimpimage-private.h"
 #include "core/gimpimage-sample-points.h"
 #include "core/gimpimage-undo.h"
@@ -208,6 +209,28 @@ xcf_load_image (Gimp     *gimp,
         }
     }
 
+  /* check for a metadata parasite */
+  parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
+                                       "gimp-image-metadata");
+  if (parasite)
+    {
+      GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+      GimpMetadata     *metadata;
+      const gchar      *meta_string;
+
+      meta_string = (gchar *) gimp_parasite_data (parasite);
+      metadata = gimp_metadata_deserialize (meta_string);
+
+      if (metadata)
+        {
+          gimp_image_set_metadata (image, metadata);
+          g_object_unref (metadata);
+        }
+
+      gimp_parasite_list_remove (private->parasites,
+                                 gimp_parasite_name (parasite));
+    }
+
   xcf_progress_update (info);
 
   while (TRUE)
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c
index d931f81..ff28aa4 100644
--- a/app/xcf/xcf-save.c
+++ b/app/xcf/xcf-save.c
@@ -41,6 +41,7 @@
 #include "core/gimpimage-colormap.h"
 #include "core/gimpimage-grid.h"
 #include "core/gimpimage-guides.h"
+#include "core/gimpimage-metadata.h"
 #include "core/gimpimage-private.h"
 #include "core/gimpimage-sample-points.h"
 #include "core/gimplayer.h"
@@ -390,9 +391,10 @@ xcf_save_image_props (XcfInfo    *info,
                       GimpImage  *image,
                       GError    **error)
 {
-  GimpImagePrivate *private  = GIMP_IMAGE_GET_PRIVATE (image);
-  GimpParasite     *parasite = NULL;
-  GimpUnit          unit     = gimp_image_get_unit (image);
+  GimpImagePrivate *private       = GIMP_IMAGE_GET_PRIVATE (image);
+  GimpParasite     *grid_parasite = NULL;
+  GimpParasite     *meta_parasite = NULL;
+  GimpUnit          unit          = gimp_image_get_unit (image);
   gdouble           xres;
   gdouble           yres;
 
@@ -440,8 +442,26 @@ xcf_save_image_props (XcfInfo    *info,
     {
       GimpGrid *grid = gimp_image_get_grid (image);
 
-      parasite = gimp_grid_to_parasite (grid);
-      gimp_parasite_list_add (private->parasites, parasite);
+      grid_parasite = gimp_grid_to_parasite (grid);
+      gimp_parasite_list_add (private->parasites, grid_parasite);
+    }
+
+  if (gimp_image_get_metadata (image))
+    {
+      GimpMetadata *metadata = gimp_image_get_metadata (image);
+      gchar        *meta_string;
+
+      meta_string = gimp_metadata_serialize (metadata);
+
+      if (meta_string)
+        {
+          meta_parasite = gimp_parasite_new ("gimp-image-metadata",
+                                             GIMP_PARASITE_PERSISTENT,
+                                             strlen (meta_string) + 1,
+                                             meta_string);
+          gimp_parasite_list_add (private->parasites, meta_parasite);
+          g_free (meta_string);
+        }
     }
 
   if (gimp_parasite_list_length (private->parasites) > 0)
@@ -450,11 +470,18 @@ xcf_save_image_props (XcfInfo    *info,
                                       private->parasites));
     }
 
-  if (parasite)
+  if (grid_parasite)
+    {
+      gimp_parasite_list_remove (private->parasites,
+                                 gimp_parasite_name (grid_parasite));
+      gimp_parasite_free (grid_parasite);
+    }
+
+  if (meta_parasite)
     {
       gimp_parasite_list_remove (private->parasites,
-                                 gimp_parasite_name (parasite));
-      gimp_parasite_free (parasite);
+                                 gimp_parasite_name (meta_parasite));
+      gimp_parasite_free (meta_parasite);
     }
 
   xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
diff --git a/configure.ac b/configure.ac
index a56aa9f..d6d2e79 100644
--- a/configure.ac
+++ b/configure.ac
@@ -59,7 +59,7 @@ m4_define([pygtk_required_version], [2.10.4])
 m4_define([poppler_required_version], [0.12.4])
 m4_define([libcurl_required_version], [7.15.1])
 m4_define([libgudev_required_version], [167])
-m4_define([exif_required_version], [0.6.15])
+m4_define([gexiv2_required_version], [0.6.1])
 m4_define([lcms_required_version], [2.2])
 m4_define([libpng_required_version], [1.2.37])
 m4_define([liblzma_required_version], [5.0.0])
@@ -123,11 +123,13 @@ GDK_PIXBUF_REQUIRED_VERSION=gdk_pixbuf_required_version
 GTK_REQUIRED_VERSION=gtk_required_version
 CAIRO_REQUIRED_VERSION=cairo_required_version
 GEGL_REQUIRED_VERSION=gegl_required_version
+GEXIV2_REQUIRED_VERSION=gexiv2_required_version
 AC_SUBST(GLIB_REQUIRED_VERSION)
 AC_SUBST(GDK_PIXBUF_REQUIRED_VERSION)
 AC_SUBST(GTK_REQUIRED_VERSION)
 AC_SUBST(CAIRO_REQUIRED_VERSION)
 AC_SUBST(GEGL_REQUIRED_VERSION)
+AC_SUBST(GEXIV2_REQUIRED_VERSION)
 
 # The symbol GIMP_UNSTABLE is defined above for substitution in
 # Makefiles and conditionally defined here as a preprocessor symbol
@@ -607,6 +609,7 @@ if test "x$FREETYPE_CONFIG" != "xno" ; then
 fi
 AC_SUBST(FREETYPE_LIBS)
 
+PKG_CHECK_MODULES(GEXIV2, gexiv2 >= gexiv2_required_version)
 
 ##########################################
 # Check for some special functions we need
@@ -1327,28 +1330,6 @@ AC_SUBST(MNG_LIBS)
 AC_SUBST(MNG_CFLAGS)
 
 
-############################################################
-# libexif: Library to allow exif tags to be read from, and
-# saved to, jpeg files. Currently, this permits exif data to
-# avoid destruction, but no data modification is performed.
-############################################################
-
-AC_ARG_WITH(libexif,  [  --without-libexif       build without EXIF support])
-
-have_libexif=no
-if test "x$with_libexif" != xno && test -z "$EXIF_LIBS" && test -n "$JPEG_LIBS"; then
-  have_libexif=yes
-  PKG_CHECK_MODULES(EXIF, libexif >= exif_required_version,
-    AC_DEFINE(HAVE_LIBEXIF, 1, [Define to 1 if libexif is available]),
-    have_libexif="no (libexif not found or too old)")
-fi
-
-AC_SUBST(EXIF_CFLAGS)
-AC_SUBST(EXIF_LIBS)
-
-AM_CONDITIONAL(HAVE_LIBEXIF, test "x$have_libexif" = xyes)
-
-
 #################
 # Check for libaa
 #################
@@ -2406,9 +2387,6 @@ Optional Plug-Ins:
   X11 Mouse Cursor:    $have_xmc
   XPM:                 $have_libxpm
 
-Plug-In Features:
-  EXIF support:        $have_libexif
-
 Optional Modules:
   ALSA (MIDI Input):   $have_alsa
   Linux Input:         $have_linux_input (GUdev support: $have_libgudev)
diff --git a/libgimp/Makefile.am b/libgimp/Makefile.am
index bff3883..7de00fb 100644
--- a/libgimp/Makefile.am
+++ b/libgimp/Makefile.am
@@ -68,6 +68,7 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)                 \
        $(GTK_CFLAGS)                   \
        $(GEGL_CFLAGS)                  \
+       $(GEXIV2_CFLAGS)                \
        -I$(includedir)                 \
        $(xobjective_c)
 
@@ -280,6 +281,8 @@ libgimpui_sources = \
        gimpitemcombobox.h              \
        gimpmenu.c                      \
        gimpmenu.h                      \
+       gimpmetadata.c                  \
+       gimpmetadats.h                  \
        gimppalettemenu.c               \
        gimppalettemenu.h               \
        gimppaletteselectbutton.c       \
@@ -354,6 +357,7 @@ gimpinclude_HEADERS = \
        gimpimagecombobox.h             \
        gimpitemcombobox.h              \
        gimpmenu.h                      \
+       gimpmetadata.h                  \
        gimppalettemenu.h               \
        gimppaletteselectbutton.h       \
        gimppatternmenu.h               \
@@ -399,6 +403,7 @@ libgimpui_ GIMP_API_VERSION@_la_LIBADD = \
        $(libgimpbase)          \
        $(libgimpmodule)        \
        $(GTK_LIBS)             \
+       $(GEXIV2_LIBS)          \
        $(RT_LIBS)
 
 libgimpui_ GIMP_API_VERSION@_la_DEPENDENCIES = \
diff --git a/libgimp/gimp.def b/libgimp/gimp.def
index fcf3fa8..8c1fd0c 100644
--- a/libgimp/gimp.def
+++ b/libgimp/gimp.def
@@ -403,6 +403,7 @@ EXPORTS
        gimp_image_get_layer_by_tattoo
        gimp_image_get_layer_position
        gimp_image_get_layers
+       gimp_image_get_metadata
        gimp_image_get_name
        gimp_image_get_parasite
        gimp_image_get_parasite_list
@@ -484,6 +485,7 @@ EXPORTS
        gimp_image_set_component_active
        gimp_image_set_component_visible
        gimp_image_set_filename
+       gimp_image_set_metadata
        gimp_image_set_resolution
        gimp_image_set_tattoo_state
        gimp_image_set_unit
diff --git a/libgimp/gimpimage.c b/libgimp/gimpimage.c
index f6ef79d..2c324ad 100644
--- a/libgimp/gimpimage.c
+++ b/libgimp/gimpimage.c
@@ -101,6 +101,66 @@ gimp_image_get_thumbnail_data (gint32  image_ID,
 }
 
 /**
+ * gimp_image_get_metadata:
+ * @image_ID: The image.
+ *
+ * Returns the image's metadata.
+ *
+ * Returns exif/iptc/xmp metadata from the image.
+ *
+ * Returns: The exif/ptc/xmp metadata, or %NULL if there is none.
+ *
+ * Since: GIMP 2.10
+ **/
+GimpMetadata *
+gimp_image_get_metadata (gint32 image_ID)
+{
+  GimpMetadata *metadata = NULL;
+  gchar        *metadata_string;
+
+  metadata_string = _gimp_image_get_metadata (image_ID);
+  if (metadata_string)
+    {
+      metadata = gimp_metadata_deserialize (metadata_string);
+      g_free (metadata_string);
+    }
+
+  return metadata;
+}
+
+/**
+ * gimp_image_set_metadata:
+ * @image_ID: The image.
+ * @metadata: The exif/ptc/xmp metadata.
+ *
+ * Set the image's metadata.
+ *
+ * Sets exif/iptc/xmp metadata on the image, or deletes it if
+ * @metadata is %NULL.
+ *
+ * Returns: TRUE on success.
+ *
+ * Since: GIMP 2.10
+ **/
+gboolean
+gimp_image_set_metadata (gint32        image_ID,
+                         GimpMetadata *metadata)
+{
+  gchar    *metadata_string = NULL;
+  gboolean  success;
+
+  if (metadata)
+    metadata_string = gimp_metadata_serialize (metadata);
+
+  success = _gimp_image_set_metadata (image_ID, metadata_string);
+
+  if (metadata_string)
+    g_free (metadata_string);
+
+  return success;
+}
+
+/**
  * gimp_image_get_cmap:
  * @image_ID:   The image.
  * @num_colors: Number of colors in the colormap array.
diff --git a/libgimp/gimpimage.h b/libgimp/gimpimage.h
index f25b9d7..376b033 100644
--- a/libgimp/gimpimage.h
+++ b/libgimp/gimpimage.h
@@ -41,6 +41,10 @@ guchar       * gimp_image_get_thumbnail_data      (gint32              image_ID,
                                                    gint               *height,
                                                    gint               *bpp);
 
+GimpMetadata * gimp_image_get_metadata            (gint32              image_ID);
+gboolean       gimp_image_set_metadata            (gint32              image_ID,
+                                                   GimpMetadata       *metadata);
+
 GIMP_DEPRECATED_FOR(gimp_image_get_colormap)
 guchar       * gimp_image_get_cmap                (gint32              image_ID,
                                                    gint               *num_colors);
diff --git a/libgimp/gimpimage_pdb.c b/libgimp/gimpimage_pdb.c
index ace2f69..76d9c15 100644
--- a/libgimp/gimpimage_pdb.c
+++ b/libgimp/gimpimage_pdb.c
@@ -1800,6 +1800,68 @@ _gimp_image_set_colormap (gint32        image_ID,
 }
 
 /**
+ * _gimp_image_get_metadata:
+ * @image_ID: The image.
+ *
+ * Returns the image's metadata.
+ *
+ * Returns exif/iptc/xmp metadata from the image.
+ *
+ * Returns: The exif/ptc/xmp metadata as a string.
+ **/
+gchar *
+_gimp_image_get_metadata (gint32 image_ID)
+{
+  GimpParam *return_vals;
+  gint nreturn_vals;
+  gchar *metadata_string = NULL;
+
+  return_vals = gimp_run_procedure ("gimp-image-get-metadata",
+                                    &nreturn_vals,
+                                    GIMP_PDB_IMAGE, image_ID,
+                                    GIMP_PDB_END);
+
+  if (return_vals[0].data.d_status == GIMP_PDB_SUCCESS)
+    metadata_string = g_strdup (return_vals[1].data.d_string);
+
+  gimp_destroy_params (return_vals, nreturn_vals);
+
+  return metadata_string;
+}
+
+/**
+ * _gimp_image_set_metadata:
+ * @image_ID: The image.
+ * @metadata_string: The exif/ptc/xmp metadata as a string.
+ *
+ * Set the image's metadata.
+ *
+ * Sets exif/iptc/xmp metadata on the image.
+ *
+ * Returns: TRUE on success.
+ **/
+gboolean
+_gimp_image_set_metadata (gint32       image_ID,
+                          const gchar *metadata_string)
+{
+  GimpParam *return_vals;
+  gint nreturn_vals;
+  gboolean success = TRUE;
+
+  return_vals = gimp_run_procedure ("gimp-image-set-metadata",
+                                    &nreturn_vals,
+                                    GIMP_PDB_IMAGE, image_ID,
+                                    GIMP_PDB_STRING, metadata_string,
+                                    GIMP_PDB_END);
+
+  success = return_vals[0].data.d_status == GIMP_PDB_SUCCESS;
+
+  gimp_destroy_params (return_vals, nreturn_vals);
+
+  return success;
+}
+
+/**
  * gimp_image_clean_all:
  * @image_ID: The image.
  *
diff --git a/libgimp/gimpimage_pdb.h b/libgimp/gimpimage_pdb.h
index ae5015e..e5a2466 100644
--- a/libgimp/gimpimage_pdb.h
+++ b/libgimp/gimpimage_pdb.h
@@ -148,6 +148,9 @@ G_GNUC_INTERNAL guint8*  _gimp_image_get_colormap            (gint32
 G_GNUC_INTERNAL gboolean _gimp_image_set_colormap            (gint32                  image_ID,
                                                               gint                    num_bytes,
                                                               const guint8           *colormap);
+G_GNUC_INTERNAL gchar*   _gimp_image_get_metadata            (gint32                  image_ID);
+G_GNUC_INTERNAL gboolean _gimp_image_set_metadata            (gint32                  image_ID,
+                                                              const gchar            *metadata_string);
 gboolean                 gimp_image_clean_all                (gint32                  image_ID);
 gboolean                 gimp_image_is_dirty                 (gint32                  image_ID);
 G_GNUC_INTERNAL gboolean _gimp_image_thumbnail               (gint32                  image_ID,
diff --git a/libgimp/gimpmetadata.c b/libgimp/gimpmetadata.c
new file mode 100644
index 0000000..f1cc2b8
--- /dev/null
+++ b/libgimp/gimpmetadata.c
@@ -0,0 +1,612 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-2000 Peter Mattis and Spencer Kimball
+ *
+ * gimpmetadata.c
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gexiv2/gexiv2.h>
+
+#include "gimp.h"
+#include "gimpui.h"
+#include "gimpmetadata.h"
+
+#include "libgimp-intl.h"
+
+
+static void     gimp_image_metadata_rotate        (gint32        image_ID,
+                                                   gint          orientation);
+static void     gimp_image_metadata_rotate_query  (gint32        image_ID,
+                                                   const gchar  *mime_type,
+                                                   GimpMetadata *metadata,
+                                                   gboolean      interactive);
+static gboolean gimp_image_metadata_rotate_dialog (gint32        image_ID,
+                                                   const gchar  *parasite_name);
+
+
+/*  public functions  */
+
+void
+gimp_image_metadata_load (gint32       image_ID,
+                          const gchar *mime_type,
+                          GFile       *file,
+                          gboolean     interactive)
+{
+  GimpMetadata *metadata;
+
+  g_return_if_fail (image_ID > 0);
+  g_return_if_fail (mime_type != NULL);
+  g_return_if_fail (G_IS_FILE (file));
+
+  metadata = gimp_metadata_load_from_file (file, NULL);
+
+  if (metadata)
+    {
+      gchar    *comment;
+      gdouble   xres;
+      gdouble   yres;
+      GimpUnit  unit;
+
+      comment = gexiv2_metadata_get_tag_string (metadata,
+                                                "Exif.Photo.UserComment");
+      if (! comment)
+        comment = gexiv2_metadata_get_tag_string (metadata,
+                                                  "Exif.Image.ImageDescription");
+
+      if (comment)
+        {
+          GimpParasite *parasite;
+
+          parasite = gimp_parasite_new ("gimp-comment",
+                                        GIMP_PARASITE_PERSISTENT,
+                                        strlen (comment) + 1,
+                                        comment);
+          g_free (comment);
+
+          gimp_image_attach_parasite (image_ID, parasite);
+          gimp_parasite_free (parasite);
+        }
+
+      if (gimp_metadata_get_resolution (metadata, &xres, &yres, &unit))
+        {
+          gimp_image_set_resolution (image_ID, xres, yres);
+          gimp_image_set_unit (image_ID, unit);
+        }
+
+      gimp_image_metadata_rotate_query (image_ID, mime_type,
+                                        metadata, interactive);
+
+      gexiv2_metadata_erase_exif_thumbnail (metadata);
+    }
+}
+
+GimpMetadata *
+gimp_image_metadata_save_prepare (gint32       image_ID,
+                                  const gchar *mime_type)
+{
+  GimpMetadata *metadata;
+
+  g_return_val_if_fail (image_ID > 0, NULL);
+  g_return_val_if_fail (mime_type != NULL, NULL);
+
+  metadata = gimp_image_get_metadata (image_ID);
+
+  if (metadata)
+    {
+      GDateTime          *datetime;
+      const GimpParasite *comment_parasite;
+      const gchar        *comment = NULL;
+      gint                image_width;
+      gint                image_height;
+      gdouble             xres;
+      gdouble             yres;
+      gchar               buffer[32];
+
+      image_width  = gimp_image_width  (image_ID);
+      image_height = gimp_image_height (image_ID);
+
+      datetime = g_date_time_new_now_local ();
+
+      comment_parasite = gimp_image_get_parasite (image_ID, "gimp-comment");
+      if (comment_parasite)
+        comment = gimp_parasite_data (comment_parasite);
+
+      /* EXIF */
+
+      if (comment)
+        {
+          gexiv2_metadata_set_tag_string (metadata,
+                                          "Exif.Photo.UserComment",
+                                          comment);
+          gexiv2_metadata_set_tag_string (metadata,
+                                          "Exif.Image.ImageDescription",
+                                          comment);
+        }
+
+      g_snprintf (buffer, sizeof (buffer),
+                  "%d:%02d:%02d %02d:%02d:%02d",
+                  g_date_time_get_year (datetime),
+                  g_date_time_get_month (datetime),
+                  g_date_time_get_day_of_month (datetime),
+                  g_date_time_get_hour (datetime),
+                  g_date_time_get_minute (datetime),
+                  g_date_time_get_second (datetime));
+      gexiv2_metadata_set_tag_string (metadata,
+                                      "Exif.Image.DateTime",
+                                      buffer);
+
+      gexiv2_metadata_set_tag_string (metadata,
+                                      "Exif.Image.Software",
+                                      PACKAGE_STRING);
+
+      gimp_metadata_set_pixel_size (metadata,
+                                    image_width, image_height);
+
+      gimp_image_get_resolution (image_ID, &xres, &yres);
+      gimp_metadata_set_resolution (metadata, xres, yres,
+                                    gimp_image_get_unit (image_ID));
+
+      /* XMP */
+
+      gexiv2_metadata_set_tag_string (metadata,
+                                      "Xmp.dc.Format",
+                                      mime_type);
+
+      if (! g_strcmp0 (mime_type, "image/tiff"))
+        {
+          /* TIFF specific XMP data */
+
+          g_snprintf (buffer, sizeof (buffer), "%d", image_width);
+          gexiv2_metadata_set_tag_string (metadata,
+                                          "Xmp.tiff.ImageWidth",
+                                          buffer);
+
+          g_snprintf (buffer, sizeof (buffer), "%d", image_height);
+          gexiv2_metadata_set_tag_string (metadata,
+                                          "Xmp.tiff.ImageLength",
+                                          buffer);
+
+          g_snprintf (buffer, sizeof (buffer),
+                      "%d:%02d:%02d %02d:%02d:%02d",
+                      g_date_time_get_year (datetime),
+                      g_date_time_get_month (datetime),
+                      g_date_time_get_day_of_month (datetime),
+                      g_date_time_get_hour (datetime),
+                      g_date_time_get_minute (datetime),
+                      g_date_time_get_second (datetime));
+          gexiv2_metadata_set_tag_string (metadata,
+                                          "Xmp.tiff.DateTime",
+                                          buffer);
+        }
+
+      /* IPTC */
+
+      g_snprintf (buffer, sizeof (buffer),
+                  "%d-%d-%d",
+                  g_date_time_get_year (datetime),
+                  g_date_time_get_month (datetime),
+                  g_date_time_get_day_of_month (datetime));
+      gexiv2_metadata_set_tag_string (metadata,
+                                      "Iptc.Application2.DateCreated",
+                                      buffer);
+
+      g_snprintf (buffer, sizeof (buffer),
+                  "%02d:%02d:%02d-%02d:%02d",
+                  g_date_time_get_hour (datetime),
+                  g_date_time_get_minute (datetime),
+                  g_date_time_get_second (datetime),
+                  g_date_time_get_hour (datetime),
+                  g_date_time_get_minute (datetime));
+      gexiv2_metadata_set_tag_string (metadata,
+                                      "Iptc.Application2.TimeCreated",
+                                      buffer);
+
+      g_date_time_unref (datetime);
+    }
+
+  return metadata;
+}
+
+gboolean
+gimp_image_metadata_save_finish (gint32                  image_ID,
+                                 const gchar            *mime_type,
+                                 GimpMetadata           *metadata,
+                                 GFile                  *file,
+                                 GimpMetadataSaveFlags   flags,
+                                 GError                **error)
+{
+  GExiv2Metadata *new_metadata;
+  gboolean        support_exif;
+  gboolean        support_xmp;
+  gboolean        support_iptc;
+  gchar          *value;
+  gboolean        success = FALSE;
+  gint            i;
+
+  g_return_val_if_fail (image_ID > 0, FALSE);
+  g_return_val_if_fail (mime_type != NULL, FALSE);
+  g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), FALSE);
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  /* read metadata from saved file */
+  new_metadata = gimp_metadata_load_from_file (file, error);
+
+  if (! new_metadata)
+    return FALSE;
+
+  support_exif = gexiv2_metadata_get_supports_exif (new_metadata);
+  support_xmp  = gexiv2_metadata_get_supports_xmp  (new_metadata);
+  support_iptc = gexiv2_metadata_get_supports_iptc (new_metadata);
+
+  if ((flags & GIMP_METADATA_SAVE_EXIF) && support_exif)
+    {
+      gchar **exif_data = gexiv2_metadata_get_exif_tags (metadata);
+
+      for (i = 0; exif_data[i] != NULL; i++)
+        {
+          if (! gexiv2_metadata_has_tag (new_metadata, exif_data[i]) &&
+              gimp_metadata_is_tag_supported (exif_data[i], mime_type))
+            {
+              value = gexiv2_metadata_get_tag_string (metadata, exif_data[i]);
+              gexiv2_metadata_set_tag_string (new_metadata, exif_data[i],
+                                              value);
+              g_free (value);
+            }
+        }
+
+      g_strfreev (exif_data);
+    }
+
+  if ((flags & GIMP_METADATA_SAVE_XMP) && support_xmp)
+    {
+      gchar **xmp_data = gexiv2_metadata_get_xmp_tags (metadata);
+
+      for (i = 0; xmp_data[i] != NULL; i++)
+        {
+          if (! gexiv2_metadata_has_tag (new_metadata, xmp_data[i]) &&
+              gimp_metadata_is_tag_supported (xmp_data[i], mime_type))
+            {
+              value = gexiv2_metadata_get_tag_string (metadata, xmp_data[i]);
+              gexiv2_metadata_set_tag_string (new_metadata, xmp_data[i],
+                                              value);
+              g_free (value);
+            }
+        }
+
+      g_strfreev (xmp_data);
+    }
+
+  if ((flags & GIMP_METADATA_SAVE_IPTC) && support_iptc)
+    {
+      gchar **iptc_data = gexiv2_metadata_get_iptc_tags (metadata);
+
+      for (i = 0; iptc_data[i] != NULL; i++)
+        {
+          if (! gexiv2_metadata_has_tag (new_metadata, iptc_data[i]) &&
+              gimp_metadata_is_tag_supported (iptc_data[i], mime_type))
+            {
+              value = gexiv2_metadata_get_tag_string (metadata, iptc_data[i]);
+              gexiv2_metadata_set_tag_string (new_metadata, iptc_data[i],
+                                              value);
+              g_free (value);
+            }
+        }
+
+      g_strfreev (iptc_data);
+    }
+
+  if (flags & GIMP_METADATA_SAVE_THUMBNAIL)
+    {
+      GdkPixbuf *thumb_pixbuf;
+      gchar     *thumb_buffer;
+      gint       image_width;
+      gint       image_height;
+      gsize      count;
+      gint       thumbw;
+      gint       thumbh;
+
+#define EXIF_THUMBNAIL_SIZE 256
+
+      image_width  = gimp_image_width  (image_ID);
+      image_height = gimp_image_height (image_ID);
+
+      if (image_width > image_height)
+        {
+          thumbw = EXIF_THUMBNAIL_SIZE;
+          thumbh = EXIF_THUMBNAIL_SIZE * image_height / image_width;
+        }
+      else
+        {
+          thumbh = EXIF_THUMBNAIL_SIZE;
+          thumbw = EXIF_THUMBNAIL_SIZE * image_height / image_width;
+        }
+
+      thumb_pixbuf = gimp_image_get_thumbnail (image_ID, thumbw, thumbh,
+                                               GIMP_PIXBUF_KEEP_ALPHA);
+
+      if (gdk_pixbuf_save_to_buffer (thumb_pixbuf, &thumb_buffer, &count,
+                                     "jpeg", NULL,
+                                     "quality", "75",
+                                     NULL))
+        {
+          gchar buffer[32];
+
+          gexiv2_metadata_set_exif_thumbnail_from_buffer (new_metadata,
+                                                          (guchar *) thumb_buffer,
+                                                          count);
+
+          g_snprintf (buffer, sizeof (buffer), "%d", thumbw);
+          gexiv2_metadata_set_tag_string (new_metadata,
+                                          "Exif.Thumbnail.ImageWidth",
+                                          buffer);
+
+          g_snprintf (buffer, sizeof (buffer), "%d", thumbh);
+          gexiv2_metadata_set_tag_string (new_metadata,
+                                          "Exif.Thumbnail.ImageLength",
+                                          buffer);
+
+          gexiv2_metadata_set_tag_string (new_metadata,
+                                          "Exif.Thumbnail.BitsPerSample",
+                                          "8 8 8");
+          gexiv2_metadata_set_tag_string (new_metadata,
+                                          "Exif.Thumbnail.SamplesPerPixel",
+                                          "3");
+          gexiv2_metadata_set_tag_string (new_metadata,
+                                          "Exif.Thumbnail.PhotometricInterpretation",
+                                          "6");
+
+          g_free (thumb_buffer);
+        }
+
+      g_object_unref (thumb_pixbuf);
+    }
+
+  success = gimp_metadata_save_to_file (new_metadata, file, error);
+
+  g_object_unref (new_metadata);
+
+  return success;
+}
+
+
+/*  private functions  */
+
+static void
+gimp_image_metadata_rotate (gint32 image_ID,
+                            gint   orientation)
+{
+  switch (orientation)
+    {
+    case 1:  /* standard orientation, do nothing */
+      break;
+
+    case 2:  /* flipped right-left               */
+      gimp_image_flip (image_ID, GIMP_ORIENTATION_HORIZONTAL);
+      break;
+
+    case 3:  /* rotated 180                      */
+      gimp_image_rotate (image_ID, GIMP_ROTATE_180);
+      break;
+
+    case 4:  /* flipped top-bottom               */
+      gimp_image_flip (image_ID, GIMP_ORIENTATION_VERTICAL);
+      break;
+
+    case 5:  /* flipped diagonally around '\'    */
+      gimp_image_rotate (image_ID, GIMP_ROTATE_90);
+      gimp_image_flip (image_ID, GIMP_ORIENTATION_HORIZONTAL);
+      break;
+
+    case 6:  /* 90 CW                            */
+      gimp_image_rotate (image_ID, GIMP_ROTATE_90);
+      break;
+
+    case 7:  /* flipped diagonally around '/'    */
+      gimp_image_rotate (image_ID, GIMP_ROTATE_90);
+      gimp_image_flip (image_ID, GIMP_ORIENTATION_VERTICAL);
+      break;
+
+    case 8:  /* 90 CCW                           */
+      gimp_image_rotate (image_ID, GIMP_ROTATE_270);
+      break;
+
+    default: /* shouldn't happen                 */
+      break;
+    }
+}
+
+static void
+gimp_image_metadata_rotate_query (gint32        image_ID,
+                                  const gchar  *mime_type,
+                                  GimpMetadata *metadata,
+                                  gboolean      interactive)
+{
+  GimpParasite *parasite;
+  gchar        *parasite_name;
+  gint          orientation;
+  gboolean      query = interactive;
+
+  orientation = gexiv2_metadata_get_orientation (metadata);
+
+  if (orientation < 2 || orientation > 8)
+    return;
+
+  parasite_name = g_strdup_printf ("gimp-metadata-exif-rotate(%s)", mime_type);
+
+  parasite = gimp_get_parasite (parasite_name);
+
+  if (parasite)
+    {
+      if (strncmp (gimp_parasite_data (parasite), "yes",
+                   gimp_parasite_data_size (parasite)) == 0)
+        {
+          query = FALSE;
+        }
+      else if (strncmp (gimp_parasite_data (parasite), "no",
+                        gimp_parasite_data_size (parasite)) == 0)
+        {
+          gimp_parasite_free (parasite);
+          g_free (parasite_name);
+          return;
+        }
+
+      gimp_parasite_free (parasite);
+    }
+
+  if (query && ! gimp_image_metadata_rotate_dialog (image_ID,
+                                                    parasite_name))
+    {
+      g_free (parasite_name);
+      return;
+    }
+
+  g_free (parasite_name);
+
+  gimp_image_metadata_rotate (image_ID, orientation);
+
+  gexiv2_metadata_set_tag_string (metadata,
+                                  "Exif.Image.Orientation", "1");
+  gexiv2_metadata_set_tag_string (metadata,
+                                  "Xmp.tiff.Orientation", "1");
+}
+
+static gboolean
+gimp_image_metadata_rotate_dialog (gint32       image_ID,
+                                   const gchar *parasite_name)
+{
+  GtkWidget *dialog;
+  GtkWidget *hbox;
+  GtkWidget *vbox;
+  GtkWidget *label;
+  GtkWidget *toggle;
+  GdkPixbuf *pixbuf;
+  gint       response;
+
+  dialog = gimp_dialog_new (_("Rotate Image?"), "gimp-metadata-rotate-dialog",
+                            NULL, 0, NULL, NULL,
+
+                            _("_Keep Orientation"), GTK_RESPONSE_CANCEL,
+                            GIMP_STOCK_TOOL_ROTATE, GTK_RESPONSE_OK,
+
+                            NULL);
+
+  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+                                           GTK_RESPONSE_OK,
+                                           GTK_RESPONSE_CANCEL,
+                                           -1);
+
+  gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+  gimp_window_set_transient (GTK_WINDOW (dialog));
+
+  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+  gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
+  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+                      hbox, FALSE, FALSE, 0);
+  gtk_widget_show (hbox);
+
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+  gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
+  gtk_widget_show (vbox);
+
+#define THUMBNAIL_SIZE 128
+
+  pixbuf = gimp_image_get_thumbnail (image_ID,
+                                     THUMBNAIL_SIZE, THUMBNAIL_SIZE,
+                                     GIMP_PIXBUF_SMALL_CHECKS);
+
+  if (pixbuf)
+    {
+      GtkWidget *image;
+      gchar     *name;
+
+      image = gtk_image_new_from_pixbuf (pixbuf);
+      g_object_unref (pixbuf);
+
+      gtk_box_pack_start (GTK_BOX (vbox), image, FALSE, FALSE, 0);
+      gtk_widget_show (image);
+
+      name = gimp_image_get_name (image_ID);
+
+      label = gtk_label_new (name);
+      gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE);
+      gimp_label_set_attributes (GTK_LABEL (label),
+                                 PANGO_ATTR_STYLE,  PANGO_STYLE_ITALIC,
+                                 -1);
+      gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+      gtk_widget_show (label);
+
+      g_free (name);
+    }
+
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+  gtk_widget_show (vbox);
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "label",   _("According to the EXIF data, "
+                                     "this image is rotated."),
+                        "wrap",    TRUE,
+                        "justify", GTK_JUSTIFY_LEFT,
+                        "xalign",  0.0,
+                        "yalign",  0.5,
+                        NULL);
+  gimp_label_set_attributes (GTK_LABEL (label),
+                             PANGO_ATTR_SCALE,  PANGO_SCALE_LARGE,
+                             PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
+                             -1);
+  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+  gtk_widget_show (label);
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "label",   _("Would you like GIMP to rotate it "
+                                     "into the standard orientation?"),
+                        "wrap",    TRUE,
+                        "justify", GTK_JUSTIFY_LEFT,
+                        "xalign",  0.0,
+                        "yalign",  0.5,
+                        NULL);
+  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+  gtk_widget_show (label);
+
+  toggle = gtk_check_button_new_with_mnemonic (_("_Don't ask me again"));
+  gtk_box_pack_end (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE);
+  gtk_widget_show (toggle);
+
+  response = gimp_dialog_run (GIMP_DIALOG (dialog));
+
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)))
+    {
+      GimpParasite *parasite;
+      const gchar  *str = (response == GTK_RESPONSE_OK) ? "yes" : "no";
+
+      parasite = gimp_parasite_new (parasite_name,
+                                    GIMP_PARASITE_PERSISTENT,
+                                    strlen (str), str);
+      gimp_attach_parasite (parasite);
+      gimp_parasite_free (parasite);
+    }
+
+  gtk_widget_destroy (dialog);
+
+  return (response == GTK_RESPONSE_OK);
+}
diff --git a/libgimp/gimpmetadata.h b/libgimp/gimpmetadata.h
new file mode 100644
index 0000000..a2f9574
--- /dev/null
+++ b/libgimp/gimpmetadata.h
@@ -0,0 +1,48 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-2000 Peter Mattis and Spencer Kimball
+ *
+ * gimpmetadata.h
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__GIMP_UI_H_INSIDE__) && !defined (GIMP_COMPILATION)
+#error "Only <libgimp/gimpui.h> can be included directly."
+#endif
+
+#ifndef __LIBGIMP_GIMP_METADATA_H__
+#define __LIBGIMP_GIMP_METADATA_H__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+
+void           gimp_image_metadata_load         (gint32              image_ID,
+                                                 const gchar        *mime_type,
+                                                 GFile              *file,
+                                                 gboolean            interactive);
+GimpMetadata * gimp_image_metadata_save_prepare (gint32              image_ID,
+                                                 const gchar        *mime_type);
+gboolean       gimp_image_metadata_save_finish  (gint32              image_ID,
+                                                 const gchar        *mime_type,
+                                                 GimpMetadata       *metadata,
+                                                 GFile              *file,
+                                                 GimpMetadataSaveFlags flags,
+                                                 GError            **error);
+
+G_END_DECLS
+
+#endif /* ___LIBGIMP_GIMP_METADATA_H__ */
diff --git a/libgimp/gimpui.def b/libgimp/gimpui.def
index f9ad3af..d50f081 100644
--- a/libgimp/gimpui.def
+++ b/libgimp/gimpui.def
@@ -38,6 +38,9 @@ EXPORTS
        gimp_image_combo_box_get_type
        gimp_image_combo_box_new
        gimp_image_menu_new
+       gimp_image_metadata_load
+       gimp_image_metadata_save_prepare
+       gimp_image_metadata_save_finish
        gimp_layer_combo_box_get_type
        gimp_layer_combo_box_new
        gimp_layer_menu_new
diff --git a/libgimp/gimpui.h b/libgimp/gimpui.h
index 84fdea9..85c5ea5 100644
--- a/libgimp/gimpui.h
+++ b/libgimp/gimpui.h
@@ -29,6 +29,7 @@
 
 #include <libgimp/gimpexport.h>
 #include <libgimp/gimpmenu.h>
+#include <libgimp/gimpmetadata.h>
 #include <libgimp/gimpaspectpreview.h>
 #include <libgimp/gimpdrawablepreview.h>
 #include <libgimp/gimpbrushmenu.h>
diff --git a/libgimpbase/Makefile.am b/libgimpbase/Makefile.am
index 59a258b..ffc8b3e 100644
--- a/libgimpbase/Makefile.am
+++ b/libgimpbase/Makefile.am
@@ -61,6 +61,7 @@ AM_CPPFLAGS = \
        -DGIMP_BASE_COMPILATION                                 \
        -I$(top_srcdir)                                         \
        $(GIO_CFLAGS)                                           \
+       $(GEXIV2_CFLAGS)                                        \
        $(BINRELOC_CFLAGS)                                      \
        -I$(includedir)                                         \
        $(xobjective_c)
@@ -101,6 +102,8 @@ libgimpbase_sources = \
        gimpenv.h               \
        gimpmemsize.c           \
        gimpmemsize.h           \
+       gimpmetadata.c          \
+       gimpmetadata.h          \
        gimpparasite.c          \
        gimpparasite.h          \
        gimpparasiteio.c        \
@@ -144,6 +147,7 @@ libgimpbaseinclude_HEADERS = \
        gimpdatafiles.h         \
        gimpenv.h               \
        gimpmemsize.h           \
+       gimpmetadata.h          \
        gimpparasite.h          \
        gimpparasiteio.h        \
        gimprectangle.h         \
@@ -152,7 +156,6 @@ libgimpbaseinclude_HEADERS = \
        gimputils.h             \
        gimpvaluearray.h
 
-
 libgimpbase_ GIMP_API_VERSION@_la_LDFLAGS = \
        -version-info $(LT_VERSION_INFO)        \
        $(no_undefined)                         \
@@ -163,9 +166,9 @@ libgimpbase_ GIMP_API_VERSION@_la_DEPENDENCIES = $(gimpbase_def)
 
 libgimpbase_ GIMP_API_VERSION@_la_LIBADD = \
        $(GIO_LIBS)     \
+       $(GEXIV2_LIBS)  \
        $(ole32_lib)
 
-
 install-data-local: install-ms-lib install-libtool-import-lib
 
 uninstall-local: uninstall-ms-lib uninstall-libtool-import-lib
diff --git a/libgimpbase/gimpbase.def b/libgimpbase/gimpbase.def
index a6330e0..6d87e07 100644
--- a/libgimpbase/gimpbase.def
+++ b/libgimpbase/gimpbase.def
@@ -50,6 +50,15 @@ EXPORTS
        gimp_memsize_serialize
        gimp_memsize_to_string
        gimp_message_handler_type_get_type
+       gimp_metadata_deserialize
+       gimp_metadata_get_resolution
+       gimp_metadata_is_tag_supported
+       gimp_metadata_load_from_file
+       gimp_metadata_save_to_file
+       gimp_metadata_serialize
+       gimp_metadata_set_bits_per_sample
+       gimp_metadata_set_pixel_size
+       gimp_metadata_set_resolution
        gimp_micro_version
        gimp_minor_version
        gimp_paint_application_mode_get_type
@@ -100,7 +109,7 @@ EXPORTS
        gimp_sysconf_directory
        gimp_text_direction_get_type
        gimp_text_hint_style_get_type
-       gimp_text_justification_get_type 
+       gimp_text_justification_get_type
        gimp_transfer_mode_get_type
        gimp_transform_direction_get_type
        gimp_transform_resize_get_type
diff --git a/libgimpbase/gimpbase.h b/libgimpbase/gimpbase.h
index 08930dc..a5682e7 100644
--- a/libgimpbase/gimpbase.h
+++ b/libgimpbase/gimpbase.h
@@ -29,6 +29,7 @@
 #include <libgimpbase/gimpenv.h>
 #include <libgimpbase/gimplimits.h>
 #include <libgimpbase/gimpmemsize.h>
+#include <libgimpbase/gimpmetadata.h>
 #include <libgimpbase/gimpparasite.h>
 #include <libgimpbase/gimprectangle.h>
 #include <libgimpbase/gimpunit.h>
diff --git a/libgimpbase/gimpbasetypes.h b/libgimpbase/gimpbasetypes.h
index 481c1cd..bef9904 100644
--- a/libgimpbase/gimpbasetypes.h
+++ b/libgimpbase/gimpbasetypes.h
@@ -55,6 +55,8 @@ typedef struct _GimpValueArray   GimpValueArray;
 typedef void (* GimpDatafileLoaderFunc) (const GimpDatafileData *file_data,
                                          gpointer                user_data);
 
+typedef struct _GExiv2Metadata GimpMetadata;
+
 
 /**
  * GimpEnumDesc:
diff --git a/libgimpbase/gimpmetadata.c b/libgimpbase/gimpmetadata.c
new file mode 100644
index 0000000..1d0149b
--- /dev/null
+++ b/libgimpbase/gimpmetadata.c
@@ -0,0 +1,653 @@
+/* LIBGIMPBASE - The GIMP Basic Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpmetadata.c
+ * Copyright (C) 2013 Hartmut Kuhse <hartmutkuhse src gnome org>
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gio/gio.h>
+#include <gexiv2/gexiv2.h>
+
+#include "gimpbasetypes.h"
+
+#include "gimpmetadata.h"
+#include "gimpunit.h"
+
+#include "libgimp/libgimp-intl.h"
+
+
+#define TAG_LINE_DELIMITER "\v"
+#define TAG_TAG_DELIMITER  "#"
+
+
+static gint       gimp_metadata_length       (const gchar  *testline,
+                                              const gchar  *delim);
+static gboolean   gimp_metadata_get_rational (const gchar  *value,
+                                              gint          sections,
+                                              gchar      ***numerator,
+                                              gchar      ***denominator);
+
+
+static const gchar *tiff_tags[] =
+{
+  "Xmp.tiff",
+  "Exif.Image.ImageWidth",
+  "Exif.Image.ImageLength",
+  "Exif.Image.BitsPerSample",
+  "Exif.Image.Compression",
+  "Exif.Image.PhotometricInterpretation",
+  "Exif.Image.FillOrder",
+  "Exif.Image.SamplesPerPixel",
+  "Exif.Image.StripOffsets",
+  "Exif.Image.RowsPerStrip",
+  "Exif.Image.StripByteCounts",
+  "Exif.Image.PlanarConfiguration"
+};
+
+static const gchar *jpeg_tags[] =
+{
+  "Exif.Image.JPEGProc",
+  "Exif.Image.JPEGInterchangeFormat",
+  "Exif.Image.JPEGInterchangeFormatLength",
+  "Exif.Image.JPEGRestartInterval",
+  "Exif.Image.JPEGLosslessPredictors",
+  "Exif.Image.JPEGPointTransforms",
+  "Exif.Image.JPEGQTables",
+  "Exif.Image.JPEGDCTables",
+  "Exif.Image.JPEGACTables"
+};
+
+static const gchar *unsupported_tags[] =
+{
+  "Exif.Image.SubIFDs",
+  "Exif.Image.ClipPath",
+  "Exif.Image.XClipPathUnits",
+  "Exif.Image.YClipPathUnits",
+  "Xmp.xmpMM.History",
+  "Exif.Image.XPTitle",
+  "Exif.Image.XPComment",
+  "Exif.Image.XPAuthor",
+  "Exif.Image.XPKeywords",
+  "Exif.Image.XPSubject",
+  "Exif.Image.DNGVersion",
+  "Exif.Image.DNGBackwardVersion",
+  "Exif.Iop"
+};
+
+static const guint8 wilber_jpg[] =
+{
+  0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
+  0x01, 0x01, 0x00, 0x5a, 0x00, 0x5a, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43,
+  0x00, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x50, 0x46, 0x41, 0x46, 0x5a,
+  0x55, 0x50, 0x5f, 0x78, 0xc8, 0x82, 0x78, 0x6e, 0x6e, 0x78, 0xf5, 0xaf,
+  0xb9, 0x91, 0xc8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x55, 0x5a,
+  0x5a, 0x78, 0x69, 0x78, 0xeb, 0x82, 0x82, 0xeb, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10, 0x03,
+  0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00,
+  0x16, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x02, 0xff, 0xc4, 0x00,
+  0x1e, 0x10, 0x00, 0x01, 0x05, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x11, 0x31,
+  0x04, 0x12, 0x51, 0x61, 0x71, 0xff, 0xc4, 0x00, 0x14, 0x01, 0x01, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x11, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11,
+  0x00, 0x3f, 0x00, 0x18, 0xa0, 0x0e, 0x6d, 0xbc, 0xf5, 0xca, 0xf7, 0x78,
+  0xb6, 0xfe, 0x3b, 0x23, 0xb2, 0x1d, 0x64, 0x68, 0xf0, 0x8a, 0x39, 0x4b,
+  0x74, 0x9c, 0xa5, 0x5f, 0x35, 0x8a, 0xb2, 0x7e, 0xa0, 0xff, 0xd9, 0x00
+};
+
+static const guint wilber_jpg_len = G_N_ELEMENTS (wilber_jpg);
+
+
+/**
+ * Deserializes metadata from a string
+ **/
+GExiv2Metadata *
+gimp_metadata_deserialize (const gchar *metadata_string)
+{
+  GExiv2Metadata  *metadata = NULL;
+  GString         *buffer;
+  gint             i;
+  gint             j;
+  gchar          **meta_info = NULL;
+  gchar          **tag_info  = NULL;
+  gint             num;
+  gchar           *value;
+  GError         **error = NULL;
+
+  g_return_val_if_fail (metadata_string != NULL, NULL);
+
+  if (gexiv2_initialize ())
+    {
+      metadata = gexiv2_metadata_new ();
+
+      if (! gexiv2_metadata_open_buf (metadata, wilber_jpg, wilber_jpg_len,
+                                      error))
+        {
+          return NULL;
+        }
+    }
+
+  if (metadata)
+    {
+      meta_info = g_strsplit (metadata_string, TAG_LINE_DELIMITER, -1);
+
+      if (meta_info)
+        {
+          for (i = 0; meta_info[i] != NULL; i++)
+            {
+              num = gimp_metadata_length (meta_info[i], TAG_TAG_DELIMITER);
+              tag_info = g_strsplit (meta_info[i], TAG_TAG_DELIMITER, -1);
+              if (num > 0)
+                {
+                  if (num > 1)
+                    {
+                      buffer = g_string_new (NULL);
+                      for (j = 1; j < (num + 1); j++)
+                        {
+                          g_string_append_printf (buffer, "%s%s",
+                                                  tag_info[j], TAG_TAG_DELIMITER); /* recreates value */
+                        }
+                      value = g_strndup (buffer->str, buffer->len - 1);  /* to avoid the trailing '#' */
+                      g_string_free (buffer, TRUE);
+                    }
+                  else
+                    {
+                      value = tag_info[1];
+                    }
+
+                  gexiv2_metadata_set_tag_string (metadata, tag_info[0], value);
+                }
+            }
+
+          g_strfreev (meta_info);
+        }
+    }
+
+  if (tag_info)
+    g_strfreev (tag_info);
+
+  return metadata;
+}
+
+/**
+ * Serializing metadata as a string
+ */
+gchar *
+gimp_metadata_serialize (GExiv2Metadata *metadata)
+{
+  GString  *string;
+  gchar   **exif_data = NULL;
+  gchar   **iptc_data = NULL;
+  gchar   **xmp_data  = NULL;
+  gchar    *value;
+  gint      i;
+  gint      n;
+
+  g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), NULL);
+
+  n = 0;
+  string = g_string_new (NULL);
+
+  exif_data = gexiv2_metadata_get_exif_tags (metadata);
+
+  if (exif_data)
+    {
+      for (i = 0; exif_data[i] != NULL; i++)
+        {
+          value = gexiv2_metadata_get_tag_string (metadata, exif_data[i]);
+          g_string_append_printf (string, "%s%s%s%s", exif_data[i],
+                                  TAG_TAG_DELIMITER,
+                                  value,
+                                  TAG_LINE_DELIMITER);
+          g_free (value);
+          n++;
+        }
+
+      g_strfreev (exif_data);
+    }
+
+  xmp_data = gexiv2_metadata_get_xmp_tags (metadata);
+
+  if (xmp_data)
+    {
+      for (i = 0; xmp_data[i] != NULL; i++)
+        {
+          value = gexiv2_metadata_get_tag_string (metadata, xmp_data[i]);
+          g_string_append_printf (string, "%s%s%s%s", xmp_data[i],
+                                  TAG_TAG_DELIMITER,
+                                  value,
+                                  TAG_LINE_DELIMITER);
+          g_free (value);
+          n++;
+        }
+
+      g_strfreev (xmp_data);
+    }
+
+  iptc_data = gexiv2_metadata_get_iptc_tags (metadata);
+
+  if (iptc_data)
+    {
+      for (i = 0; iptc_data[i] != NULL; i++)
+        {
+          value = gexiv2_metadata_get_tag_string (metadata, iptc_data[i]);
+          g_string_append_printf (string, "%s%s%s%s", iptc_data[i],
+                                  TAG_TAG_DELIMITER,
+                                  value,
+                                  TAG_LINE_DELIMITER);
+          g_free (value);
+          n++;
+        }
+
+      g_strfreev (iptc_data);
+    }
+
+  return g_string_free (string, FALSE);
+}
+
+/*
+ * reads metadata from a physical file
+ */
+GimpMetadata  *
+gimp_metadata_load_from_file (GFile   *file,
+                              GError **error)
+{
+  GExiv2Metadata *meta = NULL;
+  gchar          *path;
+  gchar          *filename;
+
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  path = g_file_get_path (file);
+
+  if (! path)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   _("Can load metadata only from local files"));
+      return NULL;
+    }
+
+#ifdef G_OS_WIN32
+  filename = g_win32_locale_filename_from_utf8 (path);
+#else
+  filename = g_strdup (path);
+#endif
+
+  g_free (path);
+
+  if (gexiv2_initialize ())
+    {
+      meta = gexiv2_metadata_new ();
+
+      if (! gexiv2_metadata_open_path (meta, filename, error))
+        {
+          g_object_unref (meta);
+          g_free (filename);
+
+          return NULL;
+        }
+    }
+
+  g_free (filename);
+
+  return meta;
+}
+
+/*
+ * saves metadata to a physical file
+ */
+gboolean
+gimp_metadata_save_to_file (GimpMetadata  *metadata,
+                            GFile         *file,
+                            GError       **error)
+{
+  gchar    *path;
+  gchar    *filename;
+  gboolean  success;
+
+  g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), FALSE);
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  path = g_file_get_path (file);
+
+  if (! path)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   _("Can save metadata only from to files"));
+      return FALSE;
+    }
+
+#ifdef G_OS_WIN32
+  filename = g_win32_locale_filename_from_utf8 (path);
+#else
+  filename = g_strdup (path);
+#endif
+
+  g_free (path);
+
+  success = gexiv2_metadata_save_file (metadata, filename, error);
+
+  g_free (filename);
+
+  return success;
+}
+
+void
+gimp_metadata_set_pixel_size (GimpMetadata *metadata,
+                              gint          width,
+                              gint          height)
+{
+  gchar buffer[32];
+
+  g_return_if_fail (GEXIV2_IS_METADATA (metadata));
+
+  g_snprintf (buffer, sizeof (buffer), "%d", width);
+  gexiv2_metadata_set_tag_string (metadata, "Exif.Image.ImageWidth", buffer);
+
+  g_snprintf (buffer, sizeof (buffer), "%d", height);
+  gexiv2_metadata_set_tag_string (metadata, "Exif.Image.ImageLength", buffer);
+}
+
+void
+gimp_metadata_set_bits_per_sample (GimpMetadata *metadata,
+                                   gint          bps)
+{
+  gchar buffer[32];
+
+  g_return_if_fail (GEXIV2_IS_METADATA (metadata));
+
+  g_snprintf (buffer, sizeof (buffer), "%d %d %d", bps, bps, bps);
+  gexiv2_metadata_set_tag_string (metadata, "Exif.Image.BitsPerSample", buffer);
+}
+
+/**
+ * gets exif resolution
+ */
+gboolean
+gimp_metadata_get_resolution (GimpMetadata *metadata,
+                              gdouble      *xres,
+                              gdouble      *yres,
+                              GimpUnit     *unit)
+{
+  gchar  *xr;
+  gchar  *yr;
+  gchar  *un;
+  gint    exif_unit;
+  gchar **xnom   = NULL;
+  gchar **xdenom = NULL;
+  gchar **ynom   = NULL;
+  gchar **ydenom = NULL;
+
+  g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), FALSE);
+
+  xr = gexiv2_metadata_get_tag_string (metadata, "Exif.Image.XResolution");
+  yr = gexiv2_metadata_get_tag_string (metadata, "Exif.Image.YResolution");
+
+  un = gexiv2_metadata_get_tag_string (metadata, "Exif.Image.ResolutionUnit");
+  exif_unit = atoi (un);
+  g_free (un);
+
+  if (exif_unit == 3)
+    *unit = GIMP_UNIT_MM;
+  else
+    *unit = GIMP_UNIT_INCH;
+
+  if (gimp_metadata_get_rational (xr, 1, &xnom, &xdenom))
+    {
+      gdouble x1 = g_ascii_strtod (xnom[0], NULL);
+      gdouble x2 = g_ascii_strtod (xdenom[0], NULL);
+      gdouble xrd;
+
+      if (x2 == 0.0)
+        return FALSE;
+
+      xrd = x1 / x2;
+
+      if (exif_unit == 3)
+        xrd *= 2.54;
+
+      *xres = xrd;
+    }
+
+  if (gimp_metadata_get_rational (yr, 1, &ynom, &ydenom))
+    {
+      gdouble y1 = g_ascii_strtod (ynom[0], NULL);
+      gdouble y2 = g_ascii_strtod (ydenom[0], NULL);
+      gdouble yrd;
+
+      if (y2 == 0.0)
+        return FALSE;
+
+      yrd = y1 / y2;
+
+      if (exif_unit == 3)
+        yrd *= 2.54;
+
+      *yres = yrd;
+    }
+
+  g_free (xr);
+  g_free (yr);
+
+  g_strfreev (xnom);
+  g_strfreev (xdenom);
+  g_strfreev (ynom);
+  g_strfreev (ydenom);
+
+  return TRUE;
+}
+
+/**
+ * sets exif resolution
+ */
+void
+gimp_metadata_set_resolution (GimpMetadata *metadata,
+                              gdouble       xres,
+                              gdouble       yres,
+                              GimpUnit      unit)
+{
+  gchar buffer[32];
+  gint  exif_unit;
+
+  g_return_if_fail (GEXIV2_IS_METADATA (metadata));
+
+  if (gimp_unit_is_metric (unit))
+    {
+      xres /= 2.54;
+      yres /= 2.54;
+
+      exif_unit = 3;
+    }
+  else
+    {
+      exif_unit = 2;
+    }
+
+  g_ascii_formatd (buffer, sizeof (buffer), "%.0f/1", xres);
+  gexiv2_metadata_set_tag_string (metadata, "Exif.Image.XResolution", buffer);
+
+  g_ascii_formatd (buffer, sizeof (buffer), "%.0f/1", yres);
+  gexiv2_metadata_set_tag_string (metadata, "Exif.Image.YResolution", buffer);
+
+  g_snprintf (buffer, sizeof (buffer), "%d", exif_unit);
+  gexiv2_metadata_set_tag_string (metadata, "Exif.Image.ResolutionUnit", buffer);
+}
+
+/**
+ * checks for supported tags
+ */
+gboolean
+gimp_metadata_is_tag_supported (const gchar *tag,
+                                const gchar *mime_type)
+{
+  gint j;
+
+  g_return_val_if_fail (tag != NULL, FALSE);
+  g_return_val_if_fail (mime_type != NULL, FALSE);
+
+  for (j = 0; j < G_N_ELEMENTS (unsupported_tags); j++)
+    {
+      if (g_str_has_prefix (tag, unsupported_tags[j]))
+        {
+          return FALSE;
+        }
+    }
+
+  if (! strcmp (mime_type, "image/jpeg"))
+    {
+      for (j = 0; j < G_N_ELEMENTS (tiff_tags); j++)
+        {
+          if (g_str_has_prefix (tag, tiff_tags[j]))
+            {
+              return FALSE;
+            }
+        }
+    }
+  else if (! strcmp (mime_type, "image/tiff"))
+    {
+      for (j = 0; j < G_N_ELEMENTS (jpeg_tags); j++)
+        {
+          if (g_str_has_prefix (tag, jpeg_tags[j]))
+            {
+              return FALSE;
+            }
+        }
+    }
+
+  return TRUE;
+}
+
+
+/* private functions */
+
+/**
+ * determines the amount of delimiters in serialized
+ * metadata string
+ */
+static gint
+gimp_metadata_length (const gchar *testline,
+                      const gchar *delim)
+{
+  gchar *delim_test;
+  gint   i;
+  gint   sum;
+
+  delim_test = g_strdup (testline);
+
+  sum =0;
+
+  for (i=0; i < strlen (delim_test); i++)
+    {
+      if (delim_test[i] == delim[0])
+        sum++;
+    }
+
+  g_free (delim_test);
+
+  return sum;
+}
+
+/**
+ * gets rational values from string
+ */
+static gboolean
+gimp_metadata_get_rational (const gchar   *value,
+                            gint           sections,
+                            gchar       ***numerator,
+                            gchar       ***denominator)
+{
+  GSList *nomlist = NULL;
+  GSList *denomlist = NULL;
+
+  GSList *nlist, *dlist;
+
+  gchar   sect[] = " ";
+  gchar   rdel[] = "/";
+  gchar **sects;
+  gchar **nom = NULL;
+  gint    i;
+  gint    n;
+
+  gchar **num;
+  gchar **den;
+
+  if (! value)
+    return FALSE;
+
+  if (gimp_metadata_length (value, sect) == (sections -1))
+    {
+      i = 0;
+      sects = g_strsplit (value, sect, -1);
+      while (sects[i] != NULL)
+        {
+          if(gimp_metadata_length (sects[i], rdel) == 1)
+            {
+              nom = g_strsplit (sects[i], rdel, -1);
+              nomlist = g_slist_prepend (nomlist, g_strdup (nom[0]));
+              denomlist = g_slist_prepend (denomlist, g_strdup (nom[1]));
+            }
+          else
+            {
+              return FALSE;
+            }
+          i++;
+        }
+    }
+  else
+    {
+      return FALSE;
+    }
+
+  n = i;
+
+  num = g_new0 (gchar*, i + 1);
+  den = g_new0 (gchar*, n + 1);
+
+  for (nlist = nomlist; nlist; nlist = nlist->next)
+    num[--i] = nlist->data;
+
+  for (dlist = denomlist; dlist; dlist = dlist->next)
+    den[--n] = dlist->data;
+
+  *numerator = num;
+  *denominator = den;
+
+  g_slist_free (nomlist);
+  g_slist_free (denomlist);
+
+  g_strfreev (sects);
+  g_strfreev (nom);
+
+  return TRUE;
+}
diff --git a/libgimpbase/gimpmetadata.h b/libgimpbase/gimpmetadata.h
new file mode 100644
index 0000000..cbddd38
--- /dev/null
+++ b/libgimpbase/gimpmetadata.h
@@ -0,0 +1,72 @@
+/* LIBGIMPBASE - The GIMP Basic Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpmetadata.h
+ * Copyright (C) 2013 Hartmut Kuhse <hartmutkuhse src gnome org>
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_METADATA_H__
+#define __GIMP_METADATA_H__
+
+G_BEGIN_DECLS
+
+
+typedef enum
+{
+  GIMP_METADATA_SAVE_EXIF      = 1 << 0,
+  GIMP_METADATA_SAVE_XMP       = 1 << 1,
+  GIMP_METADATA_SAVE_IPTC      = 1 << 2,
+  GIMP_METADATA_SAVE_THUMBNAIL = 1 << 3,
+
+  GIMP_METADATA_SAVE_ALL       = (GIMP_METADATA_SAVE_EXIF     |
+                                  GIMP_METADATA_SAVE_XMP      |
+                                  GIMP_METADATA_SAVE_IPTC     |
+                                  GIMP_METADATA_SAVE_THUMBNAIL)
+} GimpMetadataSaveFlags;
+
+
+GimpMetadata * gimp_metadata_deserialize         (const gchar   *metadata_string);
+gchar        * gimp_metadata_serialize           (GimpMetadata  *metadata);
+
+GimpMetadata * gimp_metadata_load_from_file      (GFile         *file,
+                                                  GError       **error);
+gboolean       gimp_metadata_save_to_file        (GimpMetadata  *metadata,
+                                                  GFile         *file,
+                                                  GError       **error);
+
+void           gimp_metadata_set_pixel_size      (GimpMetadata  *metadata,
+                                                  gint           width,
+                                                  gint           height);
+void           gimp_metadata_set_bits_per_sample (GimpMetadata  *metadata,
+                                                  gint           bits_per_sample);
+
+gboolean       gimp_metadata_get_resolution      (GimpMetadata  *metadata,
+                                                  gdouble       *xres,
+                                                  gdouble       *yres,
+                                                  GimpUnit      *unit);
+void           gimp_metadata_set_resolution      (GimpMetadata  *metadata,
+                                                  gdouble        xres,
+                                                  gdouble        yres,
+                                                  GimpUnit       unit);
+
+gboolean       gimp_metadata_is_tag_supported    (const gchar   *tag,
+                                                  const gchar   *mime_type);
+
+
+G_END_DECLS
+
+#endif /* __GIMP_METADATA_H__ */
diff --git a/plug-ins/Makefile.am b/plug-ins/Makefile.am
index 50eb645..38cc20f 100644
--- a/plug-ins/Makefile.am
+++ b/plug-ins/Makefile.am
@@ -51,11 +51,6 @@ else
 file_uri = file-uri
 endif
 
-if HAVE_LIBEXIF
-metadata = metadata
-endif
-
-
 SUBDIRS = \
        $(script_fu)            \
        $(pygimp)               \
@@ -83,7 +78,6 @@ SUBDIRS = \
        lighting                \
        map-object              \
        maze                    \
-       $(metadata)             \
        pagecurl                \
        $(print)                \
        selection-to-path       \
diff --git a/plug-ins/common/.gitignore b/plug-ins/common/.gitignore
index 6cb37a8..917b863 100644
--- a/plug-ins/common/.gitignore
+++ b/plug-ins/common/.gitignore
@@ -182,6 +182,8 @@
 /mail.exe
 /max-rgb
 /max-rgb.exe
+/metadata
+/metadata.exe
 /newsprint
 /newsprint.exe
 /nl-filter
diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am
index 441aae7..6da3ca3 100644
--- a/plug-ins/common/Makefile.am
+++ b/plug-ins/common/Makefile.am
@@ -134,6 +134,7 @@ libexec_PROGRAMS = \
        lens-flare \
        $(MAIL) \
        max-rgb \
+       metadata \
        newsprint \
        nl-filter \
        noise-rgb \
@@ -1773,6 +1774,26 @@ max_rgb_LDADD = \
        $(INTLLIBS)             \
        $(max_rgb_RC)
 
+metadata_CFLAGS = $(GEXIV2_CFLAGS)
+
+metadata_SOURCES = \
+       metadata.c
+
+metadata_LDADD = \
+       $(libgimpui)            \
+       $(libgimpwidgets)       \
+       $(libgimpmodule)        \
+       $(libgimp)              \
+       $(libgimpmath)          \
+       $(libgimpconfig)        \
+       $(libgimpcolor)         \
+       $(libgimpbase)          \
+       $(GTK_LIBS)             \
+       $(GEXIV2_LIBS)          \
+       $(RT_LIBS)              \
+       $(INTLLIBS)             \
+       $(metadata_RC)
+
 newsprint_SOURCES = \
        newsprint.c
 
diff --git a/plug-ins/common/file-png.c b/plug-ins/common/file-png.c
index f6f8a60..c33a16a 100644
--- a/plug-ins/common/file-png.c
+++ b/plug-ins/common/file-png.c
@@ -88,6 +88,10 @@ typedef struct
   gboolean  comment;
   gboolean  save_transp_pixels;
   gint      compression_level;
+  gboolean  save_exif;
+  gboolean  save_xmp;
+  gboolean  save_iptc;
+  gboolean  save_thumbnail;
 }
 PngSaveVals;
 
@@ -104,6 +108,10 @@ typedef struct
   GtkWidget *comment;
   GtkWidget *save_transp_pixels;
   GtkAdjustment *compression_level;
+  GtkWidget *save_exif;
+  GtkWidget *save_xmp;
+  GtkWidget *save_iptc;
+  GtkWidget *save_thumbnail;
 }
 PngSaveGui;
 
@@ -119,6 +127,7 @@ typedef struct
 }
 PngGlobals;
 
+
 /*
  * Local functions...
  */
@@ -164,6 +173,7 @@ static void      load_defaults             (void);
 static void      save_defaults             (void);
 static void      load_gui_defaults         (PngSaveGui       *pg);
 
+
 /*
  * Globals...
  */
@@ -186,11 +196,15 @@ static const PngSaveVals defaults =
   TRUE,
   TRUE,
   TRUE,
-  9
+  9,
+  TRUE,                /* save exif       */
+  TRUE,                /* save xmp        */
+  TRUE,                /* save iptc        */
+  TRUE                 /* save thumbnail  */
 };
 
 static PngSaveVals pngvals;
-static PngGlobals pngg;
+static PngGlobals  pngg;
 
 
 /*
@@ -419,11 +433,21 @@ run (const gchar      *name,
     {
       run_mode = param[0].data.d_int32;
 
+      if (run_mode == GIMP_RUN_INTERACTIVE)
+        gimp_ui_init (PLUG_IN_BINARY, FALSE);
+
       image_ID = load_image (param[1].data.d_string,
                              run_mode == GIMP_RUN_INTERACTIVE, &error);
 
       if (image_ID != -1)
         {
+          GFile *file = g_file_new_for_path (param[1].data.d_string);
+
+          gimp_image_metadata_load (image_ID, "image/png", file,
+                                    run_mode == GIMP_RUN_INTERACTIVE);
+
+          g_object_unref (file);
+
           *nreturn_vals = 2;
           values[1].type = GIMP_PDB_IMAGE;
           values[1].data.d_image = image_ID;
@@ -549,6 +573,33 @@ run (const gchar      *name,
           if (save_image (param[3].data.d_string,
                           image_ID, drawable_ID, orig_image_ID, &error))
             {
+              GimpMetadata *metadata;
+
+              metadata = gimp_image_metadata_save_prepare (image_ID,
+                                                           "image/png");
+
+              if (metadata)
+                {
+                  GFile                 *file;
+                  GimpMetadataSaveFlags  flags = 0;
+
+                  gimp_metadata_set_bits_per_sample (metadata, 8);
+
+                  if (pngvals.save_exif)      flags |= GIMP_METADATA_SAVE_EXIF;
+                  if (pngvals.save_xmp)       flags |= GIMP_METADATA_SAVE_XMP;
+                  if (pngvals.save_iptc)      flags |= GIMP_METADATA_SAVE_IPTC;
+                  if (pngvals.save_thumbnail) flags |= GIMP_METADATA_SAVE_THUMBNAIL;
+
+                  file = g_file_new_for_path (param[3].data.d_string);
+                  gimp_image_metadata_save_finish (image_ID,
+                                                   "image/png",
+                                                   metadata, file, flags,
+                                                   NULL);
+                  g_object_unref (file);
+
+                  g_object_unref (metadata);
+                }
+
               gimp_set_data (SAVE_PROC, &pngvals, sizeof (pngvals));
             }
           else
@@ -587,6 +638,8 @@ run (const gchar      *name,
     {
       if (nparams == 9)
         {
+          load_defaults ();
+
           pngvals.interlaced          = param[0].data.d_int32;
           pngvals.compression_level   = param[1].data.d_int32;
           pngvals.bkgd                = param[2].data.d_int32;
@@ -2080,6 +2133,18 @@ save_dialog (gint32    image_ID,
   pg.time = toggle_button_init (builder, "save-creation-time",
                                 pngvals.time,
                                 &pngvals.time);
+  pg.save_exif = toggle_button_init (builder, "sv_exif",
+                                     pngvals.save_exif,
+                                     &pngvals.save_exif);
+  pg.save_xmp = toggle_button_init (builder, "sv_xmp",
+                                    pngvals.save_xmp,
+                                    &pngvals.save_xmp);
+  pg.save_iptc = toggle_button_init (builder, "sv_iptc",
+                                     pngvals.save_iptc,
+                                     &pngvals.save_iptc);
+  pg.save_thumbnail = toggle_button_init (builder, "sv_thumbnail",
+                                          pngvals.save_thumbnail,
+                                          &pngvals.save_thumbnail);
 
   /* Comment toggle */
   parasite = gimp_image_get_parasite (image_ID, "gimp-comment");
@@ -2150,12 +2215,15 @@ load_defaults (void)
 {
   GimpParasite *parasite;
 
+  /* initialize with hardcoded defaults */
+  pngvals = defaults;
+
   parasite = gimp_get_parasite (PNG_DEFAULTS_PARASITE);
 
   if (parasite)
     {
       gchar        *def_str;
-      PngSaveVals   tmpvals;
+      PngSaveVals   tmpvals = defaults;
       gint          num_fields;
 
       def_str = g_strndup (gimp_parasite_data (parasite),
@@ -2163,7 +2231,7 @@ load_defaults (void)
 
       gimp_parasite_free (parasite);
 
-      num_fields = sscanf (def_str, "%d %d %d %d %d %d %d %d %d",
+      num_fields = sscanf (def_str, "%d %d %d %d %d %d %d %d %d %d %d %d %d",
                            &tmpvals.interlaced,
                            &tmpvals.bkgd,
                            &tmpvals.gama,
@@ -2172,18 +2240,17 @@ load_defaults (void)
                            &tmpvals.time,
                            &tmpvals.comment,
                            &tmpvals.save_transp_pixels,
-                           &tmpvals.compression_level);
+                           &tmpvals.compression_level,
+                           &tmpvals.save_exif,
+                           &tmpvals.save_xmp,
+                           &tmpvals.save_iptc,
+                           &tmpvals.save_thumbnail);
 
       g_free (def_str);
 
-      if (num_fields == 9)
-        {
-          memcpy (&pngvals, &tmpvals, sizeof (tmpvals));
-          return;
-        }
+      if (num_fields == 9 || num_fields == 13)
+        pngvals = tmpvals;
     }
-
-  memcpy (&pngvals, &defaults, sizeof (defaults));
 }
 
 static void
@@ -2192,7 +2259,7 @@ save_defaults (void)
   GimpParasite *parasite;
   gchar        *def_str;
 
-  def_str = g_strdup_printf ("%d %d %d %d %d %d %d %d %d",
+  def_str = g_strdup_printf ("%d %d %d %d %d %d %d %d %d %d %d %d %d",
                              pngvals.interlaced,
                              pngvals.bkgd,
                              pngvals.gama,
@@ -2201,7 +2268,11 @@ save_defaults (void)
                              pngvals.time,
                              pngvals.comment,
                              pngvals.save_transp_pixels,
-                             pngvals.compression_level);
+                             pngvals.compression_level,
+                             pngvals.save_exif,
+                             pngvals.save_xmp,
+                             pngvals.save_iptc,
+                             pngvals.save_thumbnail);
 
   parasite = gimp_parasite_new (PNG_DEFAULTS_PARASITE,
                                 GIMP_PARASITE_PERSISTENT,
@@ -2230,6 +2301,10 @@ load_gui_defaults (PngSaveGui *pg)
   SET_ACTIVE (time);
   SET_ACTIVE (comment);
   SET_ACTIVE (save_transp_pixels);
+  SET_ACTIVE (save_exif);
+  SET_ACTIVE (save_xmp);
+  SET_ACTIVE (save_iptc);
+  SET_ACTIVE (save_thumbnail);
 
 #undef SET_ACTIVE
 
diff --git a/plug-ins/common/file-tiff-load.c b/plug-ins/common/file-tiff-load.c
index b5d966c..f0eb875 100644
--- a/plug-ins/common/file-tiff-load.c
+++ b/plug-ins/common/file-tiff-load.c
@@ -280,6 +280,13 @@ run (const gchar      *name,
 
                   if (image != -1)
                     {
+                      GFile *file = g_file_new_for_path (param[1].data.d_string);
+
+                      gimp_image_metadata_load (image, "image/tiff", file,
+                                                run_mode == GIMP_RUN_INTERACTIVE);
+
+                      g_object_unref (file);
+
                       *nreturn_vals = 2;
                       values[1].type         = GIMP_PDB_IMAGE;
                       values[1].data.d_image = image;
diff --git a/plug-ins/common/file-tiff-save.c b/plug-ins/common/file-tiff-save.c
index fe5ba46..7e5e029 100644
--- a/plug-ins/common/file-tiff-save.c
+++ b/plug-ins/common/file-tiff-save.c
@@ -82,6 +82,10 @@ typedef struct
   gint      compression;
   gint      fillorder;
   gboolean  save_transp_pixels;
+  gboolean  save_exif;
+  gboolean  save_xmp;
+  gboolean  save_iptc;
+  gboolean  save_thumbnail;
 } TiffSaveVals;
 
 typedef struct
@@ -110,6 +114,7 @@ static gboolean  save_image             (const gchar  *filename,
                                          gint32        image,
                                          gint32        drawable,
                                          gint32        orig_image,
+                                         gint         *saved_bpp,
                                          GError      **error);
 
 static gboolean  save_dialog            (gboolean      has_alpha,
@@ -143,8 +148,13 @@ const GimpPlugInInfo PLUG_IN_INFO =
 
 static TiffSaveVals tsvals =
 {
-  COMPRESSION_NONE,    /*  compression    */
-  TRUE,                /*  alpha handling */
+  COMPRESSION_NONE,    /*  compression         */
+  TRUE,                /*  alpha handling      */
+  TRUE,                /*  save transp. pixels */
+  TRUE,                /*  save exif           */
+  TRUE,                /*  save xmp            */
+  TRUE,                /*  save iptc           */
+  TRUE                 /*  save thumbnail      */
 };
 
 static gchar       *image_comment = NULL;
@@ -346,9 +356,38 @@ run (const gchar      *name,
 
       if (status == GIMP_PDB_SUCCESS)
         {
+          gint saved_bpp;
+
           if (save_image (param[3].data.d_string, image, drawable, orig_image,
-                          &error))
+                          &saved_bpp, &error))
             {
+              GimpMetadata *metadata;
+
+              metadata = gimp_image_metadata_save_prepare (image,
+                                                           "image/tiff");
+
+              if (metadata)
+                {
+                  GFile                 *file;
+                  GimpMetadataSaveFlags  flags = 0;
+
+                  gimp_metadata_set_bits_per_sample (metadata, saved_bpp);
+
+                  if (tsvals.save_exif)      flags |= GIMP_METADATA_SAVE_EXIF;
+                  if (tsvals.save_xmp)       flags |= GIMP_METADATA_SAVE_XMP;
+                  if (tsvals.save_iptc)      flags |= GIMP_METADATA_SAVE_IPTC;
+                  if (tsvals.save_thumbnail) flags |= GIMP_METADATA_SAVE_THUMBNAIL;
+
+                  file = g_file_new_for_path (param[3].data.d_string);
+                  gimp_image_metadata_save_finish (image,
+                                                   "image/tiff",
+                                                   metadata, file, flags,
+                                                   NULL);
+                  g_object_unref (file);
+
+                  g_object_unref (metadata);
+                }
+
               /*  Store mvals data  */
               gimp_set_data (SAVE_PROC, &tsvals, sizeof (TiffSaveVals));
             }
@@ -657,6 +696,7 @@ save_image (const gchar  *filename,
             gint32        image,
             gint32        layer,
             gint32        orig_image,  /* the export function might have */
+            gint         *saved_bpp,
             GError      **error)       /* created a duplicate            */
 {
   gboolean       status = FALSE;
@@ -725,6 +765,8 @@ save_image (const gchar  *filename,
   else
     bitspersample = 16;
 
+  *saved_bpp = bitspersample;
+
   drawable_type = gimp_drawable_type (layer);
   buffer = gimp_drawable_get_buffer (layer);
 
@@ -1075,25 +1117,44 @@ static gboolean
 save_dialog (gboolean has_alpha,
              gboolean is_monochrome)
 {
-  GtkWidget *dialog;
-  GtkWidget *vbox;
-  GtkWidget *frame;
-  GtkWidget *hbox;
-  GtkWidget *label;
-  GtkWidget *entry;
-  GtkWidget *toggle;
-  GtkWidget *g3;
-  GtkWidget *g4;
-  gboolean   run;
+  GError      *error = NULL;
+  GtkWidget   *dialog;
+  GtkWidget   *vbox;
+  GtkWidget   *frame;
+  GtkWidget   *entry;
+  GtkWidget   *toggle;
+  GtkWidget   *cmp_g3;
+  GtkWidget   *cmp_g4;
+  GtkBuilder  *builder;
+  gchar       *ui_file;
+  gboolean     run;
 
   dialog = gimp_export_dialog_new (_("TIFF"), PLUG_IN_BINARY, SAVE_PROC);
 
-  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
-  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+  builder = gtk_builder_new ();
+  ui_file = g_build_filename (gimp_data_directory (),
+                              "ui", "plug-ins", "plug-in-file-tiff.ui",
+                              NULL);
+  if (! gtk_builder_add_from_file (builder, ui_file, &error))
+    {
+      gchar *display_name = g_filename_display_name (ui_file);
+
+      g_printerr (_("Error loading UI file '%s': %s"),
+                  display_name, error ? error->message : _("Unknown error"));
+
+      g_free (display_name);
+    }
+
+  g_free (ui_file);
+
+  vbox = GTK_WIDGET (gtk_builder_get_object (builder, "tiff_export_vbox"));
+
   gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
-                      vbox, FALSE, TRUE, 0);
+                      vbox, FALSE, FALSE, 0);
+  gtk_widget_show (vbox);
+
+  vbox = GTK_WIDGET (gtk_builder_get_object (builder, "radio_button_box"));
 
-  /*  compression  */
   frame = gimp_int_radio_group_new (TRUE, _("Compression"),
                                     G_CALLBACK (gimp_radio_button_update),
                                     &tsvals.compression, tsvals.compression,
@@ -1103,20 +1164,20 @@ save_dialog (gboolean has_alpha,
                                     _("_Pack Bits"), COMPRESSION_PACKBITS,      NULL,
                                     _("_Deflate"),   COMPRESSION_ADOBE_DEFLATE, NULL,
                                     _("_JPEG"),      COMPRESSION_JPEG,          NULL,
-                                    _("CCITT Group _3 fax"), COMPRESSION_CCITTFAX3, &g3,
-                                    _("CCITT Group _4 fax"), COMPRESSION_CCITTFAX4, &g4,
+                                    _("CCITT Group _3 fax"), COMPRESSION_CCITTFAX3, &cmp_g3,
+                                    _("CCITT Group _4 fax"), COMPRESSION_CCITTFAX4, &cmp_g4,
 
                                     NULL);
 
-  gtk_widget_set_sensitive (g3, is_monochrome);
-  gtk_widget_set_sensitive (g4, is_monochrome);
+  gtk_widget_set_sensitive (cmp_g3, is_monochrome);
+  gtk_widget_set_sensitive (cmp_g4, is_monochrome);
 
   if (! is_monochrome)
     {
       if (tsvals.compression == COMPRESSION_CCITTFAX3 ||
           tsvals.compression ==  COMPRESSION_CCITTFAX4)
         {
-          gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (g3),
+          gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (cmp_g3),
                                            COMPRESSION_NONE);
         }
     }
@@ -1124,40 +1185,49 @@ save_dialog (gboolean has_alpha,
   gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
   gtk_widget_show (frame);
 
-  /* Keep colors behind alpha mask */
-  toggle = gtk_check_button_new_with_mnemonic
-    ( _("Save _color values from transparent pixels"));
+  toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_alpha"));
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
                                 has_alpha && tsvals.save_transp_pixels);
   gtk_widget_set_sensitive (toggle, has_alpha);
-  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
-  gtk_widget_show (toggle);
-
   g_signal_connect (toggle, "toggled",
                     G_CALLBACK (gimp_toggle_button_update),
                     &tsvals.save_transp_pixels);
 
-  /* comment entry */
-  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
-  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
-  gtk_widget_show (hbox);
-
-  label = gtk_label_new ( _("Comment:"));
-  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-  gtk_widget_show (label);
-
-  entry = gtk_entry_new ();
-  gtk_widget_show (entry);
-  gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
+  entry = GTK_WIDGET (gtk_builder_get_object (builder, "commentfield"));
   gtk_entry_set_text (GTK_ENTRY (entry), image_comment ? image_comment : "");
 
   g_signal_connect (entry, "changed",
                     G_CALLBACK (comment_entry_callback),
                     NULL);
 
-  gtk_widget_show (frame);
+  toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_exif"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+                                tsvals.save_exif);
+  g_signal_connect (toggle, "toggled",
+                    G_CALLBACK (gimp_toggle_button_update),
+                    &tsvals.save_exif);
+
+  toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_xmp"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+                                tsvals.save_xmp);
+  g_signal_connect (toggle, "toggled",
+                    G_CALLBACK (gimp_toggle_button_update),
+                    &tsvals.save_xmp);
+
+  toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_iptc"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+                                tsvals.save_iptc);
+  g_signal_connect (toggle, "toggled",
+                    G_CALLBACK (gimp_toggle_button_update),
+                    &tsvals.save_iptc);
+
+  toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_thumbnail"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+                                tsvals.save_thumbnail);
+  g_signal_connect (toggle, "toggled",
+                    G_CALLBACK (gimp_toggle_button_update),
+                    &tsvals.save_thumbnail);
 
-  gtk_widget_show (vbox);
   gtk_widget_show (dialog);
 
   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
diff --git a/plug-ins/common/gimprc.common b/plug-ins/common/gimprc.common
index 3a4bae5..59c8454 100644
--- a/plug-ins/common/gimprc.common
+++ b/plug-ins/common/gimprc.common
@@ -88,6 +88,7 @@ lens_apply_RC = lens-apply.rc.o
 lens_flare_RC = lens-flare.rc.o
 mail_RC = mail.rc.o
 max_rgb_RC = max-rgb.rc.o
+metadata_RC = metadata.rc.o
 newsprint_RC = newsprint.rc.o
 nl_filter_RC = nl-filter.rc.o
 noise_rgb_RC = noise-rgb.rc.o
diff --git a/plug-ins/common/metadata.c b/plug-ins/common/metadata.c
new file mode 100644
index 0000000..e94cbae
--- /dev/null
+++ b/plug-ins/common/metadata.c
@@ -0,0 +1,382 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * metadata.c
+ * Copyright (C) 2013 Hartmut Kuhse
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gexiv2/gexiv2.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+
+#define PLUG_IN_PROC   "plug-in-metadata-editor"
+#define PLUG_IN_BINARY "metadata"
+#define PLUG_IN_ROLE   "gimp-metadata"
+
+#define EXIF_PREFIX "Exif."
+#define IPTC_PREFIX "Iptc."
+#define XMP_PREFIX  "Xmp."
+
+enum
+{
+  C_XMP_TAG = 0,
+  C_XMP_VALUE,
+  NUM_XMP_COLS
+};
+
+enum
+{
+  C_EXIF_TAG = 0,
+  C_EXIF_VALUE,
+  NUM_EXIF_COLS
+};
+
+
+typedef struct
+{
+  gchar *tag;
+  gchar *mode;
+} iptc_tag;
+
+
+/*  local function prototypes  */
+
+static void       query                         (void);
+static void       run                           (const gchar      *name,
+                                                 gint              nparams,
+                                                 const GimpParam  *param,
+                                                 gint             *nreturn_vals,
+                                                 GimpParam       **return_vals);
+
+static gboolean   metadata_dialog               (gint32          image_id,
+                                                 GExiv2Metadata *metadata);
+
+static void       metadata_dialog_set_metadata  (GExiv2Metadata *metadata,
+                                                 GtkBuilder     *builder);
+
+static void       metadata_dialog_iptc_callback (GtkWidget      *dialog,
+                                                 GtkBuilder     *builder);
+
+
+/* local variables */
+
+const GimpPlugInInfo PLUG_IN_INFO =
+{
+  NULL,  /* init_proc  */
+  NULL,  /* quit_proc  */
+  query, /* query_proc */
+  run,   /* run_proc   */
+};
+
+static const iptc_tag const iptc_tags[] =
+{
+  { "Iptc.Application2.Byline",                  "single" },
+  { "Iptc.Application2.BylineTitle",             "single" },
+  { "Iptc.Application2.Caption",                 "multi"  },
+  { "Iptc.Application2.Category",                "single" },
+  { "Iptc.Application2.City",                    "single" },
+  { "Iptc.Application2.Copyright",               "single" },
+  { "Iptc.Application2.CountryName",             "single" },
+  { "Iptc.Application2.Credit",                  "single" },
+  { "Iptc.Application2.Headline",                "multi"  },
+  { "Iptc.Application2.Keywords",                "multi"  },
+  { "Iptc.Application2.ObjectName",              "single" },
+  { "Iptc.Application2.ProvinceState",           "single" },
+  { "Iptc.Application2.Source",                  "single" },
+  { "Iptc.Application2.SpecialInstructions",     "multi"  },
+  { "Iptc.Application2.SubLocation",             "single" },
+  { "Iptc.Application2.SuppCategory",            "multi"  },
+  { "Iptc.Application2.TransmissionReference",   "single" },
+  { "Iptc.Application2.Urgency",                 "single" },
+  { "Iptc.Application2.Writer",                  "single" }
+};
+
+
+/*  functions  */
+
+MAIN ()
+
+static void
+query (void)
+{
+  static const GimpParamDef metadata_args[] =
+  {
+    { GIMP_PDB_INT32, "run-mode", "Run mode { RUN-INTERACTIVE (0) }" },
+    { GIMP_PDB_IMAGE, "image",    "Input image"                      }
+  };
+
+  gimp_install_procedure (PLUG_IN_PROC,
+                          N_("View and edit metadata (EXIF, IPTC, XMP)"),
+                          "View and edit metadata information attached to the "
+                          "current image.  This can include EXIF, IPTC and/or "
+                          "XMP information.  Some or all of this metadata "
+                          "will be saved in the file, depending on the output "
+                          "file format.",
+                          "Hartmut Kuhse, Michael Natterer",
+                          "Hartmut Kuhse, Michael Natterer",
+                          "2013",
+                          N_("Show Metadata"),
+                          "*",
+                          GIMP_PLUGIN,
+                          G_N_ELEMENTS (metadata_args), 0,
+                          metadata_args, NULL);
+
+  gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/File/Info");
+}
+
+static void
+run (const gchar      *name,
+     gint              nparams,
+     const GimpParam  *param,
+     gint             *nreturn_vals,
+     GimpParam       **return_vals)
+{
+  static GimpParam   values[1];
+  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
+
+  *nreturn_vals = 1;
+  *return_vals  = values;
+
+  values[0].type          = GIMP_PDB_STATUS;
+  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
+
+  INIT_I18N();
+  gimp_ui_init (PLUG_IN_BINARY, TRUE);
+
+  if (! strcmp (name, PLUG_IN_PROC))
+    {
+      GimpMetadata *metadata;
+      gint32        image_ID = param[1].data.d_image;
+
+      metadata = gimp_image_get_metadata (image_ID);
+
+      if (metadata)
+        {
+          metadata_dialog (image_ID, metadata);
+        }
+      else
+        {
+          g_message (_("This image has no metadata attached to it."));
+        }
+
+      status = GIMP_PDB_SUCCESS;
+    }
+  else
+    {
+      status = GIMP_PDB_CALLING_ERROR;
+    }
+
+  values[0].data.d_status = status;
+}
+
+static gboolean
+metadata_dialog (gint32          image_id,
+                 GExiv2Metadata *metadata)
+{
+  GtkBuilder *builder;
+  GtkWidget  *dialog;
+  GtkWidget  *metadata_vbox;
+  GtkWidget  *content_area;
+  GObject    *write_button;
+  gchar      *ui_file;
+  gchar      *title;
+  gchar      *fname;
+  GError     *error = NULL;
+
+  builder = gtk_builder_new ();
+
+  ui_file = g_build_filename (gimp_data_directory (),
+                              "ui", "plug-ins", "plug-in-metadata.ui", NULL);
+
+  if (! gtk_builder_add_from_file (builder, ui_file, &error))
+    {
+      g_printerr ("Error occured while loading UI file!\n");
+      g_printerr ("Message: %s\n", error->message);
+      g_clear_error (&error);
+      g_free (ui_file);
+      g_object_unref (builder);
+      return FALSE;
+    }
+
+  g_free (ui_file);
+  fname = g_filename_display_basename (gimp_image_get_uri (image_id));
+  title = g_strdup_printf ("Metadata: %s", fname);
+  g_free (fname);
+
+  dialog = gimp_dialog_new (title,
+                            "gimp-metadata-dialog",
+                            NULL, 0,
+                            gimp_standard_help_func, PLUG_IN_PROC,
+
+                            GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+
+                            NULL);
+
+  g_free (title);
+
+  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+                                           GTK_RESPONSE_CLOSE,
+                                           -1);
+
+  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+  metadata_vbox = GTK_WIDGET (gtk_builder_get_object (builder,
+                                                      "metadata-vbox"));
+  gtk_container_set_border_width (GTK_CONTAINER (metadata_vbox), 12);
+  gtk_box_pack_start (GTK_BOX (content_area), metadata_vbox, TRUE, TRUE, 0);
+
+  write_button = gtk_builder_get_object (builder, "iptc-write-button");
+
+  g_signal_connect (write_button, "clicked",
+                    G_CALLBACK (metadata_dialog_iptc_callback),
+                    builder);
+
+  metadata_dialog_set_metadata (metadata, builder);
+
+  gtk_dialog_run (GTK_DIALOG (dialog));
+
+  return TRUE;
+}
+
+
+/*  private functions  */
+
+static void
+metadata_dialog_set_metadata (GExiv2Metadata *metadata,
+                              GtkBuilder     *builder)
+{
+  GtkListStore  *exif_store;
+  GtkListStore  *xmp_store;
+  gchar        **exif_data;
+  gchar        **iptc_data;
+  gchar        **xmp_data;
+  gchar         *value;
+  gchar         *value_utf;
+  GtkTreeIter    iter;
+  gint           i;
+
+  exif_store = GTK_LIST_STORE (gtk_builder_get_object (builder,
+                                                       "exif-liststore"));
+  xmp_store  = GTK_LIST_STORE (gtk_builder_get_object (builder,
+                                                       "xmp-liststore"));
+
+  exif_data = gexiv2_metadata_get_exif_tags (metadata);
+
+  for (i = 0; exif_data[i] != NULL; i++)
+    {
+      gtk_list_store_append (exif_store, &iter);
+      value = gexiv2_metadata_get_tag_interpreted_string (metadata,
+                                                          exif_data[i]);
+      value_utf = g_locale_to_utf8 (value, -1, NULL, NULL, NULL);
+
+      gtk_list_store_set (exif_store, &iter,
+                          C_EXIF_TAG,   exif_data[i],
+                          C_EXIF_VALUE, value_utf,
+                          -1);
+    }
+
+  xmp_data = gexiv2_metadata_get_xmp_tags (metadata);
+
+  for (i = 0; xmp_data[i] != NULL; i++)
+    {
+      gtk_list_store_append (xmp_store, &iter);
+      value = gexiv2_metadata_get_tag_interpreted_string (metadata,
+                                                          xmp_data[i]);
+      value_utf = g_locale_to_utf8 (value, -1, NULL, NULL, NULL);
+
+      gtk_list_store_set (xmp_store, &iter,
+                          C_XMP_TAG,   xmp_data[i],
+                          C_XMP_VALUE, value_utf,
+                          -1);
+    }
+
+  iptc_data = gexiv2_metadata_get_iptc_tags (metadata);
+
+  for (i = 0; iptc_data[i] != NULL; i++)
+    {
+      GtkWidget *widget;
+
+      widget = GTK_WIDGET (gtk_builder_get_object (builder, iptc_data[i]));
+      value = gexiv2_metadata_get_tag_interpreted_string (metadata,
+                                                          iptc_data[i]);
+      value_utf = g_locale_to_utf8 (value, -1, NULL, NULL, NULL);
+
+      if (GTK_IS_ENTRY (widget))
+        {
+          GtkEntry *entry_widget = GTK_ENTRY (widget);
+
+          gtk_entry_set_text (entry_widget, value_utf);
+        }
+      else if (GTK_IS_TEXT_VIEW (widget))
+        {
+          GtkTextView   *text_view = GTK_TEXT_VIEW (widget);
+          GtkTextBuffer *buffer;
+
+          buffer = gtk_text_view_get_buffer (text_view);
+          gtk_text_buffer_set_text (buffer, value_utf, -1);
+        }
+    }
+}
+
+static void
+metadata_dialog_iptc_callback (GtkWidget  *dialog,
+                               GtkBuilder *builder)
+{
+#if 0
+  GExiv2Metadata *metadata;
+  gint            i;
+
+  metadata = gimp_image_get_metadata (handler->image);
+
+  for (i = 0; i < G_N_ELEMENTS (iptc_tags); i++)
+    {
+      GObject *object = gtk_builder_get_object (handler->builder,
+                                                iptc_tags[i].tag);
+
+      if (! strcmp ("single", iptc_tags[i].mode))
+        {
+          GtkEntry *entry = GTK_ENTRY (object);
+
+          gexiv2_metadata_set_tag_string (metadata, iptc_tags[i].tag,
+                                          gtk_entry_get_text (entry));
+        }
+      else  if (!strcmp ("multi", iptc_tags[i].mode))
+        {
+          GtkTextView   *text_view = GTK_TEXT_VIEW (object);
+          GtkTextBuffer *buffer;
+          GtkTextIter    start;
+          GtkTextIter    end;
+          gchar         *text;
+
+          buffer = gtk_text_view_get_buffer (text_view);
+          gtk_text_buffer_get_start_iter (buffer, &start);
+          gtk_text_buffer_get_end_iter (buffer, &end);
+
+          text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
+          gexiv2_metadata_set_tag_string (metadata, iptc_tags[i].tag, text);
+          g_free (text);
+        }
+    }
+#endif
+}
diff --git a/plug-ins/common/plugin-defs.pl b/plug-ins/common/plugin-defs.pl
index 5722c4e..9456e23 100644
--- a/plug-ins/common/plugin-defs.pl
+++ b/plug-ins/common/plugin-defs.pl
@@ -89,6 +89,7 @@
     'lens-flare' => { ui => 1 },
     'mail' => { ui => 1, optional => 1 },
     'max-rgb' => { ui => 1 },
+    'metadata' => { ui => 1, libs => 'GEXIV2_LIBS', cflags => 'GEXIV2_CFLAGS' },
     'newsprint' => { ui => 1 },
     'nl-filter' => { ui => 1 },
     'noise-rgb' => { ui => 1 },
diff --git a/plug-ins/file-jpeg/Makefile.am b/plug-ins/file-jpeg/Makefile.am
index 5b3f2ae..c53d1af 100644
--- a/plug-ins/file-jpeg/Makefile.am
+++ b/plug-ins/file-jpeg/Makefile.am
@@ -22,11 +22,12 @@ AM_LDFLAGS = $(mwindows)
 libexecdir = $(gimpplugindir)/plug-ins
 
 AM_CPPFLAGS = \
-       -I$(top_srcdir) \
-       $(GTK_CFLAGS)   \
-       $(EXIF_CFLAGS)  \
-       $(LCMS_CFLAGS)  \
-       $(GEGL_CFLAGS) \
+       -I$(top_srcdir)         \
+       $(GTK_CFLAGS)           \
+       $(EXIF_CFLAGS)          \
+       $(LCMS_CFLAGS)          \
+       $(GEGL_CFLAGS)          \
+       $(GEXIV2_CFLAGS)        \
        -I$(includedir)
 
 libexec_PROGRAMS = file-jpeg
@@ -45,14 +46,6 @@ file_jpeg_SOURCES = \
        jpeg-settings.c \
        jpeg-settings.h
 
-if HAVE_LIBEXIF
-file_jpeg_SOURCES += \
-       jpeg-exif.c     \
-       jpeg-exif.h     \
-       gimpexif.c      \
-       gimpexif.h
-endif
-
 file_jpeg_LDADD = \
        $(libgimpui)            \
        $(libgimpwidgets)       \
@@ -65,15 +58,11 @@ file_jpeg_LDADD = \
        $(LCMS_LIBS)            \
        $(GTK_LIBS)             \
        $(GEGL_LIBS)            \
+       $(GEXIV2_LIBS)          \
        $(RT_LIBS)              \
        $(INTLLIBS)             \
        $(file_jpeg_RC)
 
-if HAVE_LIBEXIF
-file_jpeg_LDADD += \
-       $(EXIF_LIBS)
-endif
-
 noinst_PROGRAMS = jpegqual
 
 jpegqual_SOURCES = \
diff --git a/plug-ins/file-jpeg/jpeg-load.c b/plug-ins/file-jpeg/jpeg-load.c
index 41cd2e4..6a7ace6 100644
--- a/plug-ins/file-jpeg/jpeg-load.c
+++ b/plug-ins/file-jpeg/jpeg-load.c
@@ -21,15 +21,13 @@
 #include <errno.h>
 #include <setjmp.h>
 
+#include <gio/gio.h>
 #include <glib/gstdio.h>
+#include <gexiv2/gexiv2.h>
 
 #include <jpeglib.h>
 #include <jerror.h>
 
-#ifdef HAVE_LIBEXIF
-#include <libexif/exif-data.h>
-#endif /* HAVE_LIBEXIF */
-
 #ifdef HAVE_LCMS
 #include <lcms2.h>
 #endif
@@ -43,21 +41,11 @@
 #include "jpeg-icc.h"
 #include "jpeg-settings.h"
 #include "jpeg-load.h"
-#ifdef HAVE_LIBEXIF
-#include "jpeg-exif.h"
-#include "gimpexif.h"
-#endif
-
 
 static void  jpeg_load_resolution           (gint32    image_ID,
                                              struct jpeg_decompress_struct
                                                        *cinfo);
 
-#ifdef HAVE_LIBEXIF
-static gboolean  jpeg_load_exif_resolution  (gint32    image_ID,
-                                             ExifData *exif_data);
-#endif
-
 static void      jpeg_load_sanitize_comment (gchar    *comment);
 
 static gpointer  jpeg_load_cmyk_transform   (guint8   *profile_data,
@@ -90,9 +78,6 @@ load_image (const gchar  *filename,
   gint             tile_height;
   gint             scanlines;
   gint             i, start, end;
-#ifdef HAVE_LIBEXIF
-  gint             orientation = 0;
-#endif
 #ifdef HAVE_LCMS
   cmsHTRANSFORM    cmyk_transform = NULL;
 #else
@@ -265,9 +250,6 @@ load_image (const gchar  *filename,
       GString  *comment_buffer = NULL;
       guint8   *profile        = NULL;
       guint     profile_size   = 0;
-#ifdef HAVE_LIBEXIF
-      ExifData *exif_data      = NULL;
-#endif
 
       /* Step 5.0: save the original JPEG settings in a parasite */
       jpeg_detect_original_settings (&cinfo, image_ID);
@@ -304,18 +286,9 @@ load_image (const gchar  *filename,
               g_print ("jpeg-load: found EXIF block (%d bytes)\n",
                        (gint) (len - sizeof (JPEG_APP_HEADER_EXIF)));
 #endif
-#ifdef HAVE_LIBEXIF
-              if (! exif_data)
-                exif_data = exif_data_new ();
-              /* if there are multiple blocks, their data will be merged */
-              exif_data_load_data (exif_data, (unsigned char *) data, len);
-#endif
             }
         }
 
-#ifdef HAVE_LIBEXIF
-      if (!jpeg_load_exif_resolution (image_ID, exif_data))
-#endif
         jpeg_load_resolution (image_ID, &cinfo);
 
       /* if we found any comments, then make a parasite for them */
@@ -334,55 +307,6 @@ load_image (const gchar  *filename,
           g_string_free (comment_buffer, TRUE);
         }
 
-#ifdef HAVE_LIBEXIF
-      /* if we found any EXIF block, then attach the metadata to the image */
-      if (exif_data)
-        {
-          gimp_metadata_store_exif (image_ID, exif_data);
-          orientation = jpeg_exif_get_orientation (exif_data);
-          exif_data_unref (exif_data);
-          exif_data = NULL;
-        }
-#endif
-
-      /* Step 5.2: check for XMP metadata in APP1 markers (after EXIF) */
-      for (marker = cinfo.marker_list; marker; marker = marker->next)
-        {
-          const gchar *data = (const gchar *) marker->data;
-          gsize        len  = marker->data_length;
-
-          if ((marker->marker == JPEG_APP0 + 1)
-              && (len > sizeof (JPEG_APP_HEADER_XMP) + 20)
-              && ! strcmp (JPEG_APP_HEADER_XMP, data))
-            {
-              GimpParam *return_vals;
-              gint       nreturn_vals;
-              gchar     *xmp_packet;
-
-#ifdef GIMP_UNSTABLE
-              g_print ("jpeg-load: found XMP packet (%d bytes)\n",
-                       (gint) (len - sizeof (JPEG_APP_HEADER_XMP)));
-#endif
-              xmp_packet = g_strndup (data + sizeof (JPEG_APP_HEADER_XMP),
-                                      len - sizeof (JPEG_APP_HEADER_XMP));
-
-              /* FIXME: running this through the PDB is not very efficient */
-              return_vals = gimp_run_procedure ("plug-in-metadata-decode-xmp",
-                                                &nreturn_vals,
-                                                GIMP_PDB_IMAGE, image_ID,
-                                                GIMP_PDB_STRING, xmp_packet,
-                                                GIMP_PDB_END);
-
-              if (return_vals[0].data.d_status != GIMP_PDB_SUCCESS)
-                {
-                  g_warning ("JPEG - unable to decode XMP metadata packet");
-                }
-
-              gimp_destroy_params (return_vals, nreturn_vals);
-              g_free (xmp_packet);
-            }
-        }
-
       /* Step 5.3: check for an embedded ICC profile in APP2 markers */
       jpeg_icc_read_profile (&cinfo, &profile, &profile_size);
 
@@ -489,10 +413,6 @@ load_image (const gchar  *filename,
 
   gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
 
-#ifdef HAVE_LIBEXIF
-  jpeg_exif_rotate_query (image_ID, orientation);
-#endif
-
   return image_ID;
 }
 
@@ -537,53 +457,6 @@ jpeg_load_resolution (gint32                         image_ID,
     }
 }
 
-#ifdef HAVE_LIBEXIF
-
-static gboolean
-jpeg_load_exif_resolution (gint32        image_ID,
-                           ExifData     *exif_data)
-{
-  gboolean success;
-  gdouble xresolution;
-  gdouble yresolution;
-  gint    unit;
-
-  if (!exif_data)
-    return FALSE;
-
-  if (!jpeg_exif_get_resolution (exif_data,
-                                 &xresolution,
-                                 &yresolution,
-                                 &unit))
-    return FALSE;
-
-  switch (unit)
-    {
-    case 2:
-      success = TRUE;
-      break;
-    case 3: /* dots per cm */
-      xresolution *= 2.54;
-      yresolution *= 2.54;
-      gimp_image_set_unit (image_ID, GIMP_UNIT_MM);
-      success = TRUE;
-      break;
-    default:
-      g_warning ("Unknown EXIF resolution unit %d; skipping EXIF resolution.",
-                 unit);
-      success = FALSE;
-    }
-
-  if (success)
-    {
-      gimp_image_set_resolution (image_ID, xresolution, yresolution);
-    }
-
-  return success;
-}
-
-#endif /* HAVE_LIBEXIF */
-
 /*
  * A number of JPEG files have comments written in a local character set
  * instead of UTF-8.  Some of these files may have been saved by older
@@ -608,9 +481,6 @@ jpeg_load_sanitize_comment (gchar *comment)
     }
 }
 
-
-#ifdef HAVE_LIBEXIF
-
 typedef struct
 {
   struct jpeg_source_mgr pub;   /* public fields */
@@ -627,7 +497,6 @@ init_source (j_decompress_ptr cinfo)
 {
 }
 
-
 static boolean
 fill_input_buffer (j_decompress_ptr cinfo)
 {
@@ -660,14 +529,16 @@ term_source (j_decompress_ptr cinfo)
 }
 
 gint32
-load_thumbnail_image (const gchar   *filename,
+load_thumbnail_image (GFile         *file,
                       gint          *width,
                       gint          *height,
                       GimpImageType *type,
                       GError       **error)
 {
-  gint32 volatile  image_ID;
-  ExifData        *exif_data;
+  gint32 volatile  image_ID         = -1;
+  GimpMetadata    *metadata         = NULL;
+  guint8          *thumbnail_buffer = NULL;
+  gint             thumbnail_size;
   gint32           layer_ID;
   struct jpeg_decompress_struct cinfo;
   struct my_error_mgr           jerr;
@@ -683,20 +554,25 @@ load_thumbnail_image (const gchar   *filename,
   my_src_ptr       src;
   FILE            *infile;
 
-  image_ID = -1;
-  exif_data = jpeg_exif_data_new_from_file (filename, NULL);
-
-  if (! ((exif_data) && (exif_data->data) && (exif_data->size > 0)))
+  metadata = gimp_metadata_load_from_file (file, error);
+  if (! metadata)
     return -1;
 
-  orientation = jpeg_exif_get_orientation (exif_data);
+  orientation = gexiv2_metadata_get_orientation (metadata);
+
+  if (! gexiv2_metadata_get_exif_thumbnail (metadata,
+                                            &thumbnail_buffer, &thumbnail_size))
+    {
+      g_object_unref (metadata);
+      return -1;
+    }
 
   cinfo.err = jpeg_std_error (&jerr.pub);
   jerr.pub.error_exit     = my_error_exit;
   jerr.pub.output_message = my_output_message;
 
   gimp_progress_init_printf (_("Opening thumbnail for '%s'"),
-                             gimp_filename_to_utf8 (filename));
+                             g_file_get_parse_name (file));
 
   /* Establish the setjmp return context for my_error_exit to use. */
   if (setjmp (jerr.setjmp_buffer))
@@ -710,15 +586,15 @@ load_thumbnail_image (const gchar   *filename,
       if (image_ID != -1)
         gimp_image_delete (image_ID);
 
-      if (exif_data)
-        {
-          exif_data_unref (exif_data);
-          exif_data = NULL;
-        }
+      if (metadata)
+        g_object_unref (metadata);
 
       if (buffer)
         g_object_unref (buffer);
 
+      if (thumbnail_buffer)
+        g_free (thumbnail_buffer);
+
       return -1;
     }
 
@@ -740,11 +616,11 @@ load_thumbnail_image (const gchar   *filename,
   src->pub.resync_to_restart = jpeg_resync_to_restart;
   src->pub.term_source       = term_source;
 
-  src->pub.bytes_in_buffer   = exif_data->size;
-  src->pub.next_input_byte   = exif_data->data;
+  src->pub.bytes_in_buffer   = thumbnail_size;
+  src->pub.next_input_byte   = thumbnail_buffer;
 
-  src->buffer = exif_data->data;
-  src->size = exif_data->size;
+  src->buffer = thumbnail_buffer;
+  src->size   = thumbnail_size;
 
   /* Step 3: read file parameters with jpeg_read_header() */
 
@@ -808,11 +684,11 @@ load_thumbnail_image (const gchar   *filename,
                  cinfo.output_components, cinfo.out_color_space,
                  cinfo.jpeg_color_space);
 
-      if (exif_data)
-        {
-          exif_data_unref (exif_data);
-          exif_data = NULL;
-        }
+      if (metadata)
+        g_object_unref (metadata);
+
+      if (thumbnail_buffer)
+        g_free (thumbnail_buffer);
 
       return -1;
       break;
@@ -822,7 +698,6 @@ load_thumbnail_image (const gchar   *filename,
                              image_type);
 
   gimp_image_undo_disable (image_ID);
-  gimp_image_set_filename (image_ID, filename);
 
   jpeg_load_resolution (image_ID, &cinfo);
 
@@ -877,7 +752,9 @@ load_thumbnail_image (const gchar   *filename,
    */
   jpeg_destroy_decompress (&cinfo);
 
+  g_object_unref (metadata);
   g_object_unref (buffer);
+  g_free (thumbnail_buffer);
 
   /* free up the temporary buffers */
   g_free (rowbuf);
@@ -897,17 +774,14 @@ load_thumbnail_image (const gchar   *filename,
   jerr.pub.error_exit = my_error_exit;
   jerr.pub.output_message = my_output_message;
 
-  if ((infile = g_fopen (filename, "rb")) == NULL)
+  if ((infile = g_fopen (g_file_get_path (file), "rb")) == NULL)
     {
       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                    _("Could not open '%s' for reading: %s"),
-                   gimp_filename_to_utf8 (filename), g_strerror (errno));
+                   g_file_get_parse_name (file), g_strerror (errno));
 
-      if (exif_data)
-        {
-          exif_data_unref (exif_data);
-          exif_data = NULL;
-        }
+      if (image_ID != -1)
+        gimp_image_delete (image_ID);
 
       return -1;
     }
@@ -924,12 +798,6 @@ load_thumbnail_image (const gchar   *filename,
       if (image_ID != -1)
         gimp_image_delete (image_ID);
 
-      if (exif_data)
-        {
-          exif_data_unref (exif_data);
-          exif_data = NULL;
-        }
-
       return -1;
     }
 
@@ -958,22 +826,15 @@ load_thumbnail_image (const gchar   *filename,
 
   fclose (infile);
 
-  if (exif_data)
-    {
-      exif_data_unref (exif_data);
-      exif_data = NULL;
-    }
-
+#if 0
   jpeg_exif_rotate (image_ID, orientation);
+#endif
 
   *type = layer_type;
 
   return image_ID;
 }
 
-#endif /* HAVE_LIBEXIF */
-
-
 static gpointer
 jpeg_load_cmyk_transform (guint8 *profile_data,
                           gsize   profile_len)
diff --git a/plug-ins/file-jpeg/jpeg-load.h b/plug-ins/file-jpeg/jpeg-load.h
index fe38c41..0ecdc8c 100644
--- a/plug-ins/file-jpeg/jpeg-load.h
+++ b/plug-ins/file-jpeg/jpeg-load.h
@@ -23,15 +23,10 @@ gint32 load_image           (const gchar  *filename,
                              gboolean      preview,
                              GError      **error);
 
-
-#ifdef HAVE_LIBEXIF
-
-gint32 load_thumbnail_image (const gchar   *filename,
+gint32 load_thumbnail_image (GFile         *file,
                              gint          *width,
                              gint          *height,
                              GimpImageType *type,
                              GError       **error);
 
-#endif /* HAVE_LIBEXIF */
-
 #endif /* __JPEG_LOAD_H__ */
diff --git a/plug-ins/file-jpeg/jpeg-save.c b/plug-ins/file-jpeg/jpeg-save.c
index 9aa0c81..d4058ca 100644
--- a/plug-ins/file-jpeg/jpeg-save.c
+++ b/plug-ins/file-jpeg/jpeg-save.c
@@ -33,10 +33,6 @@
 #include <jpeglib.h>
 #include <jerror.h>
 
-#ifdef HAVE_LIBEXIF
-#include <libexif/exif-data.h>
-#endif /* HAVE_LIBEXIF */
-
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
 
@@ -47,10 +43,6 @@
 #include "jpeg-load.h"
 #include "jpeg-save.h"
 #include "jpeg-settings.h"
-#ifdef HAVE_LIBEXIF
-#include "jpeg-exif.h"
-#endif
-
 
 #define SCALE_WIDTH         125
 
@@ -69,6 +61,7 @@
 #define DEFAULT_EXIF             TRUE
 #define DEFAULT_THUMBNAIL        FALSE
 #define DEFAULT_XMP              TRUE
+#define DEFAULT_IPTC             TRUE
 #define DEFAULT_USE_ORIG_QUALITY FALSE
 
 #define JPEG_DEFAULTS_PARASITE  "jpeg-save-defaults"
@@ -111,6 +104,7 @@ typedef struct
   GtkWidget     *save_exif;
   GtkWidget     *save_thumbnail;
   GtkWidget     *save_xmp;
+  GtkWidget     *save_iptc;
   GtkWidget     *use_orig_quality;      /*quant tables toggle*/
 } JpegSaveGui;
 
@@ -127,15 +121,6 @@ static void  use_orig_qual_changed  (GtkWidget     *toggle,
 static void  use_orig_qual_changed2 (GtkWidget     *toggle,
                                      GtkWidget     *combo);
 
-#ifdef HAVE_LIBEXIF
-
-static gint  create_thumbnail    (gint32         image_ID,
-                                  gint32         drawable_ID,
-                                  gdouble        quality,
-                                  guchar       **thumbnail_buffer);
-
-#endif /* HAVE_LIBEXIF */
-
 
 static GtkWidget *restart_markers_scale = NULL;
 static GtkWidget *restart_markers_label = NULL;
@@ -527,82 +512,6 @@ save_image (const gchar  *filename,
    */
   jpeg_start_compress (&cinfo, TRUE);
 
-#ifdef HAVE_LIBEXIF
-
-  /* Create the thumbnail JPEG in a buffer */
-  if ((jsvals.save_exif && exif_data) || jsvals.save_thumbnail)
-    {
-      ExifData *exif_data_tmp           = NULL;
-      guchar   *exif_buf                = NULL;
-      guchar   *thumbnail_buffer        = NULL;
-      gint      thumbnail_buffer_length = 0;
-      guint     exif_buf_len;
-      gdouble   quality                 = MIN (75.0, jsvals.quality);
-
-      if ( (! jsvals.save_exif) || (! exif_data))
-        exif_data_tmp = exif_data_new ();
-      else
-        exif_data_tmp = exif_data;
-
-      /* avoid saving markers longer than 65533, gradually decrease
-       * quality in steps of 5 until exif_buf_len is lower than that.
-       */
-      for (exif_buf_len = 65535;
-           exif_buf_len > 65533 && quality > 0.0;
-           quality -= 5.0)
-        {
-          if (jsvals.save_thumbnail)
-            thumbnail_buffer_length = create_thumbnail (image_ID, drawable_ID,
-                                                        quality,
-                                                        &thumbnail_buffer);
-
-          exif_data_tmp->data = thumbnail_buffer;
-          exif_data_tmp->size = thumbnail_buffer_length;
-
-          if (exif_buf)
-            free (exif_buf);
-
-          exif_data_save_data (exif_data_tmp, &exif_buf, &exif_buf_len);
-        }
-
-      if (exif_buf_len > 65533)
-        {
-          /* last attempt with quality 0.0 */
-          if (jsvals.save_thumbnail)
-            thumbnail_buffer_length = create_thumbnail (image_ID, drawable_ID,
-                                                        0.0,
-                                                        &thumbnail_buffer);
-          exif_data_tmp->data = thumbnail_buffer;
-          exif_data_tmp->size = thumbnail_buffer_length;
-
-          if (exif_buf)
-            free (exif_buf);
-
-          exif_data_save_data (exif_data_tmp, &exif_buf, &exif_buf_len);
-        }
-
-      if (exif_buf_len > 65533)
-        {
-          /* still no go? save without thumbnail */
-          exif_data_tmp->data = NULL;
-          exif_data_tmp->size = 0;
-
-          if (exif_buf)
-            free (exif_buf);
-
-          exif_data_save_data (exif_data_tmp, &exif_buf, &exif_buf_len);
-        }
-
-#ifdef GIMP_UNSTABLE
-      g_print ("jpeg-save: saving EXIF block (%d bytes)\n", exif_buf_len);
-#endif
-      jpeg_write_marker (&cinfo, JPEG_APP0 + 1, exif_buf, exif_buf_len);
-
-      if (exif_buf)
-        free (exif_buf);
-    }
-#endif /* HAVE_LIBEXIF */
-
   /* Step 4.1: Write the comment out - pw */
   if (image_comment && *image_comment)
     {
@@ -614,36 +523,7 @@ save_image (const gchar  *filename,
                          (guchar *) image_comment, strlen (image_comment));
     }
 
-  /* Step 4.2: Write the XMP packet in an APP1 marker */
-  if (jsvals.save_xmp)
-    {
-      /* FIXME: temporary hack until the right thing is done by a library */
-      parasite = gimp_image_get_parasite (orig_image_ID, "gimp-metadata");
-      if (parasite)
-        {
-          const gchar *xmp_data;
-          glong        xmp_data_size;
-          guchar      *app_block;
-
-          xmp_data = ((const gchar *) gimp_parasite_data (parasite)) + 10;
-          xmp_data_size = gimp_parasite_data_size (parasite) - 10;
-#ifdef GIMP_UNSTABLE
-          g_print ("jpeg-save: saving XMP packet (%d bytes)\n",
-                   (int) xmp_data_size);
-#endif
-          app_block = g_malloc (sizeof (JPEG_APP_HEADER_XMP) + xmp_data_size);
-          memcpy (app_block, JPEG_APP_HEADER_XMP,
-                  sizeof (JPEG_APP_HEADER_XMP));
-          memcpy (app_block + sizeof (JPEG_APP_HEADER_XMP), xmp_data,
-                  xmp_data_size);
-          jpeg_write_marker (&cinfo, JPEG_APP0 + 1, app_block,
-                             sizeof (JPEG_APP_HEADER_XMP) + xmp_data_size);
-          g_free (app_block);
-          gimp_parasite_free (parasite);
-        }
-    }
-
-  /* Step 4.3: store the color profile if there is one */
+  /* Step 4.2: store the color profile if there is one */
   parasite = gimp_image_get_parasite (orig_image_ID, "icc-profile");
   if (parasite)
     {
@@ -913,7 +793,7 @@ save_dialog (void)
   gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
   gtk_widget_show (frame);
 
-  table = gtk_table_new (4, 7, FALSE);
+  table = gtk_table_new (4, 8, FALSE);
   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
   gtk_table_set_col_spacing (GTK_TABLE (table), 1, 12);
@@ -1000,9 +880,9 @@ save_dialog (void)
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
                                 jsvals.progressive);
 
-#ifdef HAVE_LIBEXIF
   pg.save_exif = toggle =
     gtk_check_button_new_with_mnemonic (_("Save _EXIF data"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_exif);
   gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
   gtk_widget_show (toggle);
 
@@ -1013,13 +893,11 @@ save_dialog (void)
                     G_CALLBACK (make_preview),
                     NULL);
 
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
-                                jsvals.save_exif && exif_data);
-
-  gtk_widget_set_sensitive (toggle, exif_data != NULL);
+  gtk_widget_set_sensitive (toggle, TRUE);
 
   pg.save_thumbnail = toggle =
     gtk_check_button_new_with_mnemonic (_("Save _thumbnail"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_thumbnail);
   gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
   gtk_widget_show (toggle);
 
@@ -1030,13 +908,10 @@ save_dialog (void)
                     G_CALLBACK (make_preview),
                     NULL);
 
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
-                                jsvals.save_thumbnail);
-#endif /* HAVE_LIBEXIF */
-
   /* XMP metadata */
   pg.save_xmp = toggle =
     gtk_check_button_new_with_mnemonic (_("Save _XMP data"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_xmp);
   gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 4, 5, GTK_FILL, 0, 0, 0);
   gtk_widget_show (toggle);
 
@@ -1047,16 +922,29 @@ save_dialog (void)
                     G_CALLBACK (make_preview),
                     NULL);
 
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
-                                jsvals.save_xmp && has_metadata);
+  gtk_widget_set_sensitive (toggle, TRUE);
 
-  gtk_widget_set_sensitive (toggle, has_metadata);
+  /* IPTC metadata */
+  pg.save_iptc = toggle =
+    gtk_check_button_new_with_mnemonic (_("Save _IPTC data"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_iptc);
+  gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 5, 6, GTK_FILL, 0, 0, 0);
+  gtk_widget_show (toggle);
+
+  g_signal_connect (toggle, "toggled",
+                    G_CALLBACK (gimp_toggle_button_update),
+                    &jsvals.save_iptc);
+  g_signal_connect (toggle, "toggled",
+                    G_CALLBACK (make_preview),
+                    NULL);
+
+  gtk_widget_set_sensitive (toggle, TRUE);
 
   /* custom quantization tables - now used also for original quality */
   pg.use_orig_quality = toggle =
     gtk_check_button_new_with_mnemonic (_("_Use quality settings from original "
                                           "image"));
-  gtk_table_attach (GTK_TABLE (table), toggle, 0, 4, 5, 6, GTK_FILL, 0, 0, 0);
+  gtk_table_attach (GTK_TABLE (table), toggle, 0, 4, 6, 7, GTK_FILL, 0, 0, 0);
   gtk_widget_show (toggle);
 
   gimp_help_set_help_data (toggle,
@@ -1266,13 +1154,9 @@ load_defaults (void)
   jsvals.save_exif        = DEFAULT_EXIF;
   jsvals.save_thumbnail   = DEFAULT_THUMBNAIL;
   jsvals.save_xmp         = DEFAULT_XMP;
+  jsvals.save_iptc        = DEFAULT_IPTC;
   jsvals.use_orig_quality = DEFAULT_USE_ORIG_QUALITY;
 
-#ifdef HAVE_LIBEXIF
-  if (exif_data && (exif_data->data))
-    jsvals.save_thumbnail = TRUE;
-#endif /* HAVE_LIBEXIF */
-
   parasite = gimp_get_parasite (JPEG_DEFAULTS_PARASITE);
 
   if (! parasite)
@@ -1283,7 +1167,7 @@ load_defaults (void)
 
   gimp_parasite_free (parasite);
 
-  num_fields = sscanf (def_str, "%lf %lf %d %d %d %d %d %d %d %d %d %d %d",
+  num_fields = sscanf (def_str, "%lf %lf %d %d %d %d %d %d %d %d %d %d %d %d",
                        &tmpvals.quality,
                        &tmpvals.smoothing,
                        &tmpvals.optimize,
@@ -1296,12 +1180,15 @@ load_defaults (void)
                        &tmpvals.save_exif,
                        &tmpvals.save_thumbnail,
                        &tmpvals.save_xmp,
-                       &tmpvals.use_orig_quality);
+                       &tmpvals.use_orig_quality,
+                       &tmpvals.save_iptc);
 
   tmpvals.subsmp = subsampling;
 
-  if (num_fields == 13)
-    memcpy (&jsvals, &tmpvals, sizeof (tmpvals));
+  if (num_fields == 13 || num_fields == 14)
+    {
+      memcpy (&jsvals, &tmpvals, sizeof (tmpvals));
+    }
 
   g_free (def_str);
 }
@@ -1312,7 +1199,7 @@ save_defaults (void)
   GimpParasite *parasite;
   gchar        *def_str;
 
-  def_str = g_strdup_printf ("%lf %lf %d %d %d %d %d %d %d %d %d %d %d",
+  def_str = g_strdup_printf ("%lf %lf %d %d %d %d %d %d %d %d %d %d %d %d",
                              jsvals.quality,
                              jsvals.smoothing,
                              jsvals.optimize,
@@ -1325,7 +1212,8 @@ save_defaults (void)
                              jsvals.save_exif,
                              jsvals.save_thumbnail,
                              jsvals.save_xmp,
-                             jsvals.use_orig_quality);
+                             jsvals.use_orig_quality,
+                             jsvals.save_iptc);
   parasite = gimp_parasite_new (JPEG_DEFAULTS_PARASITE,
                                 GIMP_PARASITE_PERSISTENT,
                                 strlen (def_str), def_str);
@@ -1350,11 +1238,10 @@ load_gui_defaults (JpegSaveGui *pg)
   SET_ACTIVE_BTTN (progressive);
   SET_ACTIVE_BTTN (use_orig_quality);
   SET_ACTIVE_BTTN (preview);
-#ifdef HAVE_LIBEXIF
   SET_ACTIVE_BTTN (save_exif);
   SET_ACTIVE_BTTN (save_thumbnail);
-#endif
   SET_ACTIVE_BTTN (save_xmp);
+  SET_ACTIVE_BTTN (save_iptc);
 
 #undef SET_ACTIVE_BTTN
 
@@ -1447,218 +1334,3 @@ use_orig_qual_changed2 (GtkWidget *toggle,
       gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), orig_subsmp);
     }
 }
-
-#ifdef HAVE_LIBEXIF
-
-static guchar *tbuffer = NULL;
-static guchar *tbuffer2 = NULL;
-
-static gint tbuffer_count = 0;
-
-typedef struct
-{
-  struct jpeg_destination_mgr  pub;   /* public fields */
-  guchar                      *buffer;
-  gint                         size;
-} my_destination_mgr;
-
-typedef my_destination_mgr *my_dest_ptr;
-
-static void
-init_destination (j_compress_ptr cinfo)
-{
-}
-
-static gboolean
-empty_output_buffer (j_compress_ptr cinfo)
-{
-  my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
-
-  tbuffer_count = tbuffer_count + 16384;
-  tbuffer = g_renew (guchar, tbuffer, tbuffer_count);
-  g_memmove (tbuffer + tbuffer_count - 16384, tbuffer2, 16384);
-
-  dest->pub.next_output_byte = tbuffer2;
-  dest->pub.free_in_buffer   = 16384;
-
-  return TRUE;
-}
-
-static void
-term_destination (j_compress_ptr cinfo)
-{
-  my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
-
-  tbuffer_count = (tbuffer_count + 16384) - (dest->pub.free_in_buffer);
-
-  tbuffer = g_renew (guchar, tbuffer, tbuffer_count);
-  g_memmove (tbuffer + tbuffer_count - (16384 - dest->pub.free_in_buffer),
-             tbuffer2, 16384 - dest->pub.free_in_buffer);
-}
-
-static gint
-create_thumbnail (gint32    image_ID,
-                  gint32    drawable_ID,
-                  gdouble   quality,
-                  guchar  **thumbnail_buffer)
-{
-  int                         width, height;
-  gint                        req_width, req_height, bpp, rbpp;
-  guchar                     *thumbnail_data = NULL;
-  struct jpeg_compress_struct cinfo;
-  struct my_error_mgr         jerr;
-  my_dest_ptr                 dest;
-  JSAMPROW                    scanline[1];
-  guchar                     *buf = NULL;
-  gint                        i;
-
-  {
-    GeglBuffer *buffer = gimp_drawable_get_buffer (drawable_ID);
-    width  = gegl_buffer_get_width (buffer);
-    height = gegl_buffer_get_height (buffer);
-    g_object_unref (buffer);
-  }
-
-  req_width  = 196;
-  req_height = 196;
-
-  if (MIN (width, height) < 196)
-    req_width = req_height = MIN(width, height);
-
-  thumbnail_data = gimp_drawable_get_thumbnail_data (drawable_ID,
-                                                     &req_width, &req_height,
-                                                     &bpp);
-
-  if (! thumbnail_data)
-    return 0;
-
-  rbpp = bpp;
-
-  if ((bpp == 2) || (bpp == 4))
-    {
-      rbpp = bpp - 1;
-    }
-
-  buf = g_new (guchar, req_width * bpp);
-  tbuffer2 = g_new (guchar, 16384);
-
-  tbuffer_count = 0;
-
-  cinfo.err = jpeg_std_error (&jerr.pub);
-  jerr.pub.error_exit = my_error_exit;
-
-  /* Establish the setjmp return context for my_error_exit to use. */
-  if (setjmp (jerr.setjmp_buffer))
-    {
-      /* If we get here, the JPEG code has signaled an error.
-       * We need to clean up the JPEG object, free memory, and return.
-       */
-      jpeg_destroy_compress (&cinfo);
-
-      if (thumbnail_data)
-        {
-          g_free (thumbnail_data);
-          thumbnail_data = NULL;
-        }
-
-      if (buf)
-        {
-          g_free (buf);
-          buf = NULL;
-        }
-
-      if (tbuffer2)
-        {
-          g_free (tbuffer2);
-          tbuffer2 = NULL;
-        }
-
-      return 0;
-    }
-
-  /* Now we can initialize the JPEG compression object. */
-  jpeg_create_compress (&cinfo);
-
-  if (cinfo.dest == NULL)
-    cinfo.dest = (struct jpeg_destination_mgr *)
-      (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT,
-                                 sizeof(my_destination_mgr));
-
-  dest = (my_dest_ptr) cinfo.dest;
-  dest->pub.init_destination    = init_destination;
-  dest->pub.empty_output_buffer = empty_output_buffer;
-  dest->pub.term_destination    = term_destination;
-
-  dest->pub.next_output_byte = tbuffer2;
-  dest->pub.free_in_buffer   = 16384;
-
-  dest->buffer = tbuffer2;
-  dest->size   = 16384;
-
-  cinfo.input_components = rbpp;
-  cinfo.image_width      = req_width;
-  cinfo.image_height     = req_height;
-
-  /* colorspace of input image */
-  cinfo.in_color_space = (rbpp == 3) ? JCS_RGB : JCS_GRAYSCALE;
-
-  /* Now use the library's routine to set default compression parameters.
-   * (You must set at least cinfo.in_color_space before calling this,
-   * since the defaults depend on the source color space.)
-   */
-  jpeg_set_defaults (&cinfo);
-
-  jpeg_set_quality (&cinfo, (gint) (quality + 0.5), jsvals.baseline);
-
-  /* Step 4: Start compressor */
-
-  /* TRUE ensures that we will write a complete interchange-JPEG file.
-   * Pass TRUE unless you are very sure of what you're doing.
-   */
-  jpeg_start_compress (&cinfo, TRUE);
-
-  while (cinfo.next_scanline < (unsigned int) req_height)
-    {
-      for (i = 0; i < req_width; i++)
-        {
-          buf[(i * rbpp) + 0] = thumbnail_data[(cinfo.next_scanline * req_width * bpp) + (i * bpp) + 0];
-
-          if (rbpp == 3)
-            {
-              buf[(i * rbpp) + 1] = thumbnail_data[(cinfo.next_scanline * req_width * bpp) + (i * bpp) + 1];
-              buf[(i * rbpp) + 2] = thumbnail_data[(cinfo.next_scanline * req_width * bpp) + (i * bpp) + 2];
-            }
-        }
-
-      scanline[0] = buf;
-      jpeg_write_scanlines (&cinfo, scanline, 1);
-  }
-
-  /* Step 6: Finish compression */
-  jpeg_finish_compress (&cinfo);
-
-  /* Step 7: release JPEG compression object */
-
-  /* This is an important step since it will release a good deal of memory. */
-  jpeg_destroy_compress (&cinfo);
-
-  /* And we're done! */
-
-  if (thumbnail_data)
-    {
-      g_free (thumbnail_data);
-      thumbnail_data = NULL;
-    }
-
-  if (buf)
-    {
-      g_free (buf);
-      buf = NULL;
-    }
-
-  *thumbnail_buffer = tbuffer;
-
-  return tbuffer_count;
-}
-
-#endif /* HAVE_LIBEXIF */
diff --git a/plug-ins/file-jpeg/jpeg-save.h b/plug-ins/file-jpeg/jpeg-save.h
index 4465991..2e5ad80 100644
--- a/plug-ins/file-jpeg/jpeg-save.h
+++ b/plug-ins/file-jpeg/jpeg-save.h
@@ -32,6 +32,7 @@ typedef struct
   gboolean         save_exif;
   gboolean         save_thumbnail;
   gboolean         save_xmp;
+  gboolean         save_iptc;
   gboolean         use_orig_quality;
 } JpegSaveVals;
 
diff --git a/plug-ins/file-jpeg/jpeg-settings.c b/plug-ins/file-jpeg/jpeg-settings.c
index e4112b9..b86e2bc 100644
--- a/plug-ins/file-jpeg/jpeg-settings.c
+++ b/plug-ins/file-jpeg/jpeg-settings.c
@@ -50,10 +50,6 @@
 
 #include <jpeglib.h>
 
-#ifdef HAVE_LIBEXIF
-#include <libexif/exif-data.h>
-#endif /* HAVE_LIBEXIF */
-
 #include <libgimp/gimp.h>
 
 #include "libgimp/stdplugins-intl.h"
diff --git a/plug-ins/file-jpeg/jpeg.c b/plug-ins/file-jpeg/jpeg.c
index bdfb70a..ca06401 100644
--- a/plug-ins/file-jpeg/jpeg.c
+++ b/plug-ins/file-jpeg/jpeg.c
@@ -24,10 +24,6 @@
 #include <jpeglib.h>
 #include <jerror.h>
 
-#ifdef HAVE_LIBEXIF
-#include <libexif/exif-data.h>
-#endif /* HAVE_LIBEXIF */
-
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
 
@@ -37,11 +33,6 @@
 #include "jpeg-settings.h"
 #include "jpeg-load.h"
 #include "jpeg-save.h"
-#ifdef HAVE_LIBEXIF
-#include "jpeg-exif.h"
-#include "gimpexif.h"
-#endif
-
 
 /* Declare local functions.
  */
@@ -66,10 +57,6 @@ JpegSubsampling  orig_subsmp;
 gint             num_quant_tables;
 
 
-#ifdef HAVE_LIBEXIF
-ExifData        *exif_data = NULL;
-#endif
-
 const GimpPlugInInfo PLUG_IN_INFO =
 {
   NULL,  /* init_proc  */
@@ -96,8 +83,6 @@ query (void)
     { GIMP_PDB_IMAGE,   "image",         "Output image" }
   };
 
-#ifdef HAVE_LIBEXIF
-
   static const GimpParamDef thumb_args[] =
   {
     { GIMP_PDB_STRING, "filename",     "The name of the file to load"  },
@@ -110,8 +95,6 @@ query (void)
     { GIMP_PDB_INT32,  "image-height", "Height of full-sized image"    }
   };
 
-#endif /* HAVE_LIBEXIF */
-
   static const GimpParamDef save_args[] =
   {
     { GIMP_PDB_INT32,    "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
@@ -149,8 +132,6 @@ query (void)
                                     "",
                                     "6,string,JFIF,6,string,Exif");
 
-#ifdef HAVE_LIBEXIF
-
   gimp_install_procedure (LOAD_THUMB_PROC,
                           "Loads a thumbnail from a JPEG image",
                           "Loads a thumbnail from a JPEG image (only if it exists)",
@@ -166,8 +147,6 @@ query (void)
 
   gimp_register_thumbnail_loader (LOAD_PROC, LOAD_THUMB_PROC);
 
-#endif /* HAVE_LIBEXIF */
-
   gimp_install_procedure (SAVE_PROC,
                           "saves files in the JPEG file format",
                           "saves files in the lossy, widely supported JPEG format",
@@ -247,9 +226,6 @@ run (const gchar      *name,
         }
 
     }
-
-#ifdef HAVE_LIBEXIF
-
   else if (strcmp (name, LOAD_THUMB_PROC) == 0)
     {
       if (nparams < 2)
@@ -258,14 +234,16 @@ run (const gchar      *name,
         }
       else
         {
-          const gchar  *filename = param[0].data.d_string;
+          GFile        *file     = g_file_new_for_path (param[0].data.d_string);
           gint          width    = 0;
           gint          height   = 0;
           GimpImageType type     = -1;
 
-          image_ID = load_thumbnail_image (filename, &width, &height, &type,
+          image_ID = load_thumbnail_image (file, &width, &height, &type,
                                            &error);
 
+          g_object_unref (file);
+
           if (image_ID != -1)
             {
               *nreturn_vals = 6;
@@ -286,9 +264,6 @@ run (const gchar      *name,
             }
         }
     }
-
-#endif /* HAVE_LIBEXIF */
-
   else if (strcmp (name, SAVE_PROC) == 0)
     {
       image_ID = orig_image_ID = param[1].data.d_int32;
@@ -348,14 +323,7 @@ run (const gchar      *name,
           gimp_parasite_free (parasite);
         }
 
-#ifdef HAVE_LIBEXIF
-
-      exif_data = gimp_metadata_generate_exif (orig_image_ID);
-      if (exif_data)
-        jpeg_setup_exif_for_save (exif_data, orig_image_ID);
-
-#endif /* HAVE_LIBEXIF */
-
+      /* load defaults from gimp parasite */
       load_defaults ();
 
       switch (run_mode)
@@ -427,6 +395,7 @@ run (const gchar      *name,
               jsvals.save_exif        = save_vals->save_exif;
               jsvals.save_thumbnail   = save_vals->save_thumbnail;
               jsvals.save_xmp         = save_vals->save_xmp;
+              jsvals.save_iptc        = save_vals->save_iptc;
               jsvals.use_orig_quality = save_vals->use_orig_quality;
 
               gimp_parasite_free (parasite);
@@ -511,9 +480,37 @@ run (const gchar      *name,
 
       if (status == GIMP_PDB_SUCCESS)
         {
+          GimpMetadata *metadata;
+
+          metadata = gimp_image_metadata_save_prepare (image_ID,
+                                                       "image/jpeg");
+
+          if (metadata)
+            {
+              GFile                 *file;
+              GimpMetadataSaveFlags  flags = 0;
+
+              gimp_metadata_set_bits_per_sample (metadata, 8);
+
+              if (jsvals.save_exif)      flags |= GIMP_METADATA_SAVE_EXIF;
+              if (jsvals.save_xmp)       flags |= GIMP_METADATA_SAVE_XMP;
+              if (jsvals.save_iptc)      flags |= GIMP_METADATA_SAVE_IPTC;
+              if (jsvals.save_thumbnail) flags |= GIMP_METADATA_SAVE_THUMBNAIL;
+
+              file = g_file_new_for_path (param[3].data.d_string);
+              gimp_image_metadata_save_finish (image_ID,
+                                               "image/jpeg",
+                                               metadata, file, flags,
+                                               NULL);
+              g_object_unref (file);
+
+              g_object_unref (metadata);
+            }
+
           /* pw - now we need to change the defaults to be whatever
            * was used to save this image.  Dump the old parasites
-           * and add new ones. */
+           * and add new ones.
+           */
 
           gimp_image_detach_parasite (orig_image_ID, "gimp-comment");
           if (image_comment && strlen (image_comment))
@@ -526,7 +523,6 @@ run (const gchar      *name,
               gimp_parasite_free (parasite);
             }
 
-          gimp_image_detach_parasite (orig_image_ID, "jpeg-save-options");
           parasite = gimp_parasite_new ("jpeg-save-options",
                                         0, sizeof (jsvals), &jsvals);
           gimp_image_attach_parasite (orig_image_ID, parasite);
diff --git a/plug-ins/file-psd/psd-image-res-load.c b/plug-ins/file-psd/psd-image-res-load.c
index bf2c343..178b9dc 100644
--- a/plug-ins/file-psd/psd-image-res-load.c
+++ b/plug-ins/file-psd/psd-image-res-load.c
@@ -94,9 +94,6 @@
 #include <jpeglib.h>
 #include <jerror.h>
 
-#ifdef HAVE_LIBEXIF
-#include <libexif/exif-data.h>
-#endif /* HAVE_LIBEXIF */
 #ifdef HAVE_IPTCDATA
 #include <libiptcdata/iptc-data.h>
 #endif /* HAVE_IPTCDATA */
@@ -196,11 +193,6 @@ static gint     load_resource_1058     (const PSDimageres     *res_a,
                                         FILE                  *f,
                                         GError               **error);
 
-static gint     load_resource_1060     (const PSDimageres     *res_a,
-                                        const gint32           image_id,
-                                        FILE                  *f,
-                                        GError               **error);
-
 static gint     load_resource_2000     (const PSDimageres     *res_a,
                                         const gint32           image_id,
                                         FILE                  *f,
@@ -353,7 +345,6 @@ load_image_resource (PSDimageres   *res_a,
             break;
 
           case PSD_XMP_DATA:
-            load_resource_1060 (res_a, image_id, f, error);
             break;
 
           default:
@@ -1209,21 +1200,7 @@ load_resource_1058 (const PSDimageres  *res_a,
                     FILE               *f,
                     GError            **error)
 {
-  /* Load EXIF data block */
-
-#ifdef HAVE_LIBEXIF
-  ExifData     *exif_data;
-  ExifEntry    *exif_entry;
-  guchar       *exif_buf;
-  guchar       *tmp_data;
-  guint         exif_buf_len;
-  gint16        jpeg_len;
-  gint16        jpeg_fill = 0;
-  GimpParam    *return_vals;
-  gint          nreturn_vals;
-#else
   gchar        *name;
-#endif /* HAVE_LIBEXIF */
 
   GimpParasite *parasite;
   gchar        *res_data;
@@ -1238,79 +1215,6 @@ load_resource_1058 (const PSDimageres  *res_a,
       return -1;
     }
 
-#ifdef HAVE_LIBEXIF
-  /* Add JPEG header & trailer to the TIFF Exif data held in PSD
-     resource to allow us to use libexif to serialize the data
-     in the same manner as the JPEG load.
-  */
-  jpeg_len = res_a->data_len + 8;
-  tmp_data = g_malloc (res_a->data_len + 12);
-  /* SOI & APP1 markers */
-  memcpy (tmp_data, "\xFF\xD8\xFF\xE1", 4);
-  /* APP1 block len */
-  memcpy (tmp_data + 4, &jpeg_len, 2);
-  /* Exif marker */
-  memcpy (tmp_data + 6, "Exif", 4);
-  /* Filler */
-  memcpy (tmp_data + 10, &jpeg_fill, 2);
-  /* Exif data */
-  memcpy (tmp_data + 12, res_data, res_a->data_len);
-
-  /* Create Exif data structure */
-  exif_data = exif_data_new_from_data (tmp_data, res_a->data_len + 12);
-  g_free (tmp_data);
-  IFDBG (3) exif_data_dump (exif_data);
-
-  /* Check for XMP data block in Exif data - PS7 */
-  if ((exif_entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0],
-                                            EXIF_TAG_XML_PACKET)))
-    {
-      IFDBG(3) g_debug ("Processing Exif XMP data block");
-      /*Create NULL terminated EXIF data block */
-      tmp_data = g_malloc (exif_entry->size + 1);
-      memcpy (tmp_data, exif_entry->data, exif_entry->size);
-      tmp_data[exif_entry->size] = 0;
-      /* Merge with existing XMP data block */
-      return_vals = gimp_run_procedure (DECODE_XMP_PROC,
-                                        &nreturn_vals,
-                                        GIMP_PDB_IMAGE,  image_id,
-                                        GIMP_PDB_STRING, tmp_data,
-                                        GIMP_PDB_END);
-      g_free (tmp_data);
-      gimp_destroy_params (return_vals, nreturn_vals);
-      IFDBG(3) g_debug ("Deleting XMP block from Exif data");
-      /* Delete XMP data from Exif block */
-      exif_content_remove_entry (exif_data->ifd[EXIF_IFD_0],
-                                 exif_entry);
-    }
-
-  /* Check for Photoshp Image Resource data block in Exif data - PS7 */
-  if ((exif_entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0],
-                                            EXIF_TAG_IMAGE_RESOURCES)))
-    {
-      IFDBG(3) g_debug ("Deleting PS Image Resource block from Exif data");
-      /* Delete PS Image Resource data from Exif block */
-      exif_content_remove_entry (exif_data->ifd[EXIF_IFD_0],
-                                 exif_entry);
-    }
-
-  IFDBG (3) exif_data_dump (exif_data);
-  /* Store resource data as a GIMP Exif parasite */
-  IFDBG (2) g_debug ("Processing exif data as GIMP Exif parasite");
-  /* Serialize exif data */
-  exif_data_save_data (exif_data, &exif_buf, &exif_buf_len);
-  if (exif_buf_len > EXIF_HEADER_SIZE)
-    {
-      parasite = gimp_parasite_new (GIMP_PARASITE_EXIF,
-                                    GIMP_PARASITE_PERSISTENT,
-                                    exif_buf_len, exif_buf);
-      gimp_image_attach_parasite (image_id, parasite);
-      gimp_parasite_free (parasite);
-    }
-  exif_data_unref (exif_data);
-  g_free (exif_buf);
-
-#else
   /* Store resource data as a standard psd parasite */
   IFDBG (2) g_debug ("Processing exif data as psd parasite");
   name = g_strdup_printf ("psd-image-resource-%.4s-%.4x",
@@ -1322,42 +1226,7 @@ load_resource_1058 (const PSDimageres  *res_a,
   gimp_parasite_free (parasite);
   g_free (name);
 
-#endif /* HAVE_LIBEXIF */
-
-  g_free (res_data);
-  return 0;
-}
-
-static gint
-load_resource_1060 (const PSDimageres  *res_a,
-                    const gint32        image_id,
-                    FILE               *f,
-                    GError            **error)
-{
-  /* Load XMP Metadata block */
-  GimpParam    *return_vals;
-  gint          nreturn_vals;
-  gchar        *res_data;
-
-  IFDBG(2) g_debug ("Process image resource block: 1060: XMP Data");
-
-  res_data = g_malloc (res_a->data_len + 1);
-  if (fread (res_data, res_a->data_len, 1, f) < 1)
-    {
-      psd_set_error (feof (f), errno, error);
-      g_free (res_data);
-      return -1;
-    }
-  /* Null terminate metadata block for decode procedure */
-  res_data[res_a->data_len] = 0;
-
-  return_vals = gimp_run_procedure (DECODE_XMP_PROC,
-                                    &nreturn_vals,
-                                    GIMP_PDB_IMAGE,  image_id,
-                                    GIMP_PDB_STRING, res_data,
-                                    GIMP_PDB_END);
   g_free (res_data);
-  gimp_destroy_params (return_vals, nreturn_vals);
   return 0;
 }
 
diff --git a/plug-ins/ui/Makefile.am b/plug-ins/ui/Makefile.am
index 0f52918..aec82b9 100644
--- a/plug-ins/ui/Makefile.am
+++ b/plug-ins/ui/Makefile.am
@@ -2,6 +2,8 @@ uidatadir = $(gimpdatadir)/ui/plug-ins
 
 uidata_DATA = \
        plug-in-file-gif.ui     \
-       plug-in-file-png.ui
+       plug-in-file-png.ui     \
+       plug-in-file-tiff.ui    \
+       plug-in-metadata.ui
 
 EXTRA_DIST = $(uidata_DATA)
diff --git a/plug-ins/ui/plug-in-file-png.ui b/plug-ins/ui/plug-in-file-png.ui
index 0005595..db4ec36 100644
--- a/plug-ins/ui/plug-in-file-png.ui
+++ b/plug-ins/ui/plug-in-file-png.ui
@@ -1,20 +1,18 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 2.12 -->
+  <requires lib="gtk+" version="2.24"/>
   <!-- interface-naming-policy project-wide -->
   <object class="GtkAdjustment" id="compression-level">
     <property name="upper">9</property>
-
-    <!-- NOTE: Set value _after_ upper so the value don't get clamped -->
     <property name="value">9</property>
-
     <property name="step_increment">1</property>
     <property name="page_increment">1</property>
   </object>
   <object class="GtkTable" id="table">
     <property name="visible">True</property>
+    <property name="can_focus">False</property>
     <property name="border_width">12</property>
-    <property name="n_rows">10</property>
+    <property name="n_rows">11</property>
     <property name="n_columns">3</property>
     <property name="column_spacing">6</property>
     <property name="row_spacing">6</property>
@@ -139,6 +137,7 @@
     <child>
       <object class="GtkLabel" id="compression-level-label">
         <property name="visible">True</property>
+        <property name="can_focus">False</property>
         <property name="xalign">0</property>
         <property name="label" translatable="yes">Co_mpression level:</property>
         <property name="use_underline">True</property>
@@ -147,7 +146,7 @@
       <packing>
         <property name="top_attach">8</property>
         <property name="bottom_attach">9</property>
-        <property name="x_options"></property>
+        <property name="x_options"/>
       </packing>
     </child>
     <child>
@@ -170,7 +169,11 @@
         <property name="can_focus">True</property>
         <property name="has_tooltip">True</property>
         <property name="tooltip_text" translatable="yes">Choose a high compression level for small file 
size</property>
-        <property name="invisible_char">&#x25CF;</property>
+        <property name="invisible_char">●</property>
+        <property name="primary_icon_activatable">False</property>
+        <property name="secondary_icon_activatable">False</property>
+        <property name="primary_icon_sensitive">True</property>
+        <property name="secondary_icon_sensitive">True</property>
         <property name="adjustment">compression-level</property>
         <property name="numeric">True</property>
       </object>
@@ -179,12 +182,13 @@
         <property name="right_attach">3</property>
         <property name="top_attach">8</property>
         <property name="bottom_attach">9</property>
-        <property name="x_options"></property>
+        <property name="x_options"/>
       </packing>
     </child>
     <child>
       <object class="GtkHButtonBox" id="hbuttonbox">
         <property name="visible">True</property>
+        <property name="can_focus">False</property>
         <property name="spacing">6</property>
         <property name="layout_style">start</property>
         <child>
@@ -218,6 +222,90 @@
       </object>
       <packing>
         <property name="right_attach">3</property>
+        <property name="top_attach">10</property>
+        <property name="bottom_attach">11</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkExpander" id="expander1">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <child>
+          <object class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkCheckButton" id="sv_exif">
+                <property name="label" translatable="yes">save EXIF data</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCheckButton" id="sv_xmp">
+                <property name="label" translatable="yes">save XMP data</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCheckButton" id="sv_iptc">
+                <property name="label" translatable="yes">save IPTC data</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCheckButton" id="sv_thumbnail">
+                <property name="label" translatable="yes">save thumbnail</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child type="label">
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Advanced</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="right_attach">3</property>
         <property name="top_attach">9</property>
         <property name="bottom_attach">10</property>
       </packing>
diff --git a/plug-ins/ui/plug-in-file-tiff.ui b/plug-ins/ui/plug-in-file-tiff.ui
new file mode 100644
index 0000000..ace9c2c
--- /dev/null
+++ b/plug-ins/ui/plug-in-file-tiff.ui
@@ -0,0 +1,404 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.24"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkVBox" id="tiff_export_vbox">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkFrame" id="frame1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label_xalign">0</property>
+        <property name="shadow_type">none</property>
+        <child>
+          <object class="GtkAlignment" id="alignment1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="left_padding">12</property>
+            <child>
+              <object class="GtkHBox" id="hbox1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkVBox" id="vbox2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">3</property>
+                    <child>
+                      <object class="GtkRadioButton" id="cmp_none">
+                        <property name="label" translatable="yes">no compression</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="cmp_lzw">
+                        <property name="label" translatable="yes">LZW</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">cmp_none</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="cmp_packbits">
+                        <property name="label" translatable="yes">PackBits</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">cmp_none</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="cmp_deflate">
+                        <property name="label" translatable="yes">Deflate</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">cmp_none</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="cmp_jpeg">
+                        <property name="label" translatable="yes">JPEG</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">cmp_none</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">4</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="padding">3</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkVBox" id="vbox4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkRadioButton" id="cmp_g3">
+                        <property name="label" translatable="yes">CCITT Group 3 fax</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">cmp_none</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="cmp_g4">
+                        <property name="label" translatable="yes">CCITT Group _4 fax</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">cmp_none</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="padding">3</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child type="label">
+          <object class="GtkLabel" id="compresslabel">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">&lt;b&gt;Compression&lt;/b&gt;</property>
+            <property name="use_markup">True</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="padding">3</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHSeparator" id="hseparator3">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkVBox" id="vbox7">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkCheckButton" id="sv_alpha">
+            <property name="label" translatable="yes">Save color values from transparent pixels</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="padding">6</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHSeparator" id="hseparator2">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">3</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkFrame" id="frame2">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label_xalign">0</property>
+        <property name="shadow_type">none</property>
+        <child>
+          <object class="GtkAlignment" id="alignment2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="left_padding">12</property>
+            <child>
+              <object class="GtkVBox" id="vbox5">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkEntry" id="commentfield">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">●</property>
+                    <property name="invisible_char_set">True</property>
+                    <property name="primary_icon_activatable">False</property>
+                    <property name="secondary_icon_activatable">False</property>
+                    <property name="primary_icon_sensitive">True</property>
+                    <property name="secondary_icon_sensitive">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="padding">3</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child type="label">
+          <object class="GtkLabel" id="commentlabel">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">&lt;b&gt;Comment&lt;/b&gt;</property>
+            <property name="use_markup">True</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="padding">3</property>
+        <property name="position">4</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHSeparator" id="hseparator1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">5</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkExpander" id="expander1">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <child>
+          <object class="GtkHBox" id="hbox2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkVBox" id="vbox3">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkCheckButton" id="sv_exif">
+                    <property name="label" translatable="yes">save EXIF data</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="sv_xmp">
+                    <property name="label" translatable="yes">save XMP data</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="vbox6">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkCheckButton" id="sv_iptc">
+                    <property name="label" translatable="yes">save IPTC data</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="sv_thumbnail">
+                    <property name="label" translatable="yes">save thumbnail</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child type="label">
+          <object class="GtkLabel" id="adv_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Advanced</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="padding">3</property>
+        <property name="position">6</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/plug-ins/ui/plug-in-metadata.ui b/plug-ins/ui/plug-in-metadata.ui
new file mode 100644
index 0000000..1d076bc
--- /dev/null
+++ b/plug-ins/ui/plug-in-metadata.ui
@@ -0,0 +1,908 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkListStore" id="exif-liststore">
+    <columns>
+      <!-- column-name c_exif_tag -->
+      <column type="gchararray"/>
+      <!-- column-name c_exif_value -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="xmp-liststore">
+    <columns>
+      <!-- column-name c_xmp_tag -->
+      <column type="gchararray"/>
+      <!-- column-name c_xmp_value -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkVBox" id="metadata-vbox">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkNotebook" id="metadata-notebook">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="scrollable">True</property>
+        <child>
+          <object class="GtkScrolledWindow" id="exif-scroll">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="border_width">6</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkTreeView" id="exif-treeview">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="model">exif-liststore</property>
+                <property name="headers_clickable">False</property>
+                <property name="search_column">0</property>
+                <child internal-child="selection">
+                  <object class="GtkTreeSelection" id="exif-treeview-selection"/>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="exif_tag_column">
+                    <property name="resizable">True</property>
+                    <property name="spacing">3</property>
+                    <property name="title" translatable="yes">EXIF Tag</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="exif_tag_cell_renderer"/>
+                      <attributes>
+                        <attribute name="text">0</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="exif_value_column">
+                    <property name="resizable">True</property>
+                    <property name="spacing">3</property>
+                    <property name="title" translatable="yes">Value</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="exif_value_cell_renderer"/>
+                      <attributes>
+                        <attribute name="text">1</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child type="tab">
+          <object class="GtkLabel" id="exif">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xpad">4</property>
+            <property name="ypad">4</property>
+            <property name="label" translatable="yes">EXIF</property>
+          </object>
+          <packing>
+            <property name="tab_expand">True</property>
+            <property name="tab_fill">False</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="xmp-scroll">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="border_width">6</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkTreeView" id="xm-ptreeview">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="model">xmp-liststore</property>
+                <property name="headers_clickable">False</property>
+                <property name="search_column">0</property>
+                <child internal-child="selection">
+                  <object class="GtkTreeSelection" id="xmp-treeview-selection"/>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="xmp_tag_column">
+                    <property name="resizable">True</property>
+                    <property name="spacing">3</property>
+                    <property name="title" translatable="yes">XMP Tag</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="xmp_tag_cell_renderer"/>
+                      <attributes>
+                        <attribute name="text">0</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="xmp_value_column">
+                    <property name="resizable">True</property>
+                    <property name="spacing">3</property>
+                    <property name="title" translatable="yes">Value</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="xmp_value_cell_renderer"/>
+                      <attributes>
+                        <attribute name="text">1</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child type="tab">
+          <object class="GtkLabel" id="xmp">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xpad">4</property>
+            <property name="ypad">4</property>
+            <property name="label" translatable="yes">XMP</property>
+          </object>
+          <packing>
+            <property name="position">1</property>
+            <property name="tab_fill">False</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="box1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">6</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkNotebook" id="iptc-notebook">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <child>
+                  <object class="GtkVBox" id="box2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkTable" id="desctable">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="border_width">6</property>
+                        <property name="n_rows">8</property>
+                        <property name="n_columns">2</property>
+                        <property name="column_spacing">3</property>
+                        <property name="row_spacing">3</property>
+                        <child>
+                          <object class="GtkLabel" id="l_title">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Title</property>
+                            <property name="single_line_mode">True</property>
+                          </object>
+                          <packing>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_author">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Author</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_authortitle">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Authortitle</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_copyright">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Copyright</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_caption">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Caption</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">4</property>
+                            <property name="bottom_attach">5</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_captionwriter">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Captionwriter</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">5</property>
+                            <property name="bottom_attach">6</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_headline">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Headline</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">6</property>
+                            <property name="bottom_attach">7</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_specialinstruct">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Special
+Instructions</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">7</property>
+                            <property name="bottom_attach">8</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.ObjectName">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.Byline">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.BylineTitle">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.Copyright">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.Writer">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">5</property>
+                            <property name="bottom_attach">6</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkScrolledWindow" id="caption_scroll">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkTextView" id="Iptc.Application2.Caption">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">4</property>
+                            <property name="bottom_attach">5</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkScrolledWindow" id="headline_scroll">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkTextView" id="Iptc.Application2.Headline">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">6</property>
+                            <property name="bottom_attach">7</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkScrolledWindow" id="instruct_scroll">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkTextView" id="Iptc.Application2.SpecialInstructions">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">7</property>
+                            <property name="bottom_attach">8</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+                <child type="tab">
+                  <object class="GtkLabel" id="iptcdesc">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xpad">2</property>
+                    <property name="ypad">2</property>
+                    <property name="label" translatable="yes">Description</property>
+                  </object>
+                  <packing>
+                    <property name="tab_fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkVBox" id="box3">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkTable" id="table3">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="border_width">6</property>
+                        <property name="n_rows">4</property>
+                        <property name="n_columns">2</property>
+                        <property name="column_spacing">3</property>
+                        <property name="row_spacing">3</property>
+                        <child>
+                          <object class="GtkLabel" id="l_keywords1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Keywords</property>
+                          </object>
+                          <packing>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_category1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Category</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_suppcat1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Supplemental
+Category</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_urgency1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Urgency</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkScrolledWindow" id="scrolledwindow10">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkTextView" id="Iptc.Application2.Keywords">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkScrolledWindow" id="scrolledwindow12">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkTextView" id="Iptc.Application2.SuppCategory">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.Urgency">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.Category">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child type="tab">
+                  <object class="GtkLabel" id="iptckeys">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xpad">2</property>
+                    <property name="ypad">2</property>
+                    <property name="label" translatable="yes">Keywords/Categories</property>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                    <property name="tab_fill">False</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkVBox" id="box4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkTable" id="table4">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="border_width">6</property>
+                        <property name="n_rows">9</property>
+                        <property name="n_columns">2</property>
+                        <property name="column_spacing">3</property>
+                        <property name="row_spacing">3</property>
+                        <child>
+                          <object class="GtkHSeparator" id="hseparator4">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                          </object>
+                          <packing>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_credit">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Credit</property>
+                          </object>
+                          <packing>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_source">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Source</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.Credit">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.Source">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_transmission">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Transmission
+reference</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">8</property>
+                            <property name="bottom_attach">9</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.TransmissionReference">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">8</property>
+                            <property name="bottom_attach">9</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_city">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">City</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.City">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_sublocation">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Sublocation</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">4</property>
+                            <property name="bottom_attach">5</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.SubLocation">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">4</property>
+                            <property name="bottom_attach">5</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_province">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="xpad">3</property>
+                            <property name="ypad">3</property>
+                            <property name="label" translatable="yes">Province/State</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">5</property>
+                            <property name="bottom_attach">6</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.ProvinceState">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">5</property>
+                            <property name="bottom_attach">6</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="l_country">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Country</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">6</property>
+                            <property name="bottom_attach">7</property>
+                            <property name="x_options">GTK_SHRINK | GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="Iptc.Application2.CountryName">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">6</property>
+                            <property name="bottom_attach">7</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkHSeparator" id="hseparator2">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                          </object>
+                          <packing>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">7</property>
+                            <property name="bottom_attach">8</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child type="tab">
+                  <object class="GtkLabel" id="iptccredits">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xpad">2</property>
+                    <property name="ypad">2</property>
+                    <property name="label" translatable="yes">Credits/Origin</property>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                    <property name="tab_fill">False</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="iptc-write-button">
+                <property name="label" translatable="yes">Write IPTC Data</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child type="tab">
+          <object class="GtkLabel" id="iptc">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xpad">4</property>
+            <property name="ypad">4</property>
+            <property name="label" translatable="yes">IPTC</property>
+          </object>
+          <packing>
+            <property name="position">2</property>
+            <property name="tab_fill">False</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/po-libgimp/POTFILES.in b/po-libgimp/POTFILES.in
index 9c9a88a..4a9164c 100644
--- a/po-libgimp/POTFILES.in
+++ b/po-libgimp/POTFILES.in
@@ -11,6 +11,7 @@ libgimp/gimpfontselectbutton.c
 libgimp/gimpgradientmenu.c
 libgimp/gimpgradientselectbutton.c
 libgimp/gimpmenu.c
+libgimp/gimpmetadata.c
 libgimp/gimppalettemenu.c
 libgimp/gimppaletteselectbutton.c
 libgimp/gimppatternmenu.c
diff --git a/po-plug-ins/POTFILES.in b/po-plug-ins/POTFILES.in
index d9c9502..a6518e5 100644
--- a/po-plug-ins/POTFILES.in
+++ b/po-plug-ins/POTFILES.in
@@ -96,6 +96,7 @@ plug-ins/common/lens-apply.c
 plug-ins/common/lens-flare.c
 plug-ins/common/mail.c
 plug-ins/common/max-rgb.c
+plug-ins/common/metadata.c
 plug-ins/common/newsprint.c
 plug-ins/common/nl-filter.c
 plug-ins/common/noise-rgb.c
@@ -187,6 +188,8 @@ plug-ins/gimpressionist/sizemap.c
 plug-ins/gimpressionist/utils.c
 [type: gettext/glade]plug-ins/ui/plug-in-file-gif.ui
 [type: gettext/glade]plug-ins/ui/plug-in-file-png.ui
+[type: gettext/glade]plug-ins/ui/plug-in-file-tiff.ui
+[type: gettext/glade]plug-ins/ui/plug-in-metadata.ui
 plug-ins/gradient-flare/gradient-flare.c
 plug-ins/help-browser/dialog.c
 plug-ins/help-browser/help-browser.c
diff --git a/tools/pdbgen/pdb/image.pdb b/tools/pdbgen/pdb/image.pdb
index 50a9342..94d6ebc 100644
--- a/tools/pdbgen/pdb/image.pdb
+++ b/tools/pdbgen/pdb/image.pdb
@@ -1625,6 +1625,61 @@ CODE
     );
 }
 
+sub image_get_metadata {
+    $blurb = "Returns the image's metadata.";
+    $help  = 'Returns exif/iptc/xmp metadata from the image.';
+
+    &std_pdb_misc('2013', '2.10');
+
+    @inargs = (
+        { name => 'image', type => 'image',
+          desc => 'The image' }
+    );
+
+    @outargs = (
+       { name => 'metadata_string', type => 'string', wrap => 1,
+         desc => 'The exif/ptc/xmp metadata as a string'}
+    );
+
+    %invoke = (
+       code => <<'CODE'
+{
+  GimpMetadata *metadata = gimp_image_get_metadata (image);
+
+  if (metadata)
+    metadata_string = gimp_metadata_serialize (metadata);
+}
+CODE
+    );
+}
+
+sub image_set_metadata {
+    $blurb = "Set the image's metadata.";
+    $help  = 'Sets exif/iptc/xmp metadata on the image.';
+
+    &std_pdb_misc('2013', '2.10');
+
+    @inargs = (
+        { name => 'image', type => 'image',
+          desc => 'The image' },
+       { name => 'metadata_string', type => 'string', wrap => 1,
+         desc => 'The exif/ptc/xmp metadata as a string' }
+    );
+
+    %invoke = (
+       code => <<'CODE'
+{
+  GimpMetadata *metadata = gimp_metadata_deserialize (metadata_string);
+
+  gimp_image_set_metadata (image, metadata);
+
+  if (metadata)
+    g_object_unref (metadata);
+}
+CODE
+    );
+}
+
 sub image_clean_all {
     $blurb = 'Set the image dirty count to 0.';
 
@@ -2988,6 +3043,7 @@ CODE
               "libgimpbase/gimpbase.h"
               "core/gimp.h"
               "core/gimpcontainer.h"
+              "core/gimpimage-metadata.h"
               "core/gimpprogress.h"
               "core/gimptempbuf.h"
               "core/gimpunit.h"
@@ -3029,6 +3085,7 @@ CODE
             image_flatten image_merge_visible_layers image_merge_down
             image_add_layer_mask image_remove_layer_mask
             image_get_colormap image_set_colormap
+            image_get_metadata image_set_metadata
             image_clean_all image_is_dirty
             image_thumbnail
             image_get_active_layer image_set_active_layer
@@ -3060,7 +3117,7 @@ CODE
 # image_add_layer_mask and image_remove_layer_mask.
 # If adding or removing functions, make sure the range below is
 # updated correctly!
-%exports = (app => [ procs], lib => [ procs[0  44,47..85]]);
+%exports = (app => [ procs], lib => [ procs[0  44,47..87]]);
 
 $desc = 'Image';
 $doc_title = 'gimpimage';



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