2013-10-20 00:38:01 +08:00
|
|
|
/* 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>
|
2013-10-28 04:11:19 +08:00
|
|
|
* Michael Natterer <mitch@gimp.org>
|
2013-10-20 00:38:01 +08:00
|
|
|
*
|
|
|
|
* 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
|
2018-07-12 05:27:07 +08:00
|
|
|
* <https://www.gnu.org/licenses/>.
|
2013-10-20 00:38:01 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
app, libgimp*, pdb: new GimpParamSpecObject abstract spec type.
This abstract spec type is basically a GParamSpecObject with a default
value. It will be used by various object spec with default values, such
as GimpParamSpecColor, GimpParamSpecUnit and all GimpParamSpecResource
subtypes. Also it has a duplicate() class method so that every spec type
can implement the proper way to duplicate itself.
This fixes the fact that in gimp_config_param_spec_duplicate(), all
unknown object spec types (because they are implemented in libgimp,
which is invisible to libgimpconfig) are just copied as
GParamSpecObject, hence losing default values and other parameters.
As a second enhancement, it also makes it easier to detect the object
spec types for which we have default value support in
gimp_config_reset_properties().
As a side fix, gimp_param_spec_color() now just always duplicates the
passed default color, making it hence much easier to avoid bugs when
reusing a GeglColor.
2024-09-03 05:38:13 +08:00
|
|
|
#include <gegl.h>
|
2013-10-20 00:38:01 +08:00
|
|
|
#include <gio/gio.h>
|
|
|
|
|
2013-11-02 00:36:26 +08:00
|
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
|
2013-10-20 00:38:01 +08:00
|
|
|
#include "gimpbasetypes.h"
|
|
|
|
|
2013-11-02 00:01:44 +08:00
|
|
|
#include "gimplimits.h"
|
2013-10-20 00:38:01 +08:00
|
|
|
#include "gimpmetadata.h"
|
app, libgimp*, pdb: new GimpParamSpecObject abstract spec type.
This abstract spec type is basically a GParamSpecObject with a default
value. It will be used by various object spec with default values, such
as GimpParamSpecColor, GimpParamSpecUnit and all GimpParamSpecResource
subtypes. Also it has a duplicate() class method so that every spec type
can implement the proper way to duplicate itself.
This fixes the fact that in gimp_config_param_spec_duplicate(), all
unknown object spec types (because they are implemented in libgimp,
which is invisible to libgimpconfig) are just copied as
GParamSpecObject, hence losing default values and other parameters.
As a second enhancement, it also makes it easier to detect the object
spec types for which we have default value support in
gimp_config_reset_properties().
As a side fix, gimp_param_spec_color() now just always duplicates the
passed default color, making it hence much easier to avoid bugs when
reusing a GeglColor.
2024-09-03 05:38:13 +08:00
|
|
|
#include "gimpparamspecs.h"
|
2013-10-20 00:38:01 +08:00
|
|
|
#include "gimpunit.h"
|
|
|
|
|
|
|
|
#include "libgimp/libgimp-intl.h"
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
/**
|
|
|
|
* SECTION: gimpmetadata
|
2019-08-01 18:48:41 +08:00
|
|
|
* @title: GimpMetadata
|
2017-01-04 02:36:22 +08:00
|
|
|
* @short_description: Basic functions for handling #GimpMetadata objects.
|
|
|
|
* @see_also: gimp_image_metadata_load_prepare(),
|
|
|
|
* gimp_image_metadata_load_finish(),
|
2020-10-24 00:47:47 +08:00
|
|
|
* gimp_image_metadata_save_prepare(),
|
|
|
|
* gimp_image_metadata_save_finish().
|
2017-01-04 02:36:22 +08:00
|
|
|
*
|
|
|
|
* Basic functions for handling #GimpMetadata objects.
|
|
|
|
**/
|
2013-10-28 04:11:19 +08:00
|
|
|
|
2024-07-24 22:20:34 +08:00
|
|
|
struct _GimpMetadata
|
|
|
|
{
|
|
|
|
GExiv2Metadata parent_instance;
|
|
|
|
};
|
2013-10-28 04:11:19 +08:00
|
|
|
|
2017-09-03 21:46:17 +08:00
|
|
|
#define GIMP_METADATA_ERROR gimp_metadata_error_quark ()
|
|
|
|
|
2023-05-22 23:54:58 +08:00
|
|
|
static GQuark gimp_metadata_error_quark (void);
|
|
|
|
static void gimp_metadata_copy_tag (GExiv2Metadata *src,
|
|
|
|
GExiv2Metadata *dest,
|
|
|
|
const gchar *tag);
|
|
|
|
static void gimp_metadata_copy_tags (GExiv2Metadata *src,
|
|
|
|
GExiv2Metadata *dest,
|
|
|
|
const gchar **tags);
|
|
|
|
static void gimp_metadata_add (GimpMetadata *src,
|
|
|
|
GimpMetadata *dest);
|
|
|
|
static void gimp_metadata_add_namespace (GHashTable *namespaces,
|
|
|
|
GString *xml,
|
|
|
|
gchar *prefix);
|
|
|
|
static void gimp_metadata_add_xmp_namespaces (GHashTable *namespaces,
|
|
|
|
GString *xml,
|
|
|
|
const gchar *tag);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
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",
|
|
|
|
"Exif.Image.XPTitle",
|
|
|
|
"Exif.Image.XPComment",
|
|
|
|
"Exif.Image.XPAuthor",
|
|
|
|
"Exif.Image.XPKeywords",
|
|
|
|
"Exif.Image.XPSubject",
|
|
|
|
"Exif.Image.DNGVersion",
|
|
|
|
"Exif.Image.DNGBackwardVersion",
|
2020-10-16 03:02:57 +08:00
|
|
|
"Exif.Iop",
|
|
|
|
/* FIXME Even though adding the tags below fixes the issue it's not very flexible.
|
|
|
|
It might be better in the long run if there was a way for a user to configure which
|
|
|
|
tags to block or a way for us to detect problems with tags before writing them. */
|
|
|
|
/* Issues #1367, #2253. Offending tag is PreviewOffset but the other Preview tags
|
|
|
|
(PreviewResolution, PreviewLength, PreviewImageBorders) also make no sense because
|
|
|
|
we are not including a Pentax specific preview image. */
|
|
|
|
"Exif.Pentax.Preview",
|
2021-11-22 07:39:35 +08:00
|
|
|
"Exif.PentaxDng.Preview",
|
|
|
|
/* Never save the complete brand specific MakerNote data. We load and
|
|
|
|
* should only save the specific brand tags inside the MakerNote.
|
|
|
|
* Sometimes the MakerNote is invalid or exiv2 doesn't know how to parse
|
|
|
|
* it. In that case we still get the (invalid) MakerNote, but not the
|
|
|
|
* individual tags or just a subset of them.
|
|
|
|
* If there are recognized brand specific tags, exiv2 will create the
|
|
|
|
* required MakerNote itself (which in can still be invalid but that's an
|
|
|
|
* exiv2 issue not ours). */
|
|
|
|
"Exif.Photo.MakerNote",
|
|
|
|
"Exif.MakerNote.ByteOrder",
|
|
|
|
"Exif.MakerNote.Offset",
|
2023-04-27 04:30:54 +08:00
|
|
|
/* Photoshop resources can contain sensitive data. We should not save the
|
|
|
|
* unedited original state. */
|
|
|
|
"Exif.Image.ImageResources",
|
|
|
|
"Exif.Image.0x935c",
|
2017-01-04 02:36:22 +08:00
|
|
|
};
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
static const guint8 minimal_exif[] =
|
|
|
|
{
|
|
|
|
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
|
|
|
|
0x01, 0x01, 0x00, 0x5a, 0x00, 0x5a, 0x00, 0x00, 0xff, 0xe1
|
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2024-07-24 22:20:34 +08:00
|
|
|
G_DEFINE_TYPE (GimpMetadata, gimp_metadata, GEXIV2_TYPE_METADATA)
|
2017-01-30 23:42:27 +08:00
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_metadata_class_init (GimpMetadataClass *klass)
|
|
|
|
{
|
2022-03-18 22:44:54 +08:00
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
if (! gexiv2_metadata_try_register_xmp_namespace ("http://ns.adobe.com/DICOM/",
|
|
|
|
"DICOM", &error))
|
2017-07-08 01:09:59 +08:00
|
|
|
{
|
2022-03-18 22:44:54 +08:00
|
|
|
g_printerr ("Failed to register XMP namespace 'DICOM': %s\n", error->message);
|
|
|
|
g_clear_error (&error);
|
2017-07-08 01:09:59 +08:00
|
|
|
}
|
2017-07-06 07:24:54 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (! gexiv2_metadata_try_register_xmp_namespace ("http://darktable.sf.net/",
|
|
|
|
"darktable", &error))
|
2018-01-09 14:58:01 +08:00
|
|
|
{
|
2022-03-18 22:44:54 +08:00
|
|
|
g_printerr ("Failed to register XMP namespace 'darktable': %s\n", error->message);
|
|
|
|
g_clear_error (&error);
|
2018-01-09 14:58:01 +08:00
|
|
|
}
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
/* Usage example Xmp.GIMP.tagname */
|
2022-03-18 22:44:54 +08:00
|
|
|
if (! gexiv2_metadata_try_register_xmp_namespace ("http://www.gimp.org/xmp/",
|
|
|
|
"GIMP", &error))
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
2022-03-18 22:44:54 +08:00
|
|
|
g_printerr ("Failed to register XMP namespace 'GIMP': %s\n", error->message);
|
|
|
|
g_clear_error (&error);
|
2017-07-06 07:24:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_init (GimpMetadata *metadata)
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_metadata_get_guid:
|
|
|
|
*
|
|
|
|
* Generate Version 4 UUID/GUID.
|
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: The new GUID/UUID string.
|
2017-07-06 07:24:54 +08:00
|
|
|
*
|
|
|
|
* Since: 2.10
|
|
|
|
*/
|
2017-07-08 01:09:59 +08:00
|
|
|
gchar *
|
2017-07-06 07:24:54 +08:00
|
|
|
gimp_metadata_get_guid (void)
|
|
|
|
{
|
2017-07-16 20:01:18 +08:00
|
|
|
GRand *rand;
|
|
|
|
gint bake;
|
|
|
|
gchar *GUID;
|
|
|
|
const gchar *szHex = "0123456789abcdef-";
|
2017-07-06 07:24:54 +08:00
|
|
|
|
2017-07-16 20:01:18 +08:00
|
|
|
rand = g_rand_new ();
|
2017-07-06 07:24:54 +08:00
|
|
|
|
2017-07-16 20:01:18 +08:00
|
|
|
#define DALLOC 36
|
|
|
|
|
2017-07-17 14:20:46 +08:00
|
|
|
GUID = g_malloc0 (DALLOC + 1);
|
2017-07-06 07:24:54 +08:00
|
|
|
|
|
|
|
for (bake = 0; bake < DALLOC; bake++)
|
2017-07-08 01:09:59 +08:00
|
|
|
{
|
2017-07-16 20:01:18 +08:00
|
|
|
gint r = g_rand_int (rand) % 16;
|
2017-07-08 01:09:59 +08:00
|
|
|
gchar c = ' ';
|
2017-07-06 07:24:54 +08:00
|
|
|
|
|
|
|
switch (bake)
|
2017-07-08 01:09:59 +08:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2017-07-06 07:24:54 +08:00
|
|
|
|
2017-07-16 20:01:18 +08:00
|
|
|
g_rand_free (rand);
|
|
|
|
|
2017-07-06 07:24:54 +08:00
|
|
|
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,
|
2017-07-08 01:09:59 +08:00
|
|
|
gchar *state_status)
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
2020-11-15 04:11:33 +08:00
|
|
|
time_t now;
|
|
|
|
struct tm *now_tm;
|
|
|
|
gchar *tmp;
|
|
|
|
char timestr[256];
|
|
|
|
char tzstr[7];
|
|
|
|
gchar strdata[1024];
|
|
|
|
gchar tagstr[1024];
|
|
|
|
gchar *uuid;
|
2023-05-13 05:52:57 +08:00
|
|
|
gchar *str;
|
2020-11-15 04:11:33 +08:00
|
|
|
gchar *did;
|
|
|
|
gchar *odid;
|
2022-01-21 03:18:53 +08:00
|
|
|
GError *error = NULL;
|
2020-11-15 04:11:33 +08:00
|
|
|
gint id_count;
|
|
|
|
gint found;
|
|
|
|
gint lastfound;
|
|
|
|
gint count;
|
|
|
|
int ii;
|
2017-07-06 07:24:54 +08:00
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
static const gchar *tags[] =
|
|
|
|
{
|
|
|
|
"Xmp.xmpMM.InstanceID",
|
|
|
|
"Xmp.xmpMM.DocumentID",
|
|
|
|
"Xmp.xmpMM.OriginalDocumentID",
|
|
|
|
"Xmp.xmpMM.History"
|
|
|
|
};
|
2017-07-06 07:24:54 +08:00
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
static const gchar *history_tags[] =
|
|
|
|
{
|
|
|
|
"/stEvt:action",
|
|
|
|
"/stEvt:instanceID",
|
|
|
|
"/stEvt:when",
|
|
|
|
"/stEvt:softwareAgent",
|
|
|
|
"/stEvt:changed"
|
|
|
|
};
|
|
|
|
|
|
|
|
g_return_if_fail (GIMP_IS_METADATA (metadata));
|
2017-07-06 07:24:54 +08:00
|
|
|
|
|
|
|
/* Update new Instance ID */
|
2017-07-08 01:09:59 +08:00
|
|
|
uuid = gimp_metadata_get_guid ();
|
|
|
|
|
2023-05-13 05:52:57 +08:00
|
|
|
str = g_strconcat ("xmp.iid:", uuid, NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
2023-05-13 05:52:57 +08:00
|
|
|
tags[0], str, NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
g_free (uuid);
|
2023-05-13 05:52:57 +08:00
|
|
|
g_free (str);
|
2017-07-06 07:24:54 +08:00
|
|
|
|
|
|
|
/* Update new Document ID if none found */
|
2022-01-21 03:18:53 +08:00
|
|
|
did = gexiv2_metadata_try_get_tag_interpreted_string (GEXIV2_METADATA (metadata),
|
|
|
|
tags[1], NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
if (! did || ! strlen (did))
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
2023-05-13 05:52:57 +08:00
|
|
|
gchar *did_data;
|
|
|
|
gchar *uuid = gimp_metadata_get_guid ();
|
2017-07-08 01:09:59 +08:00
|
|
|
|
2023-05-13 05:52:57 +08:00
|
|
|
did_data = g_strconcat ("gimp:docid:gimp:", uuid, NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
tags[1], did_data, NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
g_free (uuid);
|
2023-05-13 05:52:57 +08:00
|
|
|
g_free (did_data);
|
2017-07-06 07:24:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Update new Original Document ID if none found */
|
2022-01-21 03:18:53 +08:00
|
|
|
odid = gexiv2_metadata_try_get_tag_interpreted_string (GEXIV2_METADATA (metadata),
|
|
|
|
tags[2], NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
if (! odid || ! strlen (odid))
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
2023-05-13 05:52:57 +08:00
|
|
|
gchar *did_data;
|
2017-07-08 01:09:59 +08:00
|
|
|
gchar *uuid = gimp_metadata_get_guid ();
|
|
|
|
|
2023-05-13 05:52:57 +08:00
|
|
|
did_data = g_strconcat ("xmp.did:", uuid, NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
tags[2], did_data, NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
g_free (uuid);
|
2023-05-13 05:52:57 +08:00
|
|
|
g_free (did_data);
|
2017-07-06 07:24:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle Xmp.xmpMM.History */
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_xmp_tag_struct (GEXIV2_METADATA (metadata),
|
|
|
|
tags[3],
|
|
|
|
GEXIV2_STRUCTURE_XA_SEQ,
|
|
|
|
NULL);
|
2017-07-06 07:24:54 +08:00
|
|
|
|
|
|
|
/* Find current number of entries for Xmp.xmpMM.History */
|
|
|
|
found = 0;
|
2017-08-11 22:34:24 +08:00
|
|
|
for (count = 1; count < 65536; count++)
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
|
|
|
lastfound = 0;
|
2017-08-11 22:34:24 +08:00
|
|
|
for (ii = 0; ii < 5; ii++)
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
2017-07-08 01:09:59 +08:00
|
|
|
g_snprintf (tagstr, sizeof (tagstr), "%s[%d]%s",
|
|
|
|
tags[3], count, history_tags[ii]);
|
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (GEXIV2_METADATA (metadata),
|
|
|
|
tagstr, NULL))
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
|
|
|
lastfound = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lastfound == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
found++;
|
|
|
|
}
|
|
|
|
|
|
|
|
id_count = found + 1;
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
memset (tagstr, 0, sizeof (tagstr));
|
|
|
|
|
|
|
|
g_snprintf (tagstr, sizeof (tagstr), "%s[%d]%s",
|
|
|
|
tags[3], id_count, history_tags[0]);
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
tagstr, "saved", &error);
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to set metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, tagstr, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2017-07-08 01:09:59 +08:00
|
|
|
|
|
|
|
memset (tagstr, 0, sizeof (tagstr));
|
|
|
|
memset (strdata, 0, sizeof (strdata));
|
|
|
|
|
|
|
|
uuid = gimp_metadata_get_guid ();
|
|
|
|
|
|
|
|
g_snprintf (tagstr, sizeof (tagstr), "%s[%d]%s",
|
|
|
|
tags[3], id_count, history_tags[1]);
|
|
|
|
g_snprintf (strdata, sizeof (strdata), "xmp.iid:%s",
|
|
|
|
uuid);
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
tagstr, strdata, &error);
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to set metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, tagstr, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2017-07-08 01:09:59 +08:00
|
|
|
g_free(uuid);
|
|
|
|
|
|
|
|
memset (tagstr, 0, sizeof (tagstr));
|
|
|
|
|
|
|
|
g_snprintf (tagstr, sizeof (tagstr), "%s[%d]%s",
|
|
|
|
tags[3], id_count, history_tags[2]);
|
2017-07-06 07:24:54 +08:00
|
|
|
|
|
|
|
/* get local time */
|
2017-07-08 01:09:59 +08:00
|
|
|
time (&now);
|
|
|
|
now_tm = localtime (&now);
|
2017-07-06 07:24:54 +08:00
|
|
|
|
|
|
|
/* get timezone and fix format */
|
|
|
|
strftime (tzstr, 7, "%z", now_tm);
|
2018-10-22 21:48:26 +08:00
|
|
|
tzstr[6] = '\0';
|
2017-07-06 07:24:54 +08:00
|
|
|
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);
|
2020-11-15 04:11:33 +08:00
|
|
|
tmp = g_strdup_printf ("%s%s", timestr, tzstr);
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
tagstr, tmp, &error);
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to set metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, tagstr, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2020-11-15 04:11:33 +08:00
|
|
|
g_free (tmp);
|
2017-07-08 01:09:59 +08:00
|
|
|
|
|
|
|
memset (tagstr, 0, sizeof (tagstr));
|
|
|
|
|
|
|
|
g_snprintf (tagstr, sizeof (tagstr), "%s[%d]%s",
|
|
|
|
tags[3], id_count, history_tags[3]);
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
tagstr,
|
|
|
|
PACKAGE_STRING " "
|
2017-07-06 07:24:54 +08:00
|
|
|
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
|
2022-01-21 03:18:53 +08:00
|
|
|
"(Windows)",
|
2017-07-06 07:24:54 +08:00
|
|
|
#elif defined(__linux__)
|
2022-01-21 03:18:53 +08:00
|
|
|
"(Linux)",
|
2017-07-06 07:24:54 +08:00
|
|
|
#elif defined(__APPLE__) && defined(__MACH__)
|
2022-01-21 03:18:53 +08:00
|
|
|
"(Mac OS)",
|
2017-07-06 07:24:54 +08:00
|
|
|
#elif defined(unix) || defined(__unix__) || defined(__unix)
|
2022-01-21 03:18:53 +08:00
|
|
|
"(Unix)",
|
2017-07-06 07:24:54 +08:00
|
|
|
#else
|
2022-01-21 03:18:53 +08:00
|
|
|
"(Unknown)",
|
2017-07-06 07:24:54 +08:00
|
|
|
#endif
|
2022-01-21 03:18:53 +08:00
|
|
|
&error);
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to set metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, tagstr, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2017-07-06 07:24:54 +08:00
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
memset (tagstr, 0, sizeof (tagstr));
|
|
|
|
|
|
|
|
g_snprintf (tagstr, sizeof (tagstr), "%s[%d]%s",
|
|
|
|
tags[3], id_count, history_tags[4]);
|
|
|
|
|
2023-05-13 05:52:57 +08:00
|
|
|
str = g_strconcat ("/", state_status, NULL);
|
2017-07-08 01:09:59 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
2023-05-13 05:52:57 +08:00
|
|
|
tagstr, str, &error);
|
|
|
|
g_free (str);
|
2022-01-21 03:18:53 +08:00
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to set metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, tagstr, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2017-07-06 07:24:54 +08:00
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2013-10-28 04:11:19 +08:00
|
|
|
/**
|
|
|
|
* gimp_metadata_new:
|
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Creates a new #GimpMetadata instance.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: (transfer full): The new #GimpMetadata.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Since: 2.10
|
2013-10-28 04:11:19 +08:00
|
|
|
*/
|
2013-10-20 00:38:01 +08:00
|
|
|
GimpMetadata *
|
|
|
|
gimp_metadata_new (void)
|
|
|
|
{
|
2017-01-30 23:42:27 +08:00
|
|
|
GimpMetadata *metadata = NULL;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
if (gexiv2_initialize ())
|
|
|
|
{
|
2017-01-30 23:42:27 +08:00
|
|
|
metadata = g_object_new (GIMP_TYPE_METADATA, NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (! gexiv2_metadata_open_buf (GEXIV2_METADATA (metadata),
|
|
|
|
wilber_jpg, wilber_jpg_len,
|
2017-01-04 02:36:22 +08:00
|
|
|
NULL))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
g_object_unref (metadata);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
return NULL;
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
return metadata;
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
2013-10-28 04:11:19 +08:00
|
|
|
/**
|
|
|
|
* gimp_metadata_duplicate:
|
2017-01-04 02:36:22 +08:00
|
|
|
* @metadata: The object to duplicate, or %NULL.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Duplicates a #GimpMetadata instance.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: (transfer full):
|
2019-07-28 16:09:46 +08:00
|
|
|
* The new #GimpMetadata, or %NULL if @metadata is %NULL.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Since: 2.10
|
2013-10-28 04:11:19 +08:00
|
|
|
*/
|
2013-10-20 00:38:01 +08:00
|
|
|
GimpMetadata *
|
|
|
|
gimp_metadata_duplicate (GimpMetadata *metadata)
|
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
GimpMetadata *new_metadata = NULL;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_val_if_fail (metadata == NULL || GIMP_IS_METADATA (metadata), NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
if (metadata)
|
|
|
|
{
|
|
|
|
gchar *xml;
|
|
|
|
|
|
|
|
xml = gimp_metadata_serialize (metadata);
|
|
|
|
new_metadata = gimp_metadata_deserialize (xml);
|
2017-01-04 02:36:22 +08:00
|
|
|
g_free (xml);
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return new_metadata;
|
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
typedef struct
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
gchar name[1024];
|
2023-05-22 23:54:58 +08:00
|
|
|
gchar prefix[256];
|
2017-01-04 02:36:22 +08:00
|
|
|
gboolean base64;
|
2022-04-05 04:52:10 +08:00
|
|
|
gboolean excessive_message_shown;
|
2017-01-04 02:36:22 +08:00
|
|
|
GimpMetadata *metadata;
|
|
|
|
} GimpMetadataParseData;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static const gchar*
|
|
|
|
gimp_metadata_attribute_name_to_value (const gchar **attribute_names,
|
|
|
|
const gchar **attribute_values,
|
|
|
|
const gchar *name)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
while (*attribute_names)
|
|
|
|
{
|
|
|
|
if (! strcmp (*attribute_names, name))
|
|
|
|
{
|
|
|
|
return *attribute_values;
|
|
|
|
}
|
|
|
|
|
|
|
|
attribute_names++;
|
|
|
|
attribute_values++;
|
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_deserialize_start_element (GMarkupParseContext *context,
|
|
|
|
const gchar *element_name,
|
|
|
|
const gchar **attribute_names,
|
|
|
|
const gchar **attribute_values,
|
|
|
|
gpointer user_data,
|
|
|
|
GError **error)
|
|
|
|
{
|
|
|
|
GimpMetadataParseData *parse_data = user_data;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (! strcmp (element_name, "tag"))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
const gchar *name;
|
|
|
|
const gchar *encoding;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
name = gimp_metadata_attribute_name_to_value (attribute_names,
|
|
|
|
attribute_values,
|
|
|
|
"name");
|
|
|
|
encoding = gimp_metadata_attribute_name_to_value (attribute_names,
|
|
|
|
attribute_values,
|
|
|
|
"encoding");
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (! name)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-09-03 21:46:17 +08:00
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 1001,
|
2017-01-04 02:36:22 +08:00
|
|
|
"Element 'tag' does not contain required attribute 'name'.");
|
|
|
|
return;
|
|
|
|
}
|
2013-11-11 07:11:43 +08:00
|
|
|
|
2019-08-15 05:52:38 +08:00
|
|
|
g_strlcpy (parse_data->name, name, sizeof (parse_data->name));
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
parse_data->base64 = (encoding && ! strcmp (encoding, "base64"));
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
2023-05-22 23:54:58 +08:00
|
|
|
else if (! strcmp (element_name, "namespace"))
|
|
|
|
{
|
|
|
|
const gchar *url;
|
|
|
|
const gchar *prefix;
|
|
|
|
|
|
|
|
prefix = gimp_metadata_attribute_name_to_value (attribute_names,
|
|
|
|
attribute_values,
|
|
|
|
"prefix");
|
|
|
|
url = gimp_metadata_attribute_name_to_value (attribute_names,
|
|
|
|
attribute_values,
|
|
|
|
"url");
|
|
|
|
if (! prefix)
|
|
|
|
{
|
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 1002,
|
|
|
|
"Element 'namespace' does not contain required attribute 'prefix'.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (! url)
|
|
|
|
{
|
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 1003,
|
|
|
|
"Element 'namespace' does not contain required attribute 'url'.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_strlcpy (parse_data->prefix, prefix, sizeof (parse_data->prefix));
|
|
|
|
g_strlcpy (parse_data->name, url, sizeof (parse_data->name));
|
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_deserialize_end_element (GMarkupParseContext *context,
|
|
|
|
const gchar *element_name,
|
|
|
|
gpointer user_data,
|
|
|
|
GError **error)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_deserialize_text (GMarkupParseContext *context,
|
|
|
|
const gchar *text,
|
|
|
|
gsize text_len,
|
|
|
|
gpointer user_data,
|
|
|
|
GError **error)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
GimpMetadataParseData *parse_data = user_data;
|
|
|
|
const gchar *current_element;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
current_element = g_markup_parse_context_get_element (context);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (! g_strcmp0 (current_element, "tag"))
|
|
|
|
{
|
|
|
|
gchar *value = g_strndup (text, text_len);
|
2013-11-11 07:11:43 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (parse_data->base64)
|
|
|
|
{
|
|
|
|
guchar *decoded;
|
|
|
|
gsize len;
|
2013-11-11 07:11:43 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
decoded = g_base64_decode (value, &len);
|
|
|
|
|
|
|
|
if (decoded[len - 1] == '\0')
|
2019-01-07 00:46:31 +08:00
|
|
|
{
|
|
|
|
g_free (value);
|
|
|
|
value = (gchar *) decoded;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_clear_pointer (&value, g_free);
|
|
|
|
g_clear_pointer (&decoded, g_free);
|
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
2019-01-07 00:46:31 +08:00
|
|
|
|
|
|
|
if (value)
|
2013-11-11 07:11:43 +08:00
|
|
|
{
|
2019-01-07 00:46:31 +08:00
|
|
|
GExiv2Metadata *g2_metadata = GEXIV2_METADATA (parse_data->metadata);
|
2020-11-09 01:54:53 +08:00
|
|
|
GError *error = NULL;
|
2019-01-07 00:46:31 +08:00
|
|
|
gchar **values;
|
|
|
|
|
2020-11-09 01:54:53 +08:00
|
|
|
values = gexiv2_metadata_try_get_tag_multiple (g2_metadata,
|
|
|
|
parse_data->name,
|
|
|
|
&error);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2020-11-09 01:54:53 +08:00
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: %s\n", G_STRFUNC, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
g_strfreev (values);
|
|
|
|
}
|
|
|
|
else if (values)
|
2019-01-07 00:46:31 +08:00
|
|
|
{
|
|
|
|
guint length = g_strv_length (values);
|
|
|
|
|
2022-04-05 04:52:10 +08:00
|
|
|
if (length > 1000 &&
|
|
|
|
! g_strcmp0 (parse_data->name, "Xmp.photoshop.DocumentAncestors"))
|
2022-01-21 03:18:53 +08:00
|
|
|
{
|
2022-04-05 04:52:10 +08:00
|
|
|
/* Issue #8025, see also #7464 Some XCF images can have huge
|
|
|
|
* amounts of this tag, apparently due to a bug in PhotoShop.
|
|
|
|
* This makes deserializing it in the way we currently do
|
|
|
|
* too slow. Until we can change this let's ignore everything
|
|
|
|
* but the first 1000 values when serializing. */
|
|
|
|
|
|
|
|
if (! parse_data->excessive_message_shown)
|
|
|
|
{
|
|
|
|
g_message ("Excessive number of Xmp.photoshop.DocumentAncestors tags found. "
|
|
|
|
"Only keeping the first 1000 values.");
|
|
|
|
parse_data->excessive_message_shown = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
values = g_renew (gchar *, values, length + 2);
|
2022-06-03 05:45:13 +08:00
|
|
|
values[length] = g_strdup (value);
|
2022-04-05 04:52:10 +08:00
|
|
|
values[length + 1] = NULL;
|
|
|
|
|
|
|
|
gexiv2_metadata_try_set_tag_multiple (g2_metadata,
|
|
|
|
parse_data->name,
|
|
|
|
(const gchar **) values,
|
|
|
|
&error);
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_warning ("%s: failed to set multiple metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, parse_data->name, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2022-01-21 03:18:53 +08:00
|
|
|
}
|
2019-01-07 00:46:31 +08:00
|
|
|
g_strfreev (values);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (g2_metadata),
|
|
|
|
parse_data->name,
|
|
|
|
value, &error);
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_warning ("%s: failed to set metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, parse_data->name, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2019-01-07 00:46:31 +08:00
|
|
|
}
|
2022-06-03 03:14:33 +08:00
|
|
|
g_free (value);
|
2019-01-07 00:46:31 +08:00
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
2023-05-22 23:54:58 +08:00
|
|
|
else if (! g_strcmp0 (current_element, "namespace"))
|
|
|
|
{
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
gexiv2_metadata_try_register_xmp_namespace (parse_data->name,
|
|
|
|
parse_data->prefix,
|
|
|
|
&error);
|
|
|
|
if (error) {
|
|
|
|
g_warning ("%s: failed to register namespace %s (url: '%s'): %s\n",
|
|
|
|
G_STRFUNC, parse_data->prefix, parse_data->name,
|
|
|
|
error->message);
|
|
|
|
g_clear_error(&error);
|
|
|
|
}
|
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_deserialize_error (GMarkupParseContext *context,
|
|
|
|
GError *error,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
g_printerr ("Metadata parse error: %s\n", error->message);
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-04 02:36:22 +08:00
|
|
|
* gimp_metadata_deserialize:
|
|
|
|
* @metadata_xml: A string of serialized metadata XML.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Deserializes a string of XML that has been created by
|
|
|
|
* gimp_metadata_serialize().
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: (transfer full): The new #GimpMetadata.
|
2017-01-04 02:19:10 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Since: 2.10
|
2013-10-28 04:11:19 +08:00
|
|
|
*/
|
2017-01-04 02:36:22 +08:00
|
|
|
GimpMetadata *
|
|
|
|
gimp_metadata_deserialize (const gchar *metadata_xml)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
GimpMetadata *metadata;
|
|
|
|
GMarkupParser markup_parser;
|
|
|
|
GimpMetadataParseData parse_data;
|
|
|
|
GMarkupParseContext *context;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_return_val_if_fail (metadata_xml != NULL, NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
metadata = gimp_metadata_new ();
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
parse_data.metadata = metadata;
|
2022-04-05 04:52:10 +08:00
|
|
|
parse_data.excessive_message_shown = FALSE;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
markup_parser.start_element = gimp_metadata_deserialize_start_element;
|
|
|
|
markup_parser.end_element = gimp_metadata_deserialize_end_element;
|
|
|
|
markup_parser.text = gimp_metadata_deserialize_text;
|
|
|
|
markup_parser.passthrough = NULL;
|
|
|
|
markup_parser.error = gimp_metadata_deserialize_error;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
context = g_markup_parse_context_new (&markup_parser, 0, &parse_data, NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_markup_parse_context_parse (context,
|
|
|
|
metadata_xml, strlen (metadata_xml),
|
|
|
|
NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_markup_parse_context_unref (context);
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
return metadata;
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static gchar *
|
|
|
|
gimp_metadata_escape (const gchar *name,
|
|
|
|
const gchar *value,
|
|
|
|
gboolean *base64)
|
2013-11-01 21:15:15 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
if (! g_utf8_validate (value, -1, NULL))
|
|
|
|
{
|
|
|
|
gchar *encoded;
|
2013-11-11 07:11:43 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
encoded = g_base64_encode ((const guchar *) value, strlen (value) + 1);
|
2013-11-11 07:11:43 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_printerr ("Invalid UTF-8 in metadata value %s, encoding as base64: %s\n",
|
|
|
|
name, encoded);
|
2013-11-11 07:11:43 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
*base64 = TRUE;
|
2013-11-11 07:11:43 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
return encoded;
|
|
|
|
}
|
|
|
|
|
|
|
|
*base64 = FALSE;
|
|
|
|
|
|
|
|
return g_markup_escape_text (value, -1);
|
2013-11-01 21:15:15 +08:00
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_append_tag (GString *string,
|
|
|
|
const gchar *name,
|
|
|
|
gchar *value,
|
|
|
|
gboolean base64)
|
2013-11-11 07:11:43 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
if (value)
|
2017-01-04 02:19:10 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
if (base64)
|
|
|
|
{
|
|
|
|
g_string_append_printf (string, " <tag name=\"%s\" encoding=\"base64\">%s</tag>\n",
|
|
|
|
name, value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_string_append_printf (string, " <tag name=\"%s\">%s</tag>\n",
|
|
|
|
name, value);
|
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_free (value);
|
|
|
|
}
|
2013-11-11 07:11:43 +08:00
|
|
|
}
|
|
|
|
|
2023-05-22 23:54:58 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_add_namespace (GHashTable *namespaces,
|
|
|
|
GString *xml,
|
|
|
|
gchar *prefix)
|
|
|
|
{
|
|
|
|
if (! g_hash_table_lookup (namespaces, prefix))
|
|
|
|
{
|
2024-01-05 04:24:30 +08:00
|
|
|
gchar *namespace_url;
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
namespace_url = gexiv2_metadata_try_get_xmp_namespace_for_tag (prefix, &error);
|
|
|
|
|
|
|
|
if (! namespace_url)
|
|
|
|
{
|
|
|
|
/* Weird, we didn't find the namespace url.
|
|
|
|
Let's add a dummy url, that way we can keep the tags. */
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_warning ("XMP namespace url not found! %s", error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fix the one namespace url we know of, and add a generic fix for
|
|
|
|
any others. */
|
|
|
|
if (g_strcmp0 (prefix, "Item") == 0)
|
|
|
|
/* FIXME Remove this specific check for Item after this is fixed
|
|
|
|
in our dependencies (exiv2?), see issue #10557. */
|
|
|
|
namespace_url = g_strdup ("http://ns.google.com/photos/1.0/container/item/");
|
|
|
|
else
|
|
|
|
namespace_url = g_strdup_printf ("http://missing-url.org/%s/", prefix);
|
2023-05-22 23:54:58 +08:00
|
|
|
|
2024-01-05 04:24:30 +08:00
|
|
|
if (! gexiv2_metadata_try_register_xmp_namespace (namespace_url,
|
|
|
|
prefix, &error))
|
|
|
|
{
|
|
|
|
g_warning ("Registering XMP namespace failed! %s\n", error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
|
|
|
}
|
2023-05-22 23:54:58 +08:00
|
|
|
|
|
|
|
if (namespace_url)
|
|
|
|
{
|
|
|
|
g_debug ("Adding namespace %s, url: %s", prefix, namespace_url);
|
|
|
|
|
|
|
|
if (! g_hash_table_insert (namespaces, prefix, namespace_url))
|
|
|
|
g_warning ("Namespace already present: %s!", prefix);
|
|
|
|
|
|
|
|
g_string_append_printf (xml,
|
|
|
|
" <namespace prefix=\"%s\" url=\"%s\"></namespace>\n",
|
|
|
|
prefix, namespace_url);
|
|
|
|
|
|
|
|
/* namespace_url and prefix are added to hashtable, so we don't free here */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_free (prefix);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_free (prefix);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Register a namespace in our xml metadata for each XMP namespace.
|
|
|
|
* We use the following XML format:
|
|
|
|
* <namespace prefix="namespace-prefix" url="namespace-url"></namespace>
|
|
|
|
*
|
|
|
|
* There are two types of namespace prefixes:
|
|
|
|
* - Xmp.prefix.whatever, and
|
|
|
|
* - /prefix:something, which is prefixed by the above
|
|
|
|
*
|
|
|
|
* We use a hashtable to keep track of which namespaces we have already
|
|
|
|
* seen in the current run.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_metadata_add_xmp_namespaces (GHashTable *namespaces,
|
|
|
|
GString *xml,
|
|
|
|
const gchar *tag)
|
|
|
|
{
|
|
|
|
gchar *tag_ptr = (gchar *) tag;
|
|
|
|
gchar *prefix;
|
|
|
|
gchar **substrings;
|
|
|
|
|
|
|
|
/* Find word between the first and second '.' */
|
|
|
|
substrings = g_strsplit ((gchar *) tag_ptr, ".", 3);
|
|
|
|
if (substrings && substrings[1])
|
|
|
|
{
|
|
|
|
prefix = g_strdup (substrings[1]);
|
|
|
|
|
|
|
|
gimp_metadata_add_namespace (namespaces, xml, prefix);
|
|
|
|
}
|
|
|
|
g_strfreev (substrings);
|
|
|
|
|
|
|
|
/* Multiple namespaces in the form /prefix:value are possible in one tag. */
|
|
|
|
while (tag_ptr)
|
|
|
|
{
|
|
|
|
gchar *tag_next = NULL;
|
|
|
|
|
|
|
|
tag_ptr = strstr (tag_ptr, "/");
|
|
|
|
if (! tag_ptr || strlen (tag_ptr) <= 1)
|
|
|
|
break;
|
|
|
|
tag_ptr++;
|
|
|
|
tag_next = strstr (tag_ptr, ":");
|
|
|
|
|
|
|
|
if (tag_next)
|
|
|
|
{
|
|
|
|
gsize prefix_len = (gsize) tag_next - (gsize) tag_ptr + 1;
|
|
|
|
|
|
|
|
prefix = g_new (gchar, prefix_len);
|
|
|
|
g_strlcpy (prefix, tag_ptr, prefix_len);
|
|
|
|
|
|
|
|
gimp_metadata_add_namespace (namespaces, xml, prefix);
|
|
|
|
|
|
|
|
tag_ptr = tag_next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-20 00:38:01 +08:00
|
|
|
/**
|
2017-01-04 02:36:22 +08:00
|
|
|
* gimp_metadata_serialize:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Serializes @metadata into an XML string that can later be deserialized
|
|
|
|
* using gimp_metadata_deserialize().
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: The serialized XML string.
|
2017-01-04 02:19:10 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Since: 2.10
|
2013-10-20 00:38:01 +08:00
|
|
|
*/
|
|
|
|
gchar *
|
|
|
|
gimp_metadata_serialize (GimpMetadata *metadata)
|
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
GString *string;
|
|
|
|
gchar **exif_data = NULL;
|
|
|
|
gchar **iptc_data = NULL;
|
|
|
|
gchar **xmp_data = NULL;
|
|
|
|
gchar *value;
|
|
|
|
gchar *escaped;
|
2022-01-21 03:18:53 +08:00
|
|
|
GError *error = NULL;
|
2017-01-04 02:36:22 +08:00
|
|
|
gboolean base64;
|
|
|
|
gint i;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_val_if_fail (GIMP_IS_METADATA (metadata), NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
string = g_string_new (NULL);
|
|
|
|
|
|
|
|
g_string_append (string, "<?xml version='1.0' encoding='UTF-8'?>\n");
|
|
|
|
g_string_append (string, "<metadata>\n");
|
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
exif_data = gexiv2_metadata_get_exif_tags (GEXIV2_METADATA (metadata));
|
2017-01-04 02:36:22 +08:00
|
|
|
|
|
|
|
if (exif_data)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
for (i = 0; exif_data[i] != NULL; i++)
|
|
|
|
{
|
2022-01-21 03:18:53 +08:00
|
|
|
value = gexiv2_metadata_try_get_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
exif_data[i], &error);
|
|
|
|
if (value)
|
|
|
|
{
|
|
|
|
escaped = gimp_metadata_escape (exif_data[i], value, &base64);
|
|
|
|
g_free (value);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gimp_metadata_append_tag (string, exif_data[i], escaped, base64);
|
|
|
|
}
|
|
|
|
else if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to get Exif metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, exif_data[i], error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_strfreev (exif_data);
|
|
|
|
}
|
2013-11-01 21:15:15 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
xmp_data = gexiv2_metadata_get_xmp_tags (GEXIV2_METADATA (metadata));
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (xmp_data)
|
|
|
|
{
|
2023-05-22 23:54:58 +08:00
|
|
|
GHashTable *namespaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
for (i = 0; xmp_data[i] != NULL; i++)
|
|
|
|
{
|
2023-05-22 23:54:58 +08:00
|
|
|
gimp_metadata_add_xmp_namespaces (namespaces, string, xmp_data[i]);
|
|
|
|
|
2021-09-25 01:42:45 +08:00
|
|
|
/* XmpText is always a single value, but structures like
|
|
|
|
* XmpBag and XmpSeq can have multiple values that need to be
|
|
|
|
* treated separately or else saving will do things wrong. */
|
2022-01-21 03:18:53 +08:00
|
|
|
if (! g_strcmp0 (gexiv2_metadata_try_get_tag_type (xmp_data[i], NULL), "XmpText"))
|
2021-09-25 01:42:45 +08:00
|
|
|
{
|
2022-01-21 03:18:53 +08:00
|
|
|
value = gexiv2_metadata_try_get_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
xmp_data[i], &error);
|
|
|
|
if (value)
|
|
|
|
{
|
|
|
|
escaped = gimp_metadata_escape (xmp_data[i], value, &base64);
|
|
|
|
g_free (value);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gimp_metadata_append_tag (string, xmp_data[i], escaped, base64);
|
|
|
|
}
|
|
|
|
else if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to get XMP metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, xmp_data[i], error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2021-09-25 01:42:45 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gchar **values;
|
2013-11-01 21:15:15 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
values = gexiv2_metadata_try_get_tag_multiple (GEXIV2_METADATA (metadata),
|
|
|
|
xmp_data[i], &error);
|
2021-09-25 01:42:45 +08:00
|
|
|
|
|
|
|
if (values)
|
|
|
|
{
|
|
|
|
gint vi;
|
2021-12-16 00:54:40 +08:00
|
|
|
gint cnt = 0;
|
2021-09-25 01:42:45 +08:00
|
|
|
|
2021-12-16 00:54:40 +08:00
|
|
|
if (! g_strcmp0 (xmp_data[i], "Xmp.photoshop.DocumentAncestors"))
|
|
|
|
{
|
|
|
|
/* Issue #7464 Some images can have huge amounts of this
|
|
|
|
* tag (more than 100000 in certain cases), apparently
|
|
|
|
* due to a bug in PhotoShop. This makes deserializing it
|
|
|
|
* in the way we currently do too slow. Until we can
|
|
|
|
* change this let's remove everything but the first 1000
|
|
|
|
* values when serializing. */
|
|
|
|
cnt = g_strv_length (values);
|
|
|
|
|
|
|
|
if (cnt > 1000)
|
|
|
|
{
|
|
|
|
g_message ("Excessive number of Xmp.photoshop.DocumentAncestors tags found: %d. "
|
|
|
|
"Only keeping the first 1000 values.", cnt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (vi = 0; values[vi] != NULL && (cnt <= 1000 || vi < 1000); vi++)
|
2021-09-25 01:42:45 +08:00
|
|
|
{
|
|
|
|
escaped = gimp_metadata_escape (xmp_data[i], values[vi], &base64);
|
|
|
|
gimp_metadata_append_tag (string, xmp_data[i], escaped, base64);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_strfreev (values);
|
|
|
|
}
|
2022-01-21 03:18:53 +08:00
|
|
|
else if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to get multiple XMP metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, xmp_data[i], error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2021-09-25 01:42:45 +08:00
|
|
|
}
|
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
g_strfreev (xmp_data);
|
2023-05-22 23:54:58 +08:00
|
|
|
g_hash_table_destroy (namespaces);
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
iptc_data = gexiv2_metadata_get_iptc_tags (GEXIV2_METADATA (metadata));
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (iptc_data)
|
2017-01-04 02:19:10 +08:00
|
|
|
{
|
2020-11-17 02:01:55 +08:00
|
|
|
gchar **iptc_tags = iptc_data;
|
|
|
|
gchar *last_tag = NULL;
|
|
|
|
|
|
|
|
while (*iptc_tags)
|
2017-01-04 02:19:10 +08:00
|
|
|
{
|
2020-11-17 02:01:55 +08:00
|
|
|
gchar **values;
|
|
|
|
|
|
|
|
if (last_tag && ! strcmp (*iptc_tags, last_tag))
|
|
|
|
{
|
|
|
|
iptc_tags++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
last_tag = *iptc_tags;
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
values = gexiv2_metadata_try_get_tag_multiple (GEXIV2_METADATA (metadata),
|
|
|
|
*iptc_tags, &error);
|
2020-11-17 02:01:55 +08:00
|
|
|
|
|
|
|
if (values)
|
|
|
|
{
|
|
|
|
for (i = 0; values[i] != NULL; i++)
|
|
|
|
{
|
|
|
|
escaped = gimp_metadata_escape (*iptc_tags, values[i], &base64);
|
|
|
|
gimp_metadata_append_tag (string, *iptc_tags, escaped, base64);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_strfreev (values);
|
|
|
|
}
|
2022-01-21 03:18:53 +08:00
|
|
|
else if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: failed to get multiple IPTC metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, *iptc_tags, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2020-11-17 02:01:55 +08:00
|
|
|
iptc_tags++;
|
2017-01-04 02:19:10 +08:00
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_strfreev (iptc_data);
|
2017-01-04 02:19:10 +08:00
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_string_append (string, "</metadata>\n");
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
return g_string_free (string, FALSE);
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
2013-10-28 04:11:19 +08:00
|
|
|
/**
|
2017-01-04 02:36:22 +08:00
|
|
|
* gimp_metadata_load_from_file:
|
|
|
|
* @file: The #GFile to load the metadata from
|
|
|
|
* @error: Return location for error message
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Loads #GimpMetadata from @file.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: (transfer full): The loaded #GimpMetadata.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Since: 2.10
|
2013-10-20 00:38:01 +08:00
|
|
|
*/
|
2017-01-04 02:36:22 +08:00
|
|
|
GimpMetadata *
|
|
|
|
gimp_metadata_load_from_file (GFile *file,
|
|
|
|
GError **error)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-30 23:42:27 +08:00
|
|
|
GimpMetadata *meta = NULL;
|
|
|
|
gchar *path;
|
|
|
|
gchar *filename;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
|
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
path = g_file_get_path (file);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (! path)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-09-03 21:46:17 +08:00
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 0,
|
2017-01-04 02:36:22 +08:00
|
|
|
_("Can load metadata only from local files"));
|
|
|
|
return NULL;
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
filename = g_strdup (path);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_free (path);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
if (gexiv2_initialize ())
|
|
|
|
{
|
2017-01-30 23:42:27 +08:00
|
|
|
meta = g_object_new (GIMP_TYPE_METADATA, NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (! gexiv2_metadata_open_path (GEXIV2_METADATA (meta), filename, error))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
g_object_unref (meta);
|
|
|
|
g_free (filename);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_free (filename);
|
|
|
|
|
|
|
|
return meta;
|
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2013-10-28 04:11:19 +08:00
|
|
|
/**
|
|
|
|
* gimp_metadata_save_to_file:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
|
|
|
* @file: The file to save the metadata to
|
|
|
|
* @error: Return location for error message
|
|
|
|
*
|
|
|
|
* Saves @metadata to @file.
|
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: %TRUE on success, %FALSE otherwise.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2015-06-01 03:18:09 +08:00
|
|
|
* Since: 2.10
|
2013-10-20 00:38:01 +08:00
|
|
|
*/
|
|
|
|
gboolean
|
|
|
|
gimp_metadata_save_to_file (GimpMetadata *metadata,
|
|
|
|
GFile *file,
|
|
|
|
GError **error)
|
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
gchar *path;
|
|
|
|
gchar *filename;
|
|
|
|
gboolean success;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_val_if_fail (GIMP_IS_METADATA (metadata), FALSE);
|
2013-10-20 00:38:01 +08:00
|
|
|
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)
|
|
|
|
{
|
2017-09-03 21:46:17 +08:00
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 0,
|
2013-10-27 23:32:26 +08:00
|
|
|
_("Can save metadata only to local files"));
|
2013-10-20 00:38:01 +08:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
filename = g_strdup (path);
|
|
|
|
|
|
|
|
g_free (path);
|
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
success = gexiv2_metadata_save_file (GEXIV2_METADATA (metadata),
|
|
|
|
filename, error);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
g_free (filename);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2013-10-28 04:11:19 +08:00
|
|
|
/**
|
|
|
|
* gimp_metadata_set_from_exif:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
2020-05-04 00:11:29 +08:00
|
|
|
* @exif_data: (array length=exif_data_length): The blob of Exif data to set
|
2013-10-28 04:11:19 +08:00
|
|
|
* @exif_data_length: Length of @exif_data, in bytes
|
|
|
|
* @error: Return location for error message
|
|
|
|
*
|
2013-10-30 05:48:46 +08:00
|
|
|
* Sets the tags from a piece of Exif data on @metadata.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: %TRUE on success, %FALSE otherwise.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2015-06-01 03:18:09 +08:00
|
|
|
* Since: 2.10
|
2013-10-28 04:11:19 +08:00
|
|
|
*/
|
2013-10-20 00:38:01 +08:00
|
|
|
gboolean
|
|
|
|
gimp_metadata_set_from_exif (GimpMetadata *metadata,
|
|
|
|
const guchar *exif_data,
|
|
|
|
gint exif_data_length,
|
|
|
|
GError **error)
|
|
|
|
{
|
|
|
|
|
2024-03-08 02:03:12 +08:00
|
|
|
GByteArray *exif_bytes = NULL;
|
2017-01-04 02:36:22 +08:00
|
|
|
GimpMetadata *exif_metadata;
|
2024-03-08 02:03:12 +08:00
|
|
|
const guchar exif_marker[6] = "Exif\0\0";
|
|
|
|
const guchar *data;
|
|
|
|
gint data_length;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
g_return_val_if_fail (GIMP_IS_METADATA (metadata), FALSE);
|
2018-07-06 09:13:18 +08:00
|
|
|
g_return_val_if_fail (exif_data != NULL || exif_data_length == 0, FALSE);
|
2013-10-20 00:38:01 +08:00
|
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
|
2018-07-06 09:13:18 +08:00
|
|
|
if (exif_data_length < 0 || exif_data_length + 2 >= 65536)
|
|
|
|
{
|
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 0,
|
|
|
|
_("Invalid Exif data size."));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2024-03-08 02:03:12 +08:00
|
|
|
/* Old GIMP exif parasite marker "Exif\0\0" needs special handling. */
|
|
|
|
if (exif_data_length >= 6 &&
|
|
|
|
! memcmp (exif_marker, exif_data, sizeof(exif_marker)))
|
|
|
|
{
|
|
|
|
guint8 data_size[2] = { 0, };
|
|
|
|
const guint8 eoi[2] = { 0xff, 0xd9 };
|
|
|
|
|
|
|
|
data_size[0] = ((exif_data_length + 2) & 0xFF00) >> 8;
|
|
|
|
data_size[1] = ((exif_data_length + 2) & 0x00FF);
|
|
|
|
|
|
|
|
exif_bytes = g_byte_array_new ();
|
|
|
|
exif_bytes = g_byte_array_append (exif_bytes,
|
|
|
|
minimal_exif, G_N_ELEMENTS (minimal_exif));
|
|
|
|
exif_bytes = g_byte_array_append (exif_bytes,
|
|
|
|
data_size, 2);
|
|
|
|
exif_bytes = g_byte_array_append (exif_bytes,
|
|
|
|
(guint8 *) exif_data, exif_data_length);
|
|
|
|
exif_bytes = g_byte_array_append (exif_bytes, eoi, 2);
|
|
|
|
data = exif_bytes->data;
|
|
|
|
data_length = exif_bytes->len;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
data = exif_data;
|
|
|
|
data_length = exif_data_length;
|
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
exif_metadata = gimp_metadata_new ();
|
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (! gexiv2_metadata_open_buf (GEXIV2_METADATA (exif_metadata),
|
2024-03-08 02:03:12 +08:00
|
|
|
data, data_length, error))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
|
|
|
g_object_unref (exif_metadata);
|
2024-03-08 02:03:12 +08:00
|
|
|
if (exif_bytes)
|
|
|
|
g_byte_array_free (exif_bytes, TRUE);
|
2013-10-20 00:38:01 +08:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (! gexiv2_metadata_has_exif (GEXIV2_METADATA (exif_metadata)))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-09-03 21:46:17 +08:00
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 0,
|
2013-10-30 05:48:46 +08:00
|
|
|
_("Parsing Exif data failed."));
|
2013-10-20 00:38:01 +08:00
|
|
|
g_object_unref (exif_metadata);
|
2024-03-08 02:03:12 +08:00
|
|
|
if (exif_bytes)
|
|
|
|
g_byte_array_free (exif_bytes, TRUE);
|
2013-10-20 00:38:01 +08:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
gimp_metadata_add (exif_metadata, metadata);
|
2013-10-20 00:38:01 +08:00
|
|
|
g_object_unref (exif_metadata);
|
2024-03-08 02:03:12 +08:00
|
|
|
if (exif_bytes)
|
|
|
|
g_byte_array_free (exif_bytes, TRUE);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2017-07-06 07:24:54 +08:00
|
|
|
/**
|
|
|
|
* gimp_metadata_set_from_iptc:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
2020-05-04 00:11:29 +08:00
|
|
|
* @iptc_data: (array length=iptc_data_length): The blob of Iptc data to set
|
2017-07-06 07:24:54 +08:00
|
|
|
* @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.
|
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: %TRUE on success, %FALSE otherwise.
|
2017-07-06 07:24:54 +08:00
|
|
|
*
|
|
|
|
* Since: 2.10
|
|
|
|
*/
|
|
|
|
gboolean
|
|
|
|
gimp_metadata_set_from_iptc (GimpMetadata *metadata,
|
|
|
|
const guchar *iptc_data,
|
|
|
|
gint iptc_data_length,
|
|
|
|
GError **error)
|
|
|
|
{
|
|
|
|
GimpMetadata *iptc_metadata;
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_val_if_fail (GIMP_IS_METADATA (metadata), FALSE);
|
2018-07-06 09:13:18 +08:00
|
|
|
g_return_val_if_fail (iptc_data != NULL || iptc_data_length == 0, FALSE);
|
2017-07-06 07:24:54 +08:00
|
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
|
|
|
|
iptc_metadata = gimp_metadata_new ();
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
if (! gexiv2_metadata_open_buf (GEXIV2_METADATA (iptc_metadata),
|
2017-07-06 07:24:54 +08:00
|
|
|
iptc_data, iptc_data_length, error))
|
|
|
|
{
|
|
|
|
g_object_unref (iptc_metadata);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
if (! gexiv2_metadata_has_iptc (GEXIV2_METADATA (iptc_metadata)))
|
2017-07-06 07:24:54 +08:00
|
|
|
{
|
2017-09-03 21:46:17 +08:00
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 0,
|
2017-07-06 07:24:54 +08:00
|
|
|
_("Parsing IPTC data failed."));
|
|
|
|
g_object_unref (iptc_metadata);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
gimp_metadata_add (iptc_metadata, metadata);
|
|
|
|
g_object_unref (iptc_metadata);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2013-10-28 04:11:19 +08:00
|
|
|
/**
|
|
|
|
* gimp_metadata_set_from_xmp:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
2020-05-04 00:11:29 +08:00
|
|
|
* @xmp_data: (array length=xmp_data_length): The blob of XMP data to set
|
|
|
|
* @xmp_data_length: Length of @xmp_data, in bytes
|
2013-10-28 04:11:19 +08:00
|
|
|
* @error: Return location for error message
|
|
|
|
*
|
|
|
|
* Sets the tags from a piece of XMP data on @metadata.
|
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: %TRUE on success, %FALSE otherwise.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2015-06-01 03:18:09 +08:00
|
|
|
* Since: 2.10
|
2013-10-28 04:11:19 +08:00
|
|
|
*/
|
2013-10-20 00:38:01 +08:00
|
|
|
gboolean
|
|
|
|
gimp_metadata_set_from_xmp (GimpMetadata *metadata,
|
|
|
|
const guchar *xmp_data,
|
|
|
|
gint xmp_data_length,
|
|
|
|
GError **error)
|
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
GimpMetadata *xmp_metadata;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_val_if_fail (GIMP_IS_METADATA (metadata), FALSE);
|
2018-07-06 09:13:18 +08:00
|
|
|
g_return_val_if_fail (xmp_data != NULL || xmp_data_length == 0, FALSE);
|
2013-10-20 00:38:01 +08:00
|
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
xmp_metadata = gimp_metadata_new ();
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (! gexiv2_metadata_open_buf (GEXIV2_METADATA (xmp_metadata),
|
2013-10-20 00:38:01 +08:00
|
|
|
xmp_data, xmp_data_length, error))
|
|
|
|
{
|
|
|
|
g_object_unref (xmp_metadata);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (! gexiv2_metadata_has_xmp (GEXIV2_METADATA (xmp_metadata)))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-09-03 21:46:17 +08:00
|
|
|
g_set_error (error, GIMP_METADATA_ERROR, 0,
|
2013-10-20 00:38:01 +08:00
|
|
|
_("Parsing XMP data failed."));
|
|
|
|
g_object_unref (xmp_metadata);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
gimp_metadata_add (xmp_metadata, metadata);
|
2013-10-20 00:38:01 +08:00
|
|
|
g_object_unref (xmp_metadata);
|
2017-01-04 02:36:22 +08:00
|
|
|
|
2013-10-20 00:38:01 +08:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2013-10-28 04:11:19 +08:00
|
|
|
/**
|
2017-01-04 02:36:22 +08:00
|
|
|
* gimp_metadata_set_pixel_size:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
|
|
|
* @width: Width in pixels
|
|
|
|
* @height: Height in pixels
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Sets Exif.Image.ImageWidth and Exif.Image.ImageLength on @metadata.
|
2023-05-27 00:40:58 +08:00
|
|
|
* If already present, also sets Exif.Photo.PixelXDimension and
|
|
|
|
* Exif.Photo.PixelYDimension.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Since: 2.10
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
gimp_metadata_set_pixel_size (GimpMetadata *metadata,
|
|
|
|
gint width,
|
|
|
|
gint height)
|
|
|
|
{
|
|
|
|
gchar buffer[32];
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_if_fail (GIMP_IS_METADATA (metadata));
|
2017-01-04 02:36:22 +08:00
|
|
|
|
|
|
|
g_snprintf (buffer, sizeof (buffer), "%d", width);
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.ImageWidth", buffer, NULL);
|
2023-05-27 00:40:58 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Photo.PixelXDimension", NULL))
|
|
|
|
{
|
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Photo.PixelXDimension",
|
|
|
|
buffer, NULL);
|
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
|
|
|
|
g_snprintf (buffer, sizeof (buffer), "%d", height);
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.ImageLength", buffer, NULL);
|
2023-05-27 00:40:58 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Photo.PixelYDimension", NULL))
|
|
|
|
{
|
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Photo.PixelYDimension",
|
|
|
|
buffer, NULL);
|
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_metadata_set_bits_per_sample:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
|
|
|
* @bits_per_sample: Bits per pixel, per component
|
|
|
|
*
|
|
|
|
* Sets Exif.Image.BitsPerSample on @metadata.
|
|
|
|
*
|
|
|
|
* Since: 2.10
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
gimp_metadata_set_bits_per_sample (GimpMetadata *metadata,
|
|
|
|
gint bits_per_sample)
|
|
|
|
{
|
|
|
|
gchar buffer[32];
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_if_fail (GIMP_IS_METADATA (metadata));
|
2017-01-04 02:36:22 +08:00
|
|
|
|
|
|
|
g_snprintf (buffer, sizeof (buffer), "%d %d %d",
|
|
|
|
bits_per_sample, bits_per_sample, bits_per_sample);
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.BitsPerSample", buffer, NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_metadata_get_resolution:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
2020-05-03 23:57:23 +08:00
|
|
|
* @xres: (out) (optional): Return location for the X Resolution, in ppi
|
|
|
|
* @yres: (out) (optional): Return location for the Y Resolution, in ppi
|
|
|
|
* @unit: (out) (optional): Return location for the unit unit
|
2017-01-04 02:36:22 +08:00
|
|
|
*
|
|
|
|
* Returns values based on Exif.Image.XResolution,
|
|
|
|
* Exif.Image.YResolution and Exif.Image.ResolutionUnit of @metadata.
|
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: %TRUE on success, %FALSE otherwise.
|
2013-10-28 04:11:19 +08:00
|
|
|
*
|
2015-06-01 03:18:09 +08:00
|
|
|
* Since: 2.10
|
2013-10-20 00:38:01 +08:00
|
|
|
*/
|
|
|
|
gboolean
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-26 02:55:21 +08:00
|
|
|
gimp_metadata_get_resolution (GimpMetadata *metadata,
|
|
|
|
gdouble *xres,
|
|
|
|
gdouble *yres,
|
|
|
|
GimpUnit **unit)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
gint xnom, xdenom;
|
|
|
|
gint ynom, ydenom;
|
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_val_if_fail (GIMP_IS_METADATA (metadata), FALSE);
|
2017-01-04 02:36:22 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
if (gexiv2_metadata_try_get_exif_tag_rational (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.XResolution",
|
|
|
|
&xnom, &xdenom, NULL) &&
|
|
|
|
gexiv2_metadata_try_get_exif_tag_rational (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.YResolution",
|
|
|
|
&ynom, &ydenom, NULL))
|
2017-01-04 02:36:22 +08:00
|
|
|
{
|
|
|
|
gchar *un;
|
|
|
|
gint exif_unit = 2;
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
un = gexiv2_metadata_try_get_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.ResolutionUnit", NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
if (un)
|
|
|
|
{
|
|
|
|
exif_unit = atoi (un);
|
|
|
|
g_free (un);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xnom != 0 && xdenom != 0 &&
|
|
|
|
ynom != 0 && ydenom != 0)
|
|
|
|
{
|
|
|
|
gdouble xresolution = (gdouble) xnom / (gdouble) xdenom;
|
|
|
|
gdouble yresolution = (gdouble) ynom / (gdouble) ydenom;
|
|
|
|
|
|
|
|
if (exif_unit == 3)
|
|
|
|
{
|
|
|
|
xresolution *= 2.54;
|
|
|
|
yresolution *= 2.54;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xresolution >= GIMP_MIN_RESOLUTION &&
|
|
|
|
xresolution <= GIMP_MAX_RESOLUTION &&
|
|
|
|
yresolution >= GIMP_MIN_RESOLUTION &&
|
|
|
|
yresolution <= GIMP_MAX_RESOLUTION)
|
|
|
|
{
|
|
|
|
if (xres)
|
|
|
|
*xres = xresolution;
|
|
|
|
|
|
|
|
if (yres)
|
|
|
|
*yres = yresolution;
|
|
|
|
|
|
|
|
if (unit)
|
|
|
|
{
|
|
|
|
if (exif_unit == 3)
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-26 02:55:21 +08:00
|
|
|
*unit = gimp_unit_mm ();
|
2017-01-04 02:36:22 +08:00
|
|
|
else
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-26 02:55:21 +08:00
|
|
|
*unit = gimp_unit_inch ();
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_metadata_set_resolution:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
|
|
|
* @xres: The image's X Resolution, in ppi
|
|
|
|
* @yres: The image's Y Resolution, in ppi
|
|
|
|
* @unit: The image's unit
|
|
|
|
*
|
|
|
|
* Sets Exif.Image.XResolution, Exif.Image.YResolution and
|
|
|
|
* Exif.Image.ResolutionUnit of @metadata.
|
|
|
|
*
|
|
|
|
* Since: 2.10
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
gimp_metadata_set_resolution (GimpMetadata *metadata,
|
|
|
|
gdouble xres,
|
|
|
|
gdouble yres,
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-26 02:55:21 +08:00
|
|
|
GimpUnit *unit)
|
2017-01-04 02:36:22 +08:00
|
|
|
{
|
|
|
|
gchar buffer[32];
|
|
|
|
gint exif_unit;
|
|
|
|
gint factor;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-07-08 01:09:59 +08:00
|
|
|
g_return_if_fail (GIMP_IS_METADATA (metadata));
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (gimp_unit_is_metric (unit))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
xres /= 2.54;
|
|
|
|
yres /= 2.54;
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
exif_unit = 3;
|
|
|
|
}
|
|
|
|
else
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
exif_unit = 2;
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
|
|
|
|
for (factor = 1; factor <= 100 /* arbitrary */; factor++)
|
2013-11-02 00:36:26 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
if (fabs (xres * factor - ROUND (xres * factor)) < 0.01 &&
|
|
|
|
fabs (yres * factor - ROUND (yres * factor)) < 0.01)
|
|
|
|
break;
|
2013-11-02 00:36:26 +08:00
|
|
|
}
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_exif_tag_rational (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.XResolution",
|
|
|
|
ROUND (xres * factor), factor, NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_exif_tag_rational (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.YResolution",
|
|
|
|
ROUND (yres * factor), factor, NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
|
|
|
|
g_snprintf (buffer, sizeof (buffer), "%d", exif_unit);
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Image.ResolutionUnit", buffer, NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
2015-10-01 02:47:52 +08:00
|
|
|
/**
|
|
|
|
* gimp_metadata_get_colorspace:
|
2017-01-04 02:36:22 +08:00
|
|
|
* @metadata: A #GimpMetadata instance.
|
2015-10-01 02:47:52 +08:00
|
|
|
*
|
|
|
|
* Returns values based on Exif.Photo.ColorSpace, Xmp.exif.ColorSpace,
|
|
|
|
* Exif.Iop.InteroperabilityIndex, Exif.Nikon3.ColorSpace,
|
|
|
|
* Exif.Canon.ColorSpace of @metadata.
|
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: The colorspace specified by above tags.
|
2015-10-01 02:47:52 +08:00
|
|
|
*
|
|
|
|
* Since: 2.10
|
|
|
|
*/
|
|
|
|
GimpMetadataColorspace
|
|
|
|
gimp_metadata_get_colorspace (GimpMetadata *metadata)
|
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
glong exif_cs = -1;
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
g_return_val_if_fail (GIMP_IS_METADATA (metadata),
|
2015-10-01 02:47:52 +08:00
|
|
|
GIMP_METADATA_COLORSPACE_UNSPECIFIED);
|
|
|
|
|
|
|
|
/* the logic here was mostly taken from darktable and libkexiv2 */
|
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Photo.ColorSpace", NULL))
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
2022-01-21 03:18:53 +08:00
|
|
|
exif_cs = gexiv2_metadata_try_get_tag_long (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Photo.ColorSpace", NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
}
|
2022-03-18 22:44:54 +08:00
|
|
|
else if (gexiv2_metadata_try_has_tag (GEXIV2_METADATA (metadata),
|
|
|
|
"Xmp.exif.ColorSpace", NULL))
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
2022-01-21 03:18:53 +08:00
|
|
|
exif_cs = gexiv2_metadata_try_get_tag_long (GEXIV2_METADATA (metadata),
|
|
|
|
"Xmp.exif.ColorSpace", NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
}
|
|
|
|
|
2015-10-07 03:32:12 +08:00
|
|
|
if (exif_cs == 0x01)
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
|
|
|
return GIMP_METADATA_COLORSPACE_SRGB;
|
|
|
|
}
|
2015-10-07 03:32:12 +08:00
|
|
|
else if (exif_cs == 0x02)
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
|
|
|
return GIMP_METADATA_COLORSPACE_ADOBERGB;
|
|
|
|
}
|
2015-10-07 03:32:12 +08:00
|
|
|
else
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
2015-10-07 03:32:12 +08:00
|
|
|
if (exif_cs == 0xffff)
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
2015-10-07 03:32:12 +08:00
|
|
|
gchar *iop_index;
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
iop_index = gexiv2_metadata_try_get_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Iop.InteroperabilityIndex", NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2015-10-07 03:32:12 +08:00
|
|
|
if (! g_strcmp0 (iop_index, "R03"))
|
|
|
|
{
|
|
|
|
g_free (iop_index);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2015-10-07 03:32:12 +08:00
|
|
|
return GIMP_METADATA_COLORSPACE_ADOBERGB;
|
|
|
|
}
|
|
|
|
else if (! g_strcmp0 (iop_index, "R98"))
|
|
|
|
{
|
|
|
|
g_free (iop_index);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2015-10-07 03:32:12 +08:00
|
|
|
return GIMP_METADATA_COLORSPACE_SRGB;
|
|
|
|
}
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2015-10-07 03:32:12 +08:00
|
|
|
g_free (iop_index);
|
2015-10-01 02:47:52 +08:00
|
|
|
}
|
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Nikon3.ColorSpace", NULL))
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
glong nikon_cs;
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
nikon_cs = gexiv2_metadata_try_get_tag_long (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Nikon3.ColorSpace", NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
|
|
|
|
if (nikon_cs == 0x01)
|
|
|
|
{
|
|
|
|
return GIMP_METADATA_COLORSPACE_SRGB;
|
|
|
|
}
|
|
|
|
else if (nikon_cs == 0x02)
|
2015-10-07 03:32:12 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
return GIMP_METADATA_COLORSPACE_ADOBERGB;
|
2015-10-07 03:32:12 +08:00
|
|
|
}
|
2015-10-01 02:47:52 +08:00
|
|
|
}
|
2015-10-07 03:32:12 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Canon.ColorSpace", NULL))
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
glong canon_cs;
|
|
|
|
|
2022-01-21 03:18:53 +08:00
|
|
|
canon_cs = gexiv2_metadata_try_get_tag_long (GEXIV2_METADATA (metadata),
|
|
|
|
"Exif.Canon.ColorSpace", NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
|
|
|
|
if (canon_cs == 0x01)
|
2015-10-07 03:32:12 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
return GIMP_METADATA_COLORSPACE_SRGB;
|
|
|
|
}
|
|
|
|
else if (canon_cs == 0x02)
|
|
|
|
{
|
|
|
|
return GIMP_METADATA_COLORSPACE_ADOBERGB;
|
2015-10-07 03:32:12 +08:00
|
|
|
}
|
2015-10-01 02:47:52 +08:00
|
|
|
}
|
2015-10-07 03:32:12 +08:00
|
|
|
|
|
|
|
if (exif_cs == 0xffff)
|
|
|
|
return GIMP_METADATA_COLORSPACE_UNCALIBRATED;
|
2015-10-01 02:47:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return GIMP_METADATA_COLORSPACE_UNSPECIFIED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-04 02:36:22 +08:00
|
|
|
* gimp_metadata_set_colorspace:
|
|
|
|
* @metadata: A #GimpMetadata instance.
|
|
|
|
* @colorspace: The color space.
|
2015-10-01 02:47:52 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Sets Exif.Photo.ColorSpace, Xmp.exif.ColorSpace,
|
|
|
|
* Exif.Iop.InteroperabilityIndex, Exif.Nikon3.ColorSpace,
|
|
|
|
* Exif.Canon.ColorSpace of @metadata.
|
2017-01-04 02:19:10 +08:00
|
|
|
*
|
2017-01-04 02:36:22 +08:00
|
|
|
* Since: 2.10
|
2015-10-01 02:47:52 +08:00
|
|
|
*/
|
2017-01-04 02:36:22 +08:00
|
|
|
void
|
|
|
|
gimp_metadata_set_colorspace (GimpMetadata *metadata,
|
|
|
|
GimpMetadataColorspace colorspace)
|
2015-10-01 02:47:52 +08:00
|
|
|
{
|
2017-01-30 23:42:27 +08:00
|
|
|
GExiv2Metadata *g2metadata = GEXIV2_METADATA (metadata);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
switch (colorspace)
|
|
|
|
{
|
|
|
|
case GIMP_METADATA_COLORSPACE_UNSPECIFIED:
|
2022-03-18 22:44:54 +08:00
|
|
|
gexiv2_metadata_try_clear_tag (g2metadata, "Exif.Photo.ColorSpace", NULL);
|
|
|
|
gexiv2_metadata_try_clear_tag (g2metadata, "Xmp.exif.ColorSpace", NULL);
|
|
|
|
gexiv2_metadata_try_clear_tag (g2metadata, "Exif.Iop.InteroperabilityIndex", NULL);
|
|
|
|
gexiv2_metadata_try_clear_tag (g2metadata, "Exif.Nikon3.ColorSpace", NULL);
|
|
|
|
gexiv2_metadata_try_clear_tag (g2metadata, "Exif.Canon.ColorSpace", NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
break;
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
case GIMP_METADATA_COLORSPACE_UNCALIBRATED:
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Exif.Photo.ColorSpace", 0xffff, NULL);
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Xmp.exif.ColorSpace", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Xmp.exif.ColorSpace", 0xffff, NULL);
|
2022-03-18 22:44:54 +08:00
|
|
|
gexiv2_metadata_try_clear_tag (g2metadata, "Exif.Iop.InteroperabilityIndex", NULL);
|
|
|
|
gexiv2_metadata_try_clear_tag (g2metadata, "Exif.Nikon3.ColorSpace", NULL);
|
|
|
|
gexiv2_metadata_try_clear_tag (g2metadata, "Exif.Canon.ColorSpace", NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
break;
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
case GIMP_METADATA_COLORSPACE_SRGB:
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Exif.Photo.ColorSpace", 0x01, NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Xmp.exif.ColorSpace", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Xmp.exif.ColorSpace", 0x01, NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Exif.Iop.InteroperabilityIndex", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (g2metadata,
|
|
|
|
"Exif.Iop.InteroperabilityIndex",
|
|
|
|
"R98", NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Exif.Nikon3.ColorSpace", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Exif.Nikon3.ColorSpace", 0x01, NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Exif.Canon.ColorSpace", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Exif.Canon.ColorSpace", 0x01, NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
break;
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
case GIMP_METADATA_COLORSPACE_ADOBERGB:
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Exif.Photo.ColorSpace", 0x02, NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Xmp.exif.ColorSpace", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Xmp.exif.ColorSpace", 0x02, NULL);
|
2015-10-01 02:47:52 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Exif.Iop.InteroperabilityIndex", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (g2metadata,
|
|
|
|
"Exif.Iop.InteroperabilityIndex",
|
|
|
|
"R03", NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Exif.Nikon3.ColorSpace", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Exif.Nikon3.ColorSpace", 0x02, NULL);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2022-03-18 22:44:54 +08:00
|
|
|
if (gexiv2_metadata_try_has_tag (g2metadata, "Exif.Canon.ColorSpace", NULL))
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_long (g2metadata, "Exif.Canon.ColorSpace", 0x02, NULL);
|
2017-01-04 02:36:22 +08:00
|
|
|
break;
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
/**
|
|
|
|
* gimp_metadata_is_tag_supported:
|
|
|
|
* @tag: A metadata tag name
|
|
|
|
* @mime_type: A mime type
|
|
|
|
*
|
|
|
|
* Returns whether @tag is supported in a file of type @mime_type.
|
|
|
|
*
|
2019-08-03 06:10:14 +08:00
|
|
|
* Returns: %TRUE if the @tag supported with @mime_type, %FALSE otherwise.
|
2017-01-04 02:36:22 +08:00
|
|
|
*
|
|
|
|
* Since: 2.10
|
|
|
|
*/
|
|
|
|
gboolean
|
|
|
|
gimp_metadata_is_tag_supported (const gchar *tag,
|
|
|
|
const gchar *mime_type)
|
|
|
|
{
|
|
|
|
gint j;
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
g_return_val_if_fail (tag != NULL, FALSE);
|
|
|
|
g_return_val_if_fail (mime_type != NULL, FALSE);
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
for (j = 0; j < G_N_ELEMENTS (unsupported_tags); j++)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
if (g_str_has_prefix (tag, unsupported_tags[j]))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
return FALSE;
|
2017-01-04 02:19:10 +08:00
|
|
|
}
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (! strcmp (mime_type, "image/jpeg"))
|
|
|
|
{
|
|
|
|
for (j = 0; j < G_N_ELEMENTS (tiff_tags); j++)
|
2017-01-04 02:19:10 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
if (g_str_has_prefix (tag, tiff_tags[j]))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
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;
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
return TRUE;
|
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
/* private functions */
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static GQuark
|
|
|
|
gimp_metadata_error_quark (void)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-04 02:36:22 +08:00
|
|
|
static GQuark quark = 0;
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
if (G_UNLIKELY (quark == 0))
|
|
|
|
quark = g_quark_from_static_string ("gimp-metadata-error-quark");
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
return quark;
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
|
2019-01-08 04:35:54 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_copy_tag (GExiv2Metadata *src,
|
|
|
|
GExiv2Metadata *dest,
|
|
|
|
const gchar *tag)
|
|
|
|
{
|
2020-11-09 01:54:53 +08:00
|
|
|
gchar **values;
|
|
|
|
GError *error = NULL;
|
2019-01-08 04:35:54 +08:00
|
|
|
|
2020-11-09 01:54:53 +08:00
|
|
|
values = gexiv2_metadata_try_get_tag_multiple (src, tag, &error);
|
|
|
|
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: %s\n", G_STRFUNC, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
g_strfreev (values);
|
|
|
|
}
|
|
|
|
else if (values)
|
2019-01-08 04:35:54 +08:00
|
|
|
{
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_multiple (dest, tag, (const gchar **) values, &error);
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_warning ("%s: failed to set multiple metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, tag, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
|
|
|
|
2019-01-08 04:35:54 +08:00
|
|
|
g_strfreev (values);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-01-21 03:18:53 +08:00
|
|
|
gchar *value = gexiv2_metadata_try_get_tag_string (src, tag, &error);
|
2019-01-08 04:35:54 +08:00
|
|
|
|
|
|
|
if (value)
|
|
|
|
{
|
2022-01-21 03:18:53 +08:00
|
|
|
gexiv2_metadata_try_set_tag_string (dest, tag, value, &error);
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_warning ("%s: failed to set metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, tag, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2019-01-08 04:35:54 +08:00
|
|
|
g_free (value);
|
|
|
|
}
|
2022-01-21 03:18:53 +08:00
|
|
|
else if (error)
|
|
|
|
{
|
|
|
|
g_warning ("%s: failed to get metadata '%s': %s\n",
|
|
|
|
G_STRFUNC, tag, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2019-01-08 04:35:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_metadata_copy_tags (GExiv2Metadata *src,
|
|
|
|
GExiv2Metadata *dest,
|
|
|
|
const gchar **tags)
|
|
|
|
{
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
for (i = 0; tags[i] != NULL; i++)
|
|
|
|
{
|
2019-04-06 22:36:03 +08:00
|
|
|
/* don't copy the same tag multiple times */
|
2019-01-08 04:35:54 +08:00
|
|
|
if (i > 0 && ! strcmp (tags[i], tags[i - 1]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
gimp_metadata_copy_tag (src, dest, tags[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-04 02:36:22 +08:00
|
|
|
static void
|
|
|
|
gimp_metadata_add (GimpMetadata *src,
|
|
|
|
GimpMetadata *dest)
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2017-01-30 23:42:27 +08:00
|
|
|
GExiv2Metadata *g2src = GEXIV2_METADATA (src);
|
|
|
|
GExiv2Metadata *g2dest = GEXIV2_METADATA (dest);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (gexiv2_metadata_get_supports_exif (g2src) &&
|
|
|
|
gexiv2_metadata_get_supports_exif (g2dest))
|
2013-10-20 00:38:01 +08:00
|
|
|
{
|
2019-01-08 04:35:54 +08:00
|
|
|
gchar **exif_tags = gexiv2_metadata_get_exif_tags (g2src);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2019-01-08 04:35:54 +08:00
|
|
|
if (exif_tags)
|
2017-01-04 02:19:10 +08:00
|
|
|
{
|
2019-01-08 04:35:54 +08:00
|
|
|
gimp_metadata_copy_tags (g2src, g2dest,
|
|
|
|
(const gchar **) exif_tags);
|
|
|
|
g_strfreev (exif_tags);
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (gexiv2_metadata_get_supports_xmp (g2src) &&
|
|
|
|
gexiv2_metadata_get_supports_xmp (g2dest))
|
2017-01-04 02:19:10 +08:00
|
|
|
{
|
2019-01-08 04:35:54 +08:00
|
|
|
gchar **xmp_tags = gexiv2_metadata_get_xmp_tags (g2src);
|
2017-01-04 02:19:10 +08:00
|
|
|
|
2019-01-08 04:35:54 +08:00
|
|
|
if (xmp_tags)
|
2017-01-04 02:36:22 +08:00
|
|
|
{
|
2019-01-08 04:35:54 +08:00
|
|
|
gimp_metadata_copy_tags (g2src, g2dest,
|
|
|
|
(const gchar **) xmp_tags);
|
|
|
|
g_strfreev (xmp_tags);
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
2017-01-04 02:19:10 +08:00
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2017-01-30 23:42:27 +08:00
|
|
|
if (gexiv2_metadata_get_supports_iptc (g2src) &&
|
|
|
|
gexiv2_metadata_get_supports_iptc (g2dest))
|
2017-01-04 02:36:22 +08:00
|
|
|
{
|
2019-01-08 04:35:54 +08:00
|
|
|
gchar **iptc_tags = gexiv2_metadata_get_iptc_tags (g2src);
|
2013-10-20 00:38:01 +08:00
|
|
|
|
2019-01-08 04:35:54 +08:00
|
|
|
if (iptc_tags)
|
2017-01-04 02:36:22 +08:00
|
|
|
{
|
2019-01-08 04:35:54 +08:00
|
|
|
gimp_metadata_copy_tags (g2src, g2dest,
|
|
|
|
(const gchar **) iptc_tags);
|
|
|
|
g_strfreev (iptc_tags);
|
2017-01-04 02:36:22 +08:00
|
|
|
}
|
|
|
|
}
|
2013-10-20 00:38:01 +08:00
|
|
|
}
|