Commit dc9856cf authored by Benoit Touchette's avatar Benoit Touchette Committed by Michael Natterer

Bug 769820 - Cannot enter Iptc information when no metadata is available...

...and fails to write it to file when it is

Completely redo metadata editor and viewer code, adds support for
Xmp.xmpMM.History and more.
parent 98f7fc85
......@@ -2548,6 +2548,7 @@ plug-ins/imagemap/images/Makefile
plug-ins/lighting/Makefile
plug-ins/lighting/images/Makefile
plug-ins/map-object/Makefile
plug-ins/metadata/Makefile
plug-ins/pagecurl/Makefile
plug-ins/print/Makefile
plug-ins/pygimp/Makefile
......
......@@ -21,6 +21,7 @@
#include "config.h"
#include <string.h>
#include <sys/time.h>
#include <gtk/gtk.h>
#include <gexiv2/gexiv2.h>
......@@ -31,6 +32,12 @@
#include "libgimp-intl.h"
typedef struct
{
gchar *tag;
gint type;
} xmpstructs;
static void gimp_image_metadata_rotate (gint32 image_ID,
GExiv2Orientation orientation);
......@@ -78,28 +85,6 @@ gimp_image_metadata_load_prepare (gint32 image_ID,
if (metadata)
{
#if 0
{
gchar *xml = gimp_metadata_serialize (metadata);
GimpMetadata *new = gimp_metadata_deserialize (xml);
gchar *xml2 = gimp_metadata_serialize (new);
FILE *f = fopen ("/tmp/gimp-test-xml1", "w");
fprintf (f, "%s", xml);
fclose (f);
f = fopen ("/tmp/gimp-test-xml2", "w");
fprintf (f, "%s", xml2);
fclose (f);
system ("diff -u /tmp/gimp-test-xml1 /tmp/gimp-test-xml2");
g_free (xml);
g_free (xml2);
g_object_unref (new);
}
#endif
gexiv2_metadata_erase_exif_thumbnail (GEXIV2_METADATA (metadata));
}
......@@ -377,6 +362,7 @@ gimp_image_metadata_save_prepare (gint32 image_ID,
return metadata;
}
/**
* gimp_image_metadata_save_finish:
* @image_ID: The image
......@@ -456,7 +442,72 @@ gimp_image_metadata_save_finish (gint32 image_ID,
if ((flags & GIMP_METADATA_SAVE_XMP) && support_xmp)
{
gchar **xmp_data = gexiv2_metadata_get_xmp_tags (GEXIV2_METADATA (metadata));
gchar **xmp_data;
struct timeval timer_usec;
long long int timestamp_usec;
gchar ts[1024];
gimp_metadata_register_xmp_namespaces ();
gettimeofday(&timer_usec, NULL);
timestamp_usec = ((long long int) timer_usec.tv_sec) * 1000000ll +
(long long int) timer_usec.tv_usec;
sprintf((gchar*)&ts, "%lld", timestamp_usec);
gimp_metadata_add_xmp_history (GEXIV2_METADATA (metadata),
"");
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
"Xmp.GIMP.TimeStamp",
ts);
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
"Xmp.xmp.CreatorTool",
N_("GIMP 2.9/2.10"));
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
"Xmp.GIMP.Version",
GIMP_VERSION);
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
"Xmp.GIMP.API",
GIMP_API_VERSION);
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
"Xmp.GIMP.Platform",
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
"Windows");
#elif defined(__linux__)
"Linux");
#elif defined(__APPLE__) && defined(__MACH__)
"Mac OS");
#elif defined(unix) || defined(__unix__) || defined(__unix)
"Unix");
#else
"Unknown");
#endif
xmp_data = gexiv2_metadata_get_xmp_tags (GEXIV2_METADATA (metadata));
/* Patch necessary structures */
xmpstructs structlist[] =
{
{ "Xmp.iptcExt.LocationCreated", GEXIV2_STRUCTURE_XA_BAG },
{ "Xmp.iptcExt.LocationShown", GEXIV2_STRUCTURE_XA_BAG },
{ "Xmp.iptcExt.ArtworkOrObject", GEXIV2_STRUCTURE_XA_BAG },
{ "Xmp.iptcExt.RegistryId", GEXIV2_STRUCTURE_XA_BAG },
{ "Xmp.xmpMM.History", GEXIV2_STRUCTURE_XA_SEQ },
{ "Xmp.plus.ImageSupplier", GEXIV2_STRUCTURE_XA_SEQ },
{ "Xmp.plus.ImageCreator", GEXIV2_STRUCTURE_XA_SEQ },
{ "Xmp.plus.CopyrightOwner", GEXIV2_STRUCTURE_XA_SEQ },
{ "Xmp.plus.Licensor", GEXIV2_STRUCTURE_XA_SEQ }
};
for (i = 0; i < 9; i++)
{
gexiv2_metadata_set_xmp_tag_struct(GEXIV2_METADATA (new_g2metadata),
structlist[i].tag,
structlist[i].type);
}
for (i = 0; xmp_data[i] != NULL; i++)
{
......
......@@ -120,7 +120,6 @@ static const gchar *unsupported_tags[] =
"Exif.Image.ClipPath",
"Exif.Image.XClipPathUnits",
"Exif.Image.YClipPathUnits",
"Xmp.xmpMM.History",
"Exif.Image.XPTitle",
"Exif.Image.XPComment",
"Exif.Image.XPAuthor",
......@@ -189,6 +188,292 @@ gimp_metadata_init (GimpMetadata *metadata)
*/
}
/**
* gimp_metadata_register_xmp_namespace:
*
* Register XMP a new namespace.
*
* Since: 2.10
*/
void
gimp_metadata_register_xmp_namespace (const gchar* nspace, const gchar* prefix)
{
gboolean gexiv2_registered_namespace;
gexiv2_registered_namespace =
gexiv2_metadata_register_xmp_namespace(nspace, prefix);
if (gexiv2_registered_namespace == FALSE)
{
g_printerr("Failed to register %s namespace\n", prefix);
}
}
/**
* gimp_metadata_register_xmp_namespaces:
*
* Register XMP namespaces for GIMP and DICOM.
*
* Since: 2.10
*/
void
gimp_metadata_register_xmp_namespaces (void)
{
gimp_metadata_register_xmp_namespace ("http://ns.adobe.com/DICOM/",
"DICOM");
/* Usage example Xmp.GIMP.tagname */
gimp_metadata_register_xmp_namespace ("http://www.gimp.org/xmp/",
"GIMP");
}
/**
* gimp_metadata_get_guid:
*
* Generate Version 4 UUID/GUID.
*
* Return value: The new GUID/UUID string.
*
* Since: 2.10
*/
gchar*
gimp_metadata_get_guid (void)
{
const int DALLOC = 36;
struct timespec ts;
long time;
gint shake;
gint bake;
gchar *GUID;
gchar *szHex;
for (shake = 0; shake < 10; shake++)
{
timespec_get(&ts, TIME_UTC);
time = ts.tv_nsec / 1000;
srand (time);
}
GUID = (gchar*) g_malloc(DALLOC);
if (GUID == NULL)
{
return NULL;
}
memset(GUID, 0, DALLOC);
bake = 0;
szHex = "0123456789abcdef-";
for (bake = 0; bake < DALLOC; bake++)
{
int r = rand () % 16;
char c = ' ';
switch (bake)
{
default :
c = szHex [r];
break;
case 19 :
c = szHex [r & 0x03 | 0x08];
break;
case 8 :
case 13 :
case 18 :
case 23 :
c = '-';
break;
case 14 :
c = '4';
break;
}
GUID[bake] = ( bake < DALLOC ) ? c : 0x00;
}
return GUID;
}
/**
* gimp_metadata_add_history:
*
* Add XMP mm History data to file metadata.
*
* Since: 2.10
*/
void
gimp_metadata_add_xmp_history (GimpMetadata *metadata,
gchar *state_status)
{
time_t now;
struct tm* now_tm;
char timestr[256];
char tzstr[7];
gchar iid_data[256];
gchar strdata[1024];
gchar tagstr[1024];
gchar *uuid;
gint id_count;
gint found;
gint lastfound;
gchar *tags[] =
{
"Xmp.xmpMM.InstanceID",
"Xmp.xmpMM.DocumentID",
"Xmp.xmpMM.OriginalDocumentID",
"Xmp.xmpMM.History"
};
gchar *history_tags[] =
{
"/stEvt:action",
"/stEvt:instanceID",
"/stEvt:when",
"/stEvt:softwareAgent",
"/stEvt:changed"
};
/* Update new Instance ID */
uuid = gimp_metadata_get_guid();
strcpy((gchar*)&iid_data, "xmp.iid:");
strcat((gchar*)&iid_data, uuid);
gexiv2_metadata_set_tag_string (metadata,
tags[0],
(gchar*)&iid_data);
if (uuid)
g_free(uuid);
/* Update new Document ID if none found */
gchar *did = gexiv2_metadata_get_tag_interpreted_string (metadata,
tags[1]);
if (!did || strlen(did) < 1)
{
gchar did_data[256];
uuid = gimp_metadata_get_guid();
strcpy((gchar*)&did_data, "gimp:docid:gimp:");
strcat((gchar*)&did_data, uuid);
gexiv2_metadata_set_tag_string (metadata,
tags[1],
(gchar*)&did_data);
if (uuid)
g_free(uuid);
}
/* Update new Original Document ID if none found */
gchar *odid = gexiv2_metadata_get_tag_interpreted_string (metadata,
tags[2]);
if (!odid || strlen(odid) < 1)
{
gchar did_data[256];
gchar *uuid = gimp_metadata_get_guid();
strcpy((gchar*)&did_data, "xmp.did:");
strcat((gchar*)&did_data, uuid);
gexiv2_metadata_set_tag_string (metadata,
tags[2],
(gchar*)&did_data);
if (uuid)
g_free(uuid);
}
/* Handle Xmp.xmpMM.History */
gexiv2_metadata_set_xmp_tag_struct(metadata,
tags[3],
GEXIV2_STRUCTURE_XA_SEQ);
/* Find current number of entries for Xmp.xmpMM.History */
found = 0;
for (gint count = 1; count < 65536; count++)
{
lastfound = 0;
for (int ii = 0; ii < 5; ii++)
{
g_sprintf((gchar*)&tagstr, "%s[%d]%s", tags[3], count, history_tags[ii]);
if (gexiv2_metadata_has_tag(metadata, (gchar*)&tagstr))
{
lastfound = 1;
}
}
if (lastfound == 0)
break;
found++;
}
id_count = found + 1;
memset(tagstr, 0, 1024);
memset(strdata, 0, 1024);
g_sprintf((gchar*)&tagstr, "%s[%d]%s", tags[3], id_count, history_tags[0]);
gexiv2_metadata_set_tag_string (metadata,
(gchar*)&tagstr,
"saved");
memset(tagstr, 0, 1024);
memset(strdata, 0, 1024);
uuid = gimp_metadata_get_guid();
g_sprintf((gchar*)&tagstr, "%s[%d]%s", tags[3], id_count, history_tags[1]);
g_sprintf((gchar*)&strdata, "xmp.iid:%s", uuid);
gexiv2_metadata_set_tag_string (metadata,
(gchar*)&tagstr,
(gchar*)&strdata);
if (uuid)
g_free(uuid);
memset(tagstr, 0, 1024);
memset(strdata, 0, 1024);
g_sprintf((gchar*)&tagstr, "%s[%d]%s", tags[3], id_count, history_tags[2]);
/* get local time */
time(&now);
now_tm = localtime(&now);
/* get timezone and fix format */
strftime (tzstr, 7, "%z", now_tm);
tzstr[5] = tzstr[4];
tzstr[4] = tzstr[3];
tzstr[3] = ':';
/* get current time and timezone string */
strftime (timestr, 256, "%Y-%m-%dT%H:%M:%S", now_tm);
g_sprintf((gchar*)&timestr, "%s%s",(gchar*)&timestr, tzstr);
gexiv2_metadata_set_tag_string (metadata,
(gchar*)&tagstr,
(gchar*)&timestr);
memset(tagstr, 0, 1024);
memset(strdata, 0, 1024);
g_sprintf((gchar*)&tagstr, "%s[%d]%s", tags[3], id_count, history_tags[3]);
gexiv2_metadata_set_tag_string (metadata,
(gchar*)&tagstr,
"Gimp 2.9/2.10 "
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
"(Windows)");
#elif defined(__linux__)
"(Linux)");
#elif defined(__APPLE__) && defined(__MACH__)
"(Mac OS)");
#elif defined(unix) || defined(__unix__) || defined(__unix)
"(Unix)");
#else
"(Unknown)");
#endif
memset(tagstr, 0, 1024);
memset(strdata, 0, 1024);
g_sprintf((gchar*)&tagstr, "%s[%d]%s", tags[3], id_count, history_tags[4]);
strcpy((gchar*)&strdata, "/");
strcat((gchar*)&strdata, state_status);
gexiv2_metadata_set_tag_string (metadata,
(gchar*)&tagstr,
(gchar*)&strdata);
}
/**
* gimp_metadata_new:
......@@ -209,6 +494,8 @@ gimp_metadata_new (void)
metadata = g_object_new (GIMP_TYPE_METADATA, NULL);
gexiv2_metadata_new ();
gimp_metadata_register_xmp_namespaces ();
if (! gexiv2_metadata_open_buf (GEXIV2_METADATA (metadata),
wilber_jpg, wilber_jpg_len,
NULL))
......@@ -587,6 +874,7 @@ gimp_metadata_load_from_file (GFile *file,
if (gexiv2_initialize ())
{
meta = g_object_new (GIMP_TYPE_METADATA, NULL);
gimp_metadata_register_xmp_namespaces ();
if (! gexiv2_metadata_open_path (GEXIV2_METADATA (meta), filename, error))
{
......@@ -721,6 +1009,55 @@ gimp_metadata_set_from_exif (GimpMetadata *metadata,
return TRUE;
}
/**
* gimp_metadata_set_from_iptc:
* @metadata: A #GimpMetadata instance.
* @iptc_data: The blob of Ipc data to set
* @iptc_data_length:Length of @iptc_data, in bytes
* @error: Return location for error message
*
* Sets the tags from a piece of IPTC data on @metadata.
*
* Return value: %TRUE on success, %FALSE otherwise.
*
* Since: 2.10
*/
gboolean
gimp_metadata_set_from_iptc (GimpMetadata *metadata,
const guchar *iptc_data,
gint iptc_data_length,
GError **error)
{
GimpMetadata *iptc_metadata;
g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), FALSE);
g_return_val_if_fail (iptc_data != NULL, FALSE);
g_return_val_if_fail (iptc_data_length > 0, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
iptc_metadata = gimp_metadata_new ();
if (! gexiv2_metadata_open_buf (iptc_metadata,
iptc_data, iptc_data_length, error))
{
g_object_unref (iptc_metadata);
return FALSE;
}
if (! gexiv2_metadata_has_iptc (iptc_metadata))
{
g_set_error (error, gimp_metadata_error_quark (), 0,
_("Parsing IPTC data failed."));
g_object_unref (iptc_metadata);
return FALSE;
}
gimp_metadata_add (iptc_metadata, metadata);
g_object_unref (iptc_metadata);
return TRUE;
}
/**
* gimp_metadata_set_from_xmp:
* @metadata: A #GimpMetadata instance.
......
......@@ -66,6 +66,10 @@ GimpMetadata * gimp_metadata_duplicate (GimpMetadata *metada
GimpMetadata * gimp_metadata_deserialize (const gchar *metadata_xml);
gchar * gimp_metadata_serialize (GimpMetadata *metadata);
gchar * gimp_metadata_get_guid (void);
void gimp_metadata_add_xmp_history (GimpMetadata *metadata,
gchar *state_status);
GimpMetadata * gimp_metadata_load_from_file (GFile *file,
GError **error);
......@@ -77,6 +81,10 @@ gboolean gimp_metadata_set_from_exif (GimpMetadata *metada
const guchar *exif_data,
gint exif_data_length,
GError **error);
gboolean gimp_metadata_set_from_iptc (GimpMetadata *metadata,
const guchar *iptc_data,
gint iptc_data_length,
GError **error);
gboolean gimp_metadata_set_from_xmp (GimpMetadata *metadata,
const guchar *xmp_data,
gint xmp_data_length,
......@@ -105,6 +113,11 @@ void gimp_metadata_set_colorspace (GimpMetadata *metada
gboolean gimp_metadata_is_tag_supported (const gchar *tag,
const gchar *mime_type);
void gimp_metadata_register_xmp_namespace (const gchar* nspace,
const gchar* prefix);
void gimp_metadata_register_xmp_namespaces (void);
G_END_DECLS
#endif /* __GIMP_METADATA_H__ */
......@@ -56,6 +56,7 @@ SUBDIRS = \
imagemap \
lighting \
map-object \
metadata \
pagecurl \
$(print) \
screenshot \
......
......@@ -142,8 +142,6 @@
/mail.exe
/max-rgb
/max-rgb.exe
/metadata
/metadata.exe
/newsprint
/newsprint.exe
/nl-filter
......
......@@ -119,7 +119,6 @@ libexec_PROGRAMS = \
jigsaw \
$(MAIL) \
max-rgb \
metadata \
newsprint \
nl-filter \
oilify \
......@@ -1406,26 +1405,6 @@ 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
......
......@@ -68,7 +68,6 @@ hot_RC = hot.rc.o
jigsaw_RC = jigsaw.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
oilify_RC = oilify.rc.o
......
......@@ -69,7 +69,6 @@
'jigsaw' => { 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 },
'oilify' => { ui => 1 },
......
/Makefile.in
/Makefile
/.deps
/_libs
/.libs
/metadata-editor
/metadata-editor.exe
/metadata-viewer
/metadata-viewer.exe
## Process this file with automake to produce Makefile.in
if OS_WIN32
mwindows = -mwindows
else
libm = -lm
endif
libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la
libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la
libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la
libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la
libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la
if HAVE_WINDRES
include $(top_srcdir)/build/windows/gimprc-plug-ins.rule
metadata_editor_RC = metadata-editor.rc.o
metadata_viewer_RC = metadata-viewer.rc.o
endif
AM_LDFLAGS = $(mwindows)
libexecdir = $(gimpplugindir)/plug-ins
libexec_PROGRAMS = \
metadata-editor \
metadata-viewer
metadata_editor_SOURCES = \
metadata-impexp.c \
metadata-xml.c \
metadata-editor.c
metadata_viewer_SOURCES = \
metadata-viewer.c
AM_CPPFLAGS = \
-I$(top_srcdir) \
$(GTK_CFLAGS) \
$(GEGL_CFLAGS) \
-I$(includedir)
metadata_viewer_LDADD = \
$(libm) \
$(libgimpui) \
$(libgimpwidgets) \
$(libgimpconfig) \
$(libgimp) \
$(libgimpcolor) \
$(libgimpmath) \
$(libgimpbase) \
$(GTK_LIBS) \
$(GEXIV2_LIBS) \
$(RT_LIBS) \
$(INTLLIBS) \
$(metadata_viewer_RC)
metadata_editor_LDADD = \
$(libm) \
$(libgimpui) \
$(libgimpwidgets) \
$(libgimpconfig) \
$(libgimp) \
$(libgimpcolor) \
$(libgimpmath) \
$(libgimpbase) \
$(GTK_LIBS) \
$(GEXIV2_LIBS) \
$(RT_LIBS) \
$(INTLLIBS) \
$(metadata_editor_RC)
This diff is collapsed.
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Copyright (C) 2016, 2017 Ben Touchette
*
* 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 __METADATA_EDITOR_H__
#define __METADATA_EDITOR_H__
extern void metadata_editor_write_callback (GtkWidget *dialog,
GtkBuilder *builder,
gint32 image_id);
#endif /* __METADATA_EDITOR_H__ */
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* metadata-editor.c
* Copyright (C) 2016, 2017 Ben Touchette
*
* 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