Coverage Report

Created: 2026-01-16 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/convert.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
  File:      convert.cpp
4
  Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
5
             Vladimir Nadvornik (vn) <nadvornik@suse.cz>
6
  History:   17-Mar-08, ahu: created basic converter framework
7
             20-May-08, vn:  added actual conversion logic
8
 */
9
// *****************************************************************************
10
// included header files
11
#include "convert.hpp"
12
#include "config.h"
13
#include "error.hpp"
14
#include "exif.hpp"
15
#include "image_int.hpp"
16
#include "iptc.hpp"
17
#include "properties.hpp"
18
#include "tags.hpp"
19
#include "types.hpp"
20
#include "value.hpp"
21
#include "xmp_exiv2.hpp"
22
23
// + standard includes
24
#include <algorithm>
25
#include <functional>
26
27
#ifdef EXV_HAVE_ICONV
28
#ifndef SUPPRESS_WARNINGS
29
#include "futils.hpp"
30
#endif
31
#include <iconv.h>
32
#elif defined _WIN32
33
#include <windows.h>
34
#endif
35
36
// Adobe XMP Toolkit
37
#ifdef EXV_HAVE_XMP_TOOLKIT
38
#define TXMP_STRING_TYPE std::string
39
#ifdef EXV_ADOBE_XMPSDK
40
#include <XMP.hpp>
41
#else
42
#include <XMPSDK.hpp>
43
#endif
44
#include <MD5.h>
45
#endif  // EXV_HAVE_XMP_TOOLKIT
46
47
// *****************************************************************************
48
// local declarations
49
namespace {
50
#ifdef EXV_HAVE_ICONV
51
// Convert string charset with iconv.
52
bool convertStringCharsetIconv(std::string& str, std::string_view from, std::string_view to);
53
#elif defined _WIN32
54
// Convert string charset with Windows functions.
55
bool convertStringCharsetWindows(std::string& str, std::string_view from, std::string_view to);
56
#endif
57
/*!
58
  @brief Get the text value of an XmpDatum \em pos.
59
60
  If \em pos refers to a LangAltValue, \em value is set to the default language
61
  entry without the x-default qualifier. If there is no default but
62
  exactly one entry, \em value is set to this entry, without the qualifier.
63
  The return code indicates if the operation was successful.
64
 */
65
bool getTextValue(std::string& value, Exiv2::XmpData::iterator pos);
66
}  // namespace
67
68
// *****************************************************************************
69
// class member definitions
70
namespace Exiv2 {
71
//! Metadata conversions.
72
class Converter {
73
 public:
74
  /*!
75
    @brief Type for metadata converter functions, taking two key strings,
76
           \em from and \em to.
77
78
    These functions have access to both the source and destination metadata
79
    containers and store the result directly in the destination container.
80
   */
81
  using ConvertFct = void (Converter::*)(const char*, const char*);
82
  //! Structure to define conversions between two keys.
83
  struct Conversion {
84
    MetadataId metadataId_;  //!< Type of metadata for the first key.
85
    const char* key1_;       //!< First metadata key.
86
    const char* key2_;       //!< Second metadata key (always an XMP key for now).
87
    ConvertFct key1ToKey2_;  //!< Conversion from first to second key.
88
    ConvertFct key2ToKey1_;  //!< Conversion from second to first key.
89
  };
90
91
  //! @name Creators
92
  //@{
93
  //! Constructor for Exif tags and XMP properties.
94
  Converter(ExifData& exifData, XmpData& xmpData);
95
  //! Constructor for Iptc tags and XMP properties.
96
  Converter(IptcData& iptcData, XmpData& xmpData, const char* iptcCharset = nullptr);
97
  //@}
98
99
  //! @name Manipulators
100
  //@{
101
  //! Convert Exif tags or IPTC datasets to XMP properties according to the conversion table.
102
  void cnvToXmp();
103
  //! Convert XMP properties to Exif tags or IPTC datasets according to the conversion table.
104
  void cnvFromXmp();
105
  /*!
106
    @brief Set the erase flag.
107
108
    This flag indicates whether successfully converted source records are erased.
109
   */
110
0
  void setErase(bool onoff = true) {
111
0
    erase_ = onoff;
112
0
  }
113
  /*!
114
    @brief Set the overwrite flag.
115
116
    This flag indicates whether existing target records are overwritten.
117
   */
118
0
  void setOverwrite(bool onoff = true) {
119
0
    overwrite_ = onoff;
120
0
  }
121
  //@}
122
123
  //! @name Conversion functions (manipulators)
124
  //@{
125
  /*!
126
    @brief Do nothing conversion function.
127
128
    Use when, for example, a one-way conversion is needed.
129
   */
130
  void cnvNone(const char*, const char*);
131
  /*!
132
    @brief Simple Exif to XMP conversion function.
133
134
    Sets the XMP property to an XmpText value containing the Exif value string.
135
   */
136
  void cnvExifValue(const char* from, const char* to);
137
  /*!
138
    @brief Convert the tag Exif.Photo.UserComment to XMP.
139
140
    Todo: Convert the Exif comment to UTF-8 if necessary.
141
   */
142
  void cnvExifComment(const char* from, const char* to);
143
  /*!
144
    @brief Converts Exif tag with multiple components to XMP array.
145
146
    Converts Exif tag with multiple components to XMP array. This function is
147
    used for ComponentsConfiguration tag.
148
   */
149
  void cnvExifArray(const char* from, const char* to);
150
  /*!
151
    @brief Exif date to XMP conversion function.
152
153
    Sets the XMP property to an XmpText value containing date and time. This function
154
    combines values from multiple Exif tags as described in XMP specification. It
155
    is used for DateTime, DateTimeOriginal, DateTimeDigitized and GPSTimeStamp.
156
   */
157
  void cnvExifDate(const char* from, const char* to);
158
  /*!
159
    @brief Exif version to XMP conversion function.
160
161
    Converts ExifVersion tag to XmpText value.
162
   */
163
  void cnvExifVersion(const char* from, const char* to);
164
  /*!
165
    @brief Exif GPS version to XMP conversion function.
166
167
    Converts GPSVersionID tag to XmpText value.
168
   */
169
  void cnvExifGPSVersion(const char* from, const char* to);
170
  /*!
171
    @brief Exif Flash to XMP conversion function.
172
173
    Converts Flash tag to XMP structure.
174
   */
175
  void cnvExifFlash(const char* from, const char* to);
176
  /*!
177
    @brief Exif GPS coordinate to XMP conversion function.
178
179
    Converts GPS coordinates tag to XmpText value. It combines multiple Exif tags
180
    as described in XMP specification.
181
   */
182
  void cnvExifGPSCoord(const char* from, const char* to);
183
  /*!
184
    @brief Simple XMP to Exif conversion function.
185
186
    Sets the Exif tag according to the XMP property.
187
    For LangAlt values, only the x-default entry is used.
188
189
    Todo: Escape non-ASCII characters in XMP text values
190
   */
191
  void cnvXmpValue(const char* from, const char* to);
192
  /*!
193
    @brief Convert the tag Xmp.exif.UserComment to Exif.
194
   */
195
  void cnvXmpComment(const char* from, const char* to);
196
  /*!
197
    @brief Converts XMP array to Exif tag with multiple components.
198
199
    Converts XMP array to Exif tag with multiple components. This function is
200
    used for ComponentsConfiguration tag.
201
   */
202
  void cnvXmpArray(const char* from, const char* to);
203
  /*!
204
    @brief XMP to Exif date conversion function.
205
206
    Converts the XmpText value to Exif date and time. This function
207
    sets multiple Exif tags as described in XMP specification. It
208
    is used for DateTime, DateTimeOriginal, DateTimeDigitized and GPSTimeStamp.
209
   */
210
  void cnvXmpDate(const char* from, const char* to);
211
  /*!
212
    @brief XMP to Exif version conversion function.
213
214
    Converts XmpText value to ExifVersion tag.
215
   */
216
  void cnvXmpVersion(const char* from, const char* to);
217
  /*!
218
    @brief XMP to Exif GPS version conversion function.
219
220
    Converts XmpText value to GPSVersionID tag.
221
   */
222
  void cnvXmpGPSVersion(const char* from, const char* to);
223
  /*!
224
    @brief XMP to Exif Flash conversion function.
225
226
    Converts XMP structure to Flash tag.
227
   */
228
  void cnvXmpFlash(const char* from, const char* to);
229
  /*!
230
    @brief XMP to Exif GPS coordinate conversion function.
231
232
    Converts XmpText value to GPS coordinates tags. It sets multiple Exif tags
233
    as described in XMP specification.
234
   */
235
  void cnvXmpGPSCoord(const char* from, const char* to);
236
  /*!
237
    @brief IPTC dataset to XMP conversion function.
238
239
    Multiple IPTC datasets with the same key are converted to an XMP array.
240
   */
241
  void cnvIptcValue(const char* from, const char* to);
242
  /*!
243
    @brief XMP to IPTC dataset conversion function.
244
245
    Each array element of an XMP array value is added as one IPTC dataset.
246
   */
247
  void cnvXmpValueToIptc(const char* from, const char* to);
248
  /*!
249
    @brief Write exif:NativeDigest and tiff:NativeDigest properties to XMP.
250
251
    Compute digests from Exif values and write them to  exif:NativeDigest
252
    and tiff:NativeDigest properties. This should be compatible with XMP SDK.
253
   */
254
  void writeExifDigest();
255
  /*!
256
    @brief Copies metadata in appropriate direction.
257
258
    From values of exif:NativeDigest and tiff:NativeDigest detects which of
259
    XMP and Exif was updated more recently and copies metadata in appropriate direction.
260
   */
261
  void syncExifWithXmp();
262
  //@}
263
264
  //! @name Accessors
265
  //@{
266
  //! Get the value of the erase flag, see also setErase(bool on).
267
0
  [[nodiscard]] bool erase() const {
268
0
    return erase_;
269
0
  }
270
  //@}
271
272
 private:
273
  bool prepareExifTarget(const char* to, bool force = false);
274
  bool prepareIptcTarget(const char* to, bool force = false);
275
  bool prepareXmpTarget(const char* to, bool force = false);
276
  std::string computeExifDigest(bool tiff);
277
278
  // DATA
279
  static const Conversion conversion_[];  //!< Conversion rules
280
  bool erase_{false};
281
  bool overwrite_{true};
282
  ExifData* exifData_;
283
  IptcData* iptcData_;
284
  XmpData* xmpData_;
285
  const char* iptcCharset_;
286
287
};  // class Converter
288
289
// Order is important for computing digests
290
const Converter::Conversion Converter::conversion_[] = {
291
    {mdExif, "Exif.Image.ImageWidth", "Xmp.tiff.ImageWidth", &Converter::cnvExifValue, &Converter::cnvXmpValue},
292
    {mdExif, "Exif.Image.ImageLength", "Xmp.tiff.ImageLength", &Converter::cnvExifValue, &Converter::cnvXmpValue},
293
    {mdExif, "Exif.Image.BitsPerSample", "Xmp.tiff.BitsPerSample", &Converter::cnvExifValue, &Converter::cnvXmpValue},
294
    {mdExif, "Exif.Image.Compression", "Xmp.tiff.Compression", &Converter::cnvExifValue, &Converter::cnvXmpValue},
295
    {mdExif, "Exif.Image.PhotometricInterpretation", "Xmp.tiff.PhotometricInterpretation", &Converter::cnvExifValue,
296
     &Converter::cnvXmpValue},
297
    {mdExif, "Exif.Image.Orientation", "Xmp.tiff.Orientation", &Converter::cnvExifValue, &Converter::cnvXmpValue},
298
    {mdExif, "Exif.Image.SamplesPerPixel", "Xmp.tiff.SamplesPerPixel", &Converter::cnvExifValue,
299
     &Converter::cnvXmpValue},
300
    {mdExif, "Exif.Image.PlanarConfiguration", "Xmp.tiff.PlanarConfiguration", &Converter::cnvExifValue,
301
     &Converter::cnvXmpValue},
302
    {mdExif, "Exif.Image.YCbCrSubSampling", "Xmp.tiff.YCbCrSubSampling", &Converter::cnvExifValue,
303
     &Converter::cnvXmpValue},
304
    {mdExif, "Exif.Image.YCbCrPositioning", "Xmp.tiff.YCbCrPositioning", &Converter::cnvExifValue,
305
     &Converter::cnvXmpValue},
306
    {mdExif, "Exif.Image.XResolution", "Xmp.tiff.XResolution", &Converter::cnvExifValue, &Converter::cnvXmpValue},
307
    {mdExif, "Exif.Image.YResolution", "Xmp.tiff.YResolution", &Converter::cnvExifValue, &Converter::cnvXmpValue},
308
    {mdExif, "Exif.Image.ResolutionUnit", "Xmp.tiff.ResolutionUnit", &Converter::cnvExifValue, &Converter::cnvXmpValue},
309
    {mdExif, "Exif.Image.TransferFunction", "Xmp.tiff.TransferFunction", &Converter::cnvExifValue,
310
     &Converter::cnvXmpValue},
311
    {mdExif, "Exif.Image.WhitePoint", "Xmp.tiff.WhitePoint", &Converter::cnvExifValue, &Converter::cnvXmpValue},
312
    {mdExif, "Exif.Image.PrimaryChromaticities", "Xmp.tiff.PrimaryChromaticities", &Converter::cnvExifValue,
313
     &Converter::cnvXmpValue},
314
    {mdExif, "Exif.Image.YCbCrCoefficients", "Xmp.tiff.YCbCrCoefficients", &Converter::cnvExifValue,
315
     &Converter::cnvXmpValue},
316
    {mdExif, "Exif.Image.ReferenceBlackWhite", "Xmp.tiff.ReferenceBlackWhite", &Converter::cnvExifValue,
317
     &Converter::cnvXmpValue},
318
    {mdExif, "Exif.Image.DateTime", "Xmp.xmp.ModifyDate", &Converter::cnvExifDate,
319
     &Converter::cnvXmpDate},  // MWG Guidelines
320
    {mdExif, "Exif.Image.ImageDescription", "Xmp.dc.description", &Converter::cnvExifValue, &Converter::cnvXmpValue},
321
    {mdExif, "Exif.Image.Make", "Xmp.tiff.Make", &Converter::cnvExifValue, &Converter::cnvXmpValue},
322
    {mdExif, "Exif.Image.Model", "Xmp.tiff.Model", &Converter::cnvExifValue, &Converter::cnvXmpValue},
323
    {mdExif, "Exif.Image.Software", "Xmp.tiff.Software", &Converter::cnvExifValue, &Converter::cnvXmpValue},
324
    {mdExif, "Exif.Image.Artist", "Xmp.dc.creator", &Converter::cnvExifValue, &Converter::cnvXmpValue},
325
    {mdExif, "Exif.Image.Rating", "Xmp.xmp.Rating", &Converter::cnvExifValue, &Converter::cnvXmpValue},
326
    {mdExif, "Exif.Image.Copyright", "Xmp.dc.rights", &Converter::cnvExifValue, &Converter::cnvXmpValue},
327
    {mdExif, "Exif.Photo.ExifVersion", "Xmp.exif.ExifVersion", &Converter::cnvExifVersion, &Converter::cnvXmpVersion},
328
    {mdExif, "Exif.Photo.FlashpixVersion", "Xmp.exif.FlashpixVersion", &Converter::cnvExifVersion,
329
     &Converter::cnvXmpVersion},
330
    {mdExif, "Exif.Photo.ColorSpace", "Xmp.exif.ColorSpace", &Converter::cnvExifValue, &Converter::cnvXmpValue},
331
    {mdExif, "Exif.Photo.ComponentsConfiguration", "Xmp.exif.ComponentsConfiguration", &Converter::cnvExifArray,
332
     &Converter::cnvXmpArray},
333
    {mdExif, "Exif.Photo.CompressedBitsPerPixel", "Xmp.exif.CompressedBitsPerPixel", &Converter::cnvExifValue,
334
     &Converter::cnvXmpValue},
335
    {mdExif, "Exif.Photo.PixelXDimension", "Xmp.exif.PixelXDimension", &Converter::cnvExifValue,
336
     &Converter::cnvXmpValue},
337
    {mdExif, "Exif.Photo.PixelYDimension", "Xmp.exif.PixelYDimension", &Converter::cnvExifValue,
338
     &Converter::cnvXmpValue},
339
    {mdExif, "Exif.Photo.UserComment", "Xmp.exif.UserComment", &Converter::cnvExifComment, &Converter::cnvXmpComment},
340
    {mdExif, "Exif.Photo.RelatedSoundFile", "Xmp.exif.RelatedSoundFile", &Converter::cnvExifValue,
341
     &Converter::cnvXmpValue},
342
    {mdExif, "Exif.Photo.DateTimeOriginal", "Xmp.photoshop.DateCreated", &Converter::cnvExifDate,
343
     &Converter::cnvXmpDate},  // MWG Guidelines
344
    {mdExif, "Exif.Photo.DateTimeDigitized", "Xmp.xmp.CreateDate", &Converter::cnvExifDate,
345
     &Converter::cnvXmpDate},  // MWG Guidelines
346
    {mdExif, "Exif.Photo.ExposureTime", "Xmp.exif.ExposureTime", &Converter::cnvExifValue, &Converter::cnvXmpValue},
347
    {mdExif, "Exif.Photo.FNumber", "Xmp.exif.FNumber", &Converter::cnvExifValue, &Converter::cnvXmpValue},
348
    {mdExif, "Exif.Photo.ExposureProgram", "Xmp.exif.ExposureProgram", &Converter::cnvExifValue,
349
     &Converter::cnvXmpValue},
350
    {mdExif, "Exif.Photo.SpectralSensitivity", "Xmp.exif.SpectralSensitivity", &Converter::cnvExifValue,
351
     &Converter::cnvXmpValue},
352
    {mdExif, "Exif.Photo.ISOSpeedRatings", "Xmp.exif.ISOSpeedRatings", &Converter::cnvExifValue,
353
     &Converter::cnvXmpValue},
354
    {mdExif, "Exif.Photo.OECF", "Xmp.exif.OECF", &Converter::cnvExifValue, &Converter::cnvXmpValue},  // FIXME ?
355
    {mdExif, "Exif.Photo.ShutterSpeedValue", "Xmp.exif.ShutterSpeedValue", &Converter::cnvExifValue,
356
     &Converter::cnvXmpValue},
357
    {mdExif, "Exif.Photo.ApertureValue", "Xmp.exif.ApertureValue", &Converter::cnvExifValue, &Converter::cnvXmpValue},
358
    {mdExif, "Exif.Photo.BrightnessValue", "Xmp.exif.BrightnessValue", &Converter::cnvExifValue,
359
     &Converter::cnvXmpValue},
360
    {mdExif, "Exif.Photo.ExposureBiasValue", "Xmp.exif.ExposureBiasValue", &Converter::cnvExifValue,
361
     &Converter::cnvXmpValue},
362
    {mdExif, "Exif.Photo.MaxApertureValue", "Xmp.exif.MaxApertureValue", &Converter::cnvExifValue,
363
     &Converter::cnvXmpValue},
364
    {mdExif, "Exif.Photo.SubjectDistance", "Xmp.exif.SubjectDistance", &Converter::cnvExifValue,
365
     &Converter::cnvXmpValue},
366
    {mdExif, "Exif.Photo.MeteringMode", "Xmp.exif.MeteringMode", &Converter::cnvExifValue, &Converter::cnvXmpValue},
367
    {mdExif, "Exif.Photo.LightSource", "Xmp.exif.LightSource", &Converter::cnvExifValue, &Converter::cnvXmpValue},
368
    {mdExif, "Exif.Photo.Flash", "Xmp.exif.Flash", &Converter::cnvExifFlash, &Converter::cnvXmpFlash},
369
    {mdExif, "Exif.Photo.FocalLength", "Xmp.exif.FocalLength", &Converter::cnvExifValue, &Converter::cnvXmpValue},
370
    {mdExif, "Exif.Photo.SubjectArea", "Xmp.exif.SubjectArea", &Converter::cnvExifValue, &Converter::cnvXmpValue},
371
    {mdExif, "Exif.Photo.FlashEnergy", "Xmp.exif.FlashEnergy", &Converter::cnvExifValue, &Converter::cnvXmpValue},
372
    {mdExif, "Exif.Photo.SpatialFrequencyResponse", "Xmp.exif.SpatialFrequencyResponse", &Converter::cnvExifValue,
373
     &Converter::cnvXmpValue},  // FIXME ?
374
    {mdExif, "Exif.Photo.FocalPlaneXResolution", "Xmp.exif.FocalPlaneXResolution", &Converter::cnvExifValue,
375
     &Converter::cnvXmpValue},
376
    {mdExif, "Exif.Photo.FocalPlaneYResolution", "Xmp.exif.FocalPlaneYResolution", &Converter::cnvExifValue,
377
     &Converter::cnvXmpValue},
378
    {mdExif, "Exif.Photo.FocalPlaneResolutionUnit", "Xmp.exif.FocalPlaneResolutionUnit", &Converter::cnvExifValue,
379
     &Converter::cnvXmpValue},
380
    {mdExif, "Exif.Photo.SubjectLocation", "Xmp.exif.SubjectLocation", &Converter::cnvExifValue,
381
     &Converter::cnvXmpValue},
382
    {mdExif, "Exif.Photo.ExposureIndex", "Xmp.exif.ExposureIndex", &Converter::cnvExifValue, &Converter::cnvXmpValue},
383
    {mdExif, "Exif.Photo.SensingMethod", "Xmp.exif.SensingMethod", &Converter::cnvExifValue, &Converter::cnvXmpValue},
384
    {mdExif, "Exif.Photo.FileSource", "Xmp.exif.FileSource", &Converter::cnvExifValue,
385
     &Converter::cnvXmpValue},  // FIXME ?
386
    {mdExif, "Exif.Photo.SceneType", "Xmp.exif.SceneType", &Converter::cnvExifValue,
387
     &Converter::cnvXmpValue},  // FIXME ?
388
    {mdExif, "Exif.Photo.CFAPattern", "Xmp.exif.CFAPattern", &Converter::cnvExifValue,
389
     &Converter::cnvXmpValue},  // FIXME ?
390
    {mdExif, "Exif.Photo.CustomRendered", "Xmp.exif.CustomRendered", &Converter::cnvExifValue, &Converter::cnvXmpValue},
391
    {mdExif, "Exif.Photo.ExposureMode", "Xmp.exif.ExposureMode", &Converter::cnvExifValue, &Converter::cnvXmpValue},
392
    {mdExif, "Exif.Photo.WhiteBalance", "Xmp.exif.WhiteBalance", &Converter::cnvExifValue, &Converter::cnvXmpValue},
393
    {mdExif, "Exif.Photo.DigitalZoomRatio", "Xmp.exif.DigitalZoomRatio", &Converter::cnvExifValue,
394
     &Converter::cnvXmpValue},
395
    {mdExif, "Exif.Photo.FocalLengthIn35mmFilm", "Xmp.exif.FocalLengthIn35mmFilm", &Converter::cnvExifValue,
396
     &Converter::cnvXmpValue},
397
    {mdExif, "Exif.Photo.SceneCaptureType", "Xmp.exif.SceneCaptureType", &Converter::cnvExifValue,
398
     &Converter::cnvXmpValue},
399
    {mdExif, "Exif.Photo.GainControl", "Xmp.exif.GainControl", &Converter::cnvExifValue, &Converter::cnvXmpValue},
400
    {mdExif, "Exif.Photo.Contrast", "Xmp.exif.Contrast", &Converter::cnvExifValue, &Converter::cnvXmpValue},
401
    {mdExif, "Exif.Photo.Saturation", "Xmp.exif.Saturation", &Converter::cnvExifValue, &Converter::cnvXmpValue},
402
    {mdExif, "Exif.Photo.Sharpness", "Xmp.exif.Sharpness", &Converter::cnvExifValue, &Converter::cnvXmpValue},
403
    {mdExif, "Exif.Photo.DeviceSettingDescription", "Xmp.exif.DeviceSettingDescription", &Converter::cnvExifValue,
404
     &Converter::cnvXmpValue},  // FIXME ?
405
    {mdExif, "Exif.Photo.SubjectDistanceRange", "Xmp.exif.SubjectDistanceRange", &Converter::cnvExifValue,
406
     &Converter::cnvXmpValue},
407
    {mdExif, "Exif.Photo.ImageUniqueID", "Xmp.exif.ImageUniqueID", &Converter::cnvExifValue, &Converter::cnvXmpValue},
408
    {mdExif, "Exif.GPSInfo.GPSVersionID", "Xmp.exif.GPSVersionID", &Converter::cnvExifGPSVersion,
409
     &Converter::cnvXmpGPSVersion},
410
    {mdExif, "Exif.GPSInfo.GPSLatitude", "Xmp.exif.GPSLatitude", &Converter::cnvExifGPSCoord,
411
     &Converter::cnvXmpGPSCoord},
412
    {mdExif, "Exif.GPSInfo.GPSLongitude", "Xmp.exif.GPSLongitude", &Converter::cnvExifGPSCoord,
413
     &Converter::cnvXmpGPSCoord},
414
    {mdExif, "Exif.GPSInfo.GPSAltitudeRef", "Xmp.exif.GPSAltitudeRef", &Converter::cnvExifValue,
415
     &Converter::cnvXmpValue},
416
    {mdExif, "Exif.GPSInfo.GPSAltitude", "Xmp.exif.GPSAltitude", &Converter::cnvExifValue, &Converter::cnvXmpValue},
417
    {mdExif, "Exif.GPSInfo.GPSTimeStamp", "Xmp.exif.GPSTimeStamp", &Converter::cnvExifDate,
418
     &Converter::cnvXmpDate},  // FIXME ?
419
    {mdExif, "Exif.GPSInfo.GPSSatellites", "Xmp.exif.GPSSatellites", &Converter::cnvExifValue, &Converter::cnvXmpValue},
420
    {mdExif, "Exif.GPSInfo.GPSStatus", "Xmp.exif.GPSStatus", &Converter::cnvExifValue, &Converter::cnvXmpValue},
421
    {mdExif, "Exif.GPSInfo.GPSMeasureMode", "Xmp.exif.GPSMeasureMode", &Converter::cnvExifValue,
422
     &Converter::cnvXmpValue},
423
    {mdExif, "Exif.GPSInfo.GPSDOP", "Xmp.exif.GPSDOP", &Converter::cnvExifValue, &Converter::cnvXmpValue},
424
    {mdExif, "Exif.GPSInfo.GPSSpeedRef", "Xmp.exif.GPSSpeedRef", &Converter::cnvExifValue, &Converter::cnvXmpValue},
425
    {mdExif, "Exif.GPSInfo.GPSSpeed", "Xmp.exif.GPSSpeed", &Converter::cnvExifValue, &Converter::cnvXmpValue},
426
    {mdExif, "Exif.GPSInfo.GPSTrackRef", "Xmp.exif.GPSTrackRef", &Converter::cnvExifValue, &Converter::cnvXmpValue},
427
    {mdExif, "Exif.GPSInfo.GPSTrack", "Xmp.exif.GPSTrack", &Converter::cnvExifValue, &Converter::cnvXmpValue},
428
    {mdExif, "Exif.GPSInfo.GPSImgDirectionRef", "Xmp.exif.GPSImgDirectionRef", &Converter::cnvExifValue,
429
     &Converter::cnvXmpValue},
430
    {mdExif, "Exif.GPSInfo.GPSImgDirection", "Xmp.exif.GPSImgDirection", &Converter::cnvExifValue,
431
     &Converter::cnvXmpValue},
432
    {mdExif, "Exif.GPSInfo.GPSMapDatum", "Xmp.exif.GPSMapDatum", &Converter::cnvExifValue, &Converter::cnvXmpValue},
433
    {mdExif, "Exif.GPSInfo.GPSDestLatitude", "Xmp.exif.GPSDestLatitude", &Converter::cnvExifGPSCoord,
434
     &Converter::cnvXmpGPSCoord},
435
    {mdExif, "Exif.GPSInfo.GPSDestLongitude", "Xmp.exif.GPSDestLongitude", &Converter::cnvExifGPSCoord,
436
     &Converter::cnvXmpGPSCoord},
437
    {mdExif, "Exif.GPSInfo.GPSDestBearingRef", "Xmp.exif.GPSDestBearingRef", &Converter::cnvExifValue,
438
     &Converter::cnvXmpValue},
439
    {mdExif, "Exif.GPSInfo.GPSDestBearing", "Xmp.exif.GPSDestBearing", &Converter::cnvExifValue,
440
     &Converter::cnvXmpValue},
441
    {mdExif, "Exif.GPSInfo.GPSDestDistanceRef", "Xmp.exif.GPSDestDistanceRef", &Converter::cnvExifValue,
442
     &Converter::cnvXmpValue},
443
    {mdExif, "Exif.GPSInfo.GPSDestDistance", "Xmp.exif.GPSDestDistance", &Converter::cnvExifValue,
444
     &Converter::cnvXmpValue},
445
    {mdExif, "Exif.GPSInfo.GPSProcessingMethod", "Xmp.exif.GPSProcessingMethod", &Converter::cnvExifValue,
446
     &Converter::cnvXmpValue},  // FIXME ?
447
    {mdExif, "Exif.GPSInfo.GPSAreaInformation", "Xmp.exif.GPSAreaInformation", &Converter::cnvExifValue,
448
     &Converter::cnvXmpValue},  // FIXME ?
449
    {mdExif, "Exif.GPSInfo.GPSDifferential", "Xmp.exif.GPSDifferential", &Converter::cnvExifValue,
450
     &Converter::cnvXmpValue},
451
452
    {mdIptc, "Iptc.Application2.ObjectName", "Xmp.dc.title", &Converter::cnvIptcValue, &Converter::cnvXmpValueToIptc},
453
    {mdIptc, "Iptc.Application2.Urgency", "Xmp.photoshop.Urgency", &Converter::cnvIptcValue,
454
     &Converter::cnvXmpValueToIptc},
455
    {mdIptc, "Iptc.Application2.Category", "Xmp.photoshop.Category", &Converter::cnvIptcValue,
456
     &Converter::cnvXmpValueToIptc},
457
    {mdIptc, "Iptc.Application2.SuppCategory", "Xmp.photoshop.SupplementalCategories", &Converter::cnvIptcValue,
458
     &Converter::cnvXmpValueToIptc},
459
    {mdIptc, "Iptc.Application2.Keywords", "Xmp.dc.subject", &Converter::cnvIptcValue, &Converter::cnvXmpValueToIptc},
460
    {mdIptc, "Iptc.Application2.SubLocation", "Xmp.iptc.Location", &Converter::cnvIptcValue,
461
     &Converter::cnvXmpValueToIptc},
462
    {mdIptc, "Iptc.Application2.SpecialInstructions", "Xmp.photoshop.Instructions", &Converter::cnvIptcValue,
463
     &Converter::cnvXmpValueToIptc},
464
    {mdIptc, "Iptc.Application2.DateCreated", "Xmp.photoshop.DateCreated", &Converter::cnvNone,
465
     &Converter::cnvXmpValueToIptc},  // FIXME to IPTC Date and IPTC Time
466
    {mdIptc, "Iptc.Application2.DigitizationDate", "Xmp.xmp.CreateDate", &Converter::cnvNone,
467
     &Converter::cnvXmpValueToIptc},  // FIXME to IPTC Date and IPTC Time
468
    {mdIptc, "Iptc.Application2.Byline", "Xmp.dc.creator", &Converter::cnvIptcValue, &Converter::cnvXmpValueToIptc},
469
    {mdIptc, "Iptc.Application2.BylineTitle", "Xmp.photoshop.AuthorsPosition", &Converter::cnvIptcValue,
470
     &Converter::cnvXmpValueToIptc},
471
    {mdIptc, "Iptc.Application2.City", "Xmp.photoshop.City", &Converter::cnvIptcValue, &Converter::cnvXmpValueToIptc},
472
    {mdIptc, "Iptc.Application2.ProvinceState", "Xmp.photoshop.State", &Converter::cnvIptcValue,
473
     &Converter::cnvXmpValueToIptc},
474
    {mdIptc, "Iptc.Application2.CountryCode", "Xmp.iptc.CountryCode", &Converter::cnvIptcValue,
475
     &Converter::cnvXmpValueToIptc},
476
    {mdIptc, "Iptc.Application2.CountryName", "Xmp.photoshop.Country", &Converter::cnvIptcValue,
477
     &Converter::cnvXmpValueToIptc},
478
    {mdIptc, "Iptc.Application2.TransmissionReference", "Xmp.photoshop.TransmissionReference", &Converter::cnvIptcValue,
479
     &Converter::cnvXmpValueToIptc},
480
    {mdIptc, "Iptc.Application2.Headline", "Xmp.photoshop.Headline", &Converter::cnvIptcValue,
481
     &Converter::cnvXmpValueToIptc},
482
    {mdIptc, "Iptc.Application2.Credit", "Xmp.photoshop.Credit", &Converter::cnvIptcValue,
483
     &Converter::cnvXmpValueToIptc},
484
    {mdIptc, "Iptc.Application2.Source", "Xmp.photoshop.Source", &Converter::cnvIptcValue,
485
     &Converter::cnvXmpValueToIptc},
486
    {mdIptc, "Iptc.Application2.Copyright", "Xmp.dc.rights", &Converter::cnvIptcValue, &Converter::cnvXmpValueToIptc},
487
    {mdIptc, "Iptc.Application2.Caption", "Xmp.dc.description", &Converter::cnvIptcValue,
488
     &Converter::cnvXmpValueToIptc},
489
    {mdIptc, "Iptc.Application2.Writer", "Xmp.photoshop.CaptionWriter", &Converter::cnvIptcValue,
490
     &Converter::cnvXmpValueToIptc}
491
492
};
493
494
Converter::Converter(ExifData& exifData, XmpData& xmpData) :
495
879
    exifData_(&exifData), iptcData_(nullptr), xmpData_(&xmpData), iptcCharset_(nullptr) {
496
879
}
497
498
Converter::Converter(IptcData& iptcData, XmpData& xmpData, const char* iptcCharset) :
499
879
    exifData_(nullptr), iptcData_(&iptcData), xmpData_(&xmpData), iptcCharset_(iptcCharset) {
500
879
}
501
502
0
void Converter::cnvToXmp() {
503
0
  for (auto&& c : conversion_) {
504
0
    if ((c.metadataId_ == mdExif && exifData_) || (c.metadataId_ == mdIptc && iptcData_)) {
505
0
      std::invoke(c.key1ToKey2_, *this, c.key1_, c.key2_);
506
0
    }
507
0
  }
508
0
}
509
510
1.75k
void Converter::cnvFromXmp() {
511
221k
  for (auto&& c : conversion_) {
512
221k
    if ((c.metadataId_ == mdExif && exifData_) || (c.metadataId_ == mdIptc && iptcData_)) {
513
110k
      std::invoke(c.key2ToKey1_, *this, c.key2_, c.key1_);
514
110k
    }
515
221k
  }
516
1.75k
}
517
518
0
void Converter::cnvNone(const char*, const char*) {
519
0
}
520
521
1.95k
bool Converter::prepareExifTarget(const char* to, bool force) {
522
1.95k
  auto pos = exifData_->findKey(ExifKey(to));
523
1.95k
  if (pos == exifData_->end())
524
1.95k
    return true;
525
0
  if (!overwrite_ && !force)
526
0
    return false;
527
0
  exifData_->erase(pos);
528
0
  return true;
529
0
}
530
531
176
bool Converter::prepareIptcTarget(const char* to, bool force) {
532
176
  auto pos = iptcData_->findKey(IptcKey(to));
533
176
  if (pos == iptcData_->end())
534
176
    return true;
535
0
  if (!overwrite_ && !force)
536
0
    return false;
537
0
  while ((pos = iptcData_->findKey(IptcKey(to))) != iptcData_->end()) {
538
0
    iptcData_->erase(pos);
539
0
  }
540
0
  return true;
541
0
}
542
543
0
bool Converter::prepareXmpTarget(const char* to, bool force) {
544
0
  auto pos = xmpData_->findKey(XmpKey(to));
545
0
  if (pos == xmpData_->end())
546
0
    return true;
547
0
  if (!overwrite_ && !force)
548
0
    return false;
549
0
  xmpData_->erase(pos);
550
0
  return true;
551
0
}
552
553
0
void Converter::cnvExifValue(const char* from, const char* to) {
554
0
  auto pos = exifData_->findKey(ExifKey(from));
555
0
  if (pos == exifData_->end())
556
0
    return;
557
0
  std::string value = pos->toString();
558
0
  if (!pos->value().ok()) {
559
0
#ifndef SUPPRESS_WARNINGS
560
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
561
0
#endif
562
0
    return;
563
0
  }
564
0
  if (!prepareXmpTarget(to))
565
0
    return;
566
0
  (*xmpData_)[to] = value;
567
0
  if (erase_)
568
0
    exifData_->erase(pos);
569
0
}
570
571
0
void Converter::cnvExifComment(const char* from, const char* to) {
572
0
  auto pos = exifData_->findKey(ExifKey(from));
573
0
  if (pos == exifData_->end())
574
0
    return;
575
0
  if (!prepareXmpTarget(to))
576
0
    return;
577
0
  const auto cv = dynamic_cast<const CommentValue*>(&pos->value());
578
0
  if (!cv) {
579
0
#ifndef SUPPRESS_WARNINGS
580
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
581
0
#endif
582
0
    return;
583
0
  }
584
  // Todo: Convert to UTF-8 if necessary
585
0
  try {
586
0
    (*xmpData_)[to] = cv->comment();
587
0
  } catch (const Error&) {
588
0
#ifndef SUPPRESS_WARNINGS
589
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
590
0
#endif
591
0
  }
592
0
  if (erase_)
593
0
    exifData_->erase(pos);
594
0
}
595
596
0
void Converter::cnvExifArray(const char* from, const char* to) {
597
0
  auto pos = exifData_->findKey(ExifKey(from));
598
0
  if (pos == exifData_->end())
599
0
    return;
600
0
  if (!prepareXmpTarget(to))
601
0
    return;
602
0
  for (size_t i = 0; i < pos->count(); ++i) {
603
0
    std::string value = pos->toString(i);
604
0
    if (!pos->value().ok()) {
605
0
#ifndef SUPPRESS_WARNINGS
606
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
607
0
#endif
608
0
      return;
609
0
    }
610
0
    (*xmpData_)[to] = value;
611
0
  }
612
0
  if (erase_)
613
0
    exifData_->erase(pos);
614
0
}
615
616
0
void Converter::cnvExifDate(const char* from, const char* to) {
617
0
  auto pos = exifData_->findKey(ExifKey(from));
618
0
  if (pos == exifData_->end())
619
0
    return;
620
0
  if (!prepareXmpTarget(to))
621
0
    return;
622
0
  int year = 0;
623
0
  int month = 0;
624
0
  int day = 0;
625
0
  int hour = 0;
626
0
  int min = 0;
627
0
  int sec = 0;
628
0
  std::string subsec;
629
630
0
  if (std::string(from) != "Exif.GPSInfo.GPSTimeStamp") {
631
0
    std::string value = pos->toString();
632
0
    if (!pos->value().ok()) {
633
0
#ifndef SUPPRESS_WARNINGS
634
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
635
0
#endif
636
0
      return;
637
0
    }
638
0
    if (sscanf(value.c_str(), "%d:%d:%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec) != 6) {
639
0
#ifndef SUPPRESS_WARNINGS
640
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << ", unable to parse '" << value << "'\n";
641
0
#endif
642
0
      return;
643
0
    }
644
0
  } else {  // "Exif.GPSInfo.GPSTimeStamp"
645
0
    bool ok = true;
646
0
    if (pos->count() != 3)
647
0
      ok = false;
648
0
    if (ok) {
649
0
      for (int i = 0; i < 3; ++i) {
650
0
        if (pos->toRational(i).second == 0) {
651
0
          ok = false;
652
0
          break;
653
0
        }
654
0
      }
655
0
    }
656
0
    if (!ok) {
657
0
#ifndef SUPPRESS_WARNINGS
658
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
659
0
#endif
660
0
      return;
661
0
    }
662
663
0
    double dhour = pos->toFloat(0);
664
0
    double dmin = pos->toFloat(1);
665
    // Hack: Need Value::toDouble
666
0
    auto [r, s] = pos->toRational(2);
667
0
    double dsec = static_cast<double>(r) / s;
668
669
0
    if (!pos->value().ok()) {
670
0
#ifndef SUPPRESS_WARNINGS
671
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
672
0
#endif
673
0
      return;
674
0
    }
675
676
0
    dsec += dhour * 3600.0;
677
0
    dsec += dmin * 60.0;
678
679
0
    hour = static_cast<int>(dsec / 3600.0);
680
0
    dsec -= hour * 3600;
681
0
    min = static_cast<int>(dsec / 60.0);
682
0
    dsec -= min * 60;
683
0
    sec = static_cast<int>(dsec);
684
0
    dsec -= sec;
685
686
0
    subsec = stringFormat(".{:09.0f}", dsec * 1'000'000'000);
687
688
0
    auto datePos = exifData_->findKey(ExifKey("Exif.GPSInfo.GPSDateStamp"));
689
0
    if (datePos == exifData_->end()) {
690
0
      datePos = exifData_->findKey(ExifKey("Exif.Photo.DateTimeOriginal"));
691
0
    }
692
0
    if (datePos == exifData_->end()) {
693
0
      datePos = exifData_->findKey(ExifKey("Exif.Photo.DateTimeDigitized"));
694
0
    }
695
0
    if (datePos == exifData_->end()) {
696
0
#ifndef SUPPRESS_WARNINGS
697
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
698
0
#endif
699
0
      return;
700
0
    }
701
0
    std::string value = datePos->toString();
702
0
    if (sscanf(value.c_str(), "%d:%d:%d", &year, &month, &day) != 3) {
703
0
#ifndef SUPPRESS_WARNINGS
704
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << ", unable to parse '" << value << "'\n";
705
0
#endif
706
0
      return;
707
0
    }
708
0
  }
709
710
0
  const char* subsecTag = nullptr;
711
0
  if (std::string(from) == "Exif.Image.DateTime") {
712
0
    subsecTag = "Exif.Photo.SubSecTime";
713
0
  } else if (std::string(from) == "Exif.Photo.DateTimeOriginal") {
714
0
    subsecTag = "Exif.Photo.SubSecTimeOriginal";
715
0
  } else if (std::string(from) == "Exif.Photo.DateTimeDigitized") {
716
0
    subsecTag = "Exif.Photo.SubSecTimeDigitized";
717
0
  }
718
719
0
  if (subsecTag) {
720
0
    auto subsec_pos = exifData_->findKey(ExifKey(subsecTag));
721
0
    if (subsec_pos != exifData_->end()) {
722
0
      if (subsec_pos->typeId() == asciiString) {
723
0
        std::string ss = subsec_pos->toString();
724
0
        if (!ss.empty()) {
725
0
          bool ok = false;
726
0
          stringTo<long>(ss, ok);
727
0
          if (ok)
728
0
            subsec = std::string(".") + ss;
729
0
        }
730
0
      }
731
0
      if (erase_)
732
0
        exifData_->erase(subsec_pos);
733
0
    }
734
0
  }
735
736
0
  if (subsec.size() > 10)
737
0
    subsec.resize(10);
738
739
0
  (*xmpData_)[to] = stringFormat("{:4}-{:02}-{:02}T{:02}:{:02}:{:02}{}", year, month, day, hour, min, sec, subsec);
740
0
  if (erase_)
741
0
    exifData_->erase(pos);
742
0
}
743
744
0
void Converter::cnvExifVersion(const char* from, const char* to) {
745
0
  auto pos = exifData_->findKey(ExifKey(from));
746
0
  if (pos == exifData_->end())
747
0
    return;
748
0
  if (!prepareXmpTarget(to))
749
0
    return;
750
0
  auto count = pos->count();
751
0
  std::string value;
752
0
  value.reserve(count);
753
0
  for (size_t i = 0; i < count; ++i) {
754
0
    value.push_back(pos->toInt64(i));
755
0
  }
756
0
  (*xmpData_)[to] = value;
757
0
  if (erase_)
758
0
    exifData_->erase(pos);
759
0
}
760
761
0
void Converter::cnvExifGPSVersion(const char* from, const char* to) {
762
0
  auto pos = exifData_->findKey(ExifKey(from));
763
0
  if (pos == exifData_->end())
764
0
    return;
765
0
  if (!prepareXmpTarget(to))
766
0
    return;
767
0
  std::string value;
768
0
  for (size_t i = 0; i < pos->count(); ++i) {
769
0
    if (i > 0)
770
0
      value += '.';
771
0
    value += std::to_string(pos->toInt64(i));
772
0
  }
773
0
  (*xmpData_)[to] = value;
774
0
  if (erase_)
775
0
    exifData_->erase(pos);
776
0
}
777
778
0
void Converter::cnvExifFlash(const char* from, const char* to) {
779
0
  auto pos = exifData_->findKey(ExifKey(from));
780
0
  if (pos == exifData_->end() || pos->count() == 0)
781
0
    return;
782
0
  if (!prepareXmpTarget(to))
783
0
    return;
784
0
  auto value = pos->toUint32();
785
0
  if (!pos->value().ok()) {
786
0
#ifndef SUPPRESS_WARNINGS
787
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
788
0
#endif
789
0
    return;
790
0
  }
791
792
0
  (*xmpData_)["Xmp.exif.Flash/exif:Fired"] = static_cast<bool>(value & 1);
793
0
  (*xmpData_)["Xmp.exif.Flash/exif:Return"] = (value >> 1) & 3;
794
0
  (*xmpData_)["Xmp.exif.Flash/exif:Mode"] = (value >> 3) & 3;
795
0
  (*xmpData_)["Xmp.exif.Flash/exif:Function"] = static_cast<bool>((value >> 5) & 1);
796
0
  (*xmpData_)["Xmp.exif.Flash/exif:RedEyeMode"] = static_cast<bool>((value >> 6) & 1);
797
798
0
  if (erase_)
799
0
    exifData_->erase(pos);
800
0
}
801
802
0
void Converter::cnvExifGPSCoord(const char* from, const char* to) {
803
0
  auto pos = exifData_->findKey(ExifKey(from));
804
0
  if (pos == exifData_->end())
805
0
    return;
806
0
  if (!prepareXmpTarget(to))
807
0
    return;
808
0
  if (pos->count() != 3) {
809
0
#ifndef SUPPRESS_WARNINGS
810
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
811
0
#endif
812
0
    return;
813
0
  }
814
0
  auto refPos = exifData_->findKey(ExifKey(std::string(from) + "Ref"));
815
0
  if (refPos == exifData_->end()) {
816
0
#ifndef SUPPRESS_WARNINGS
817
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
818
0
#endif
819
0
    return;
820
0
  }
821
0
  double deg[3];
822
0
  for (int i = 0; i < 3; ++i) {
823
0
    const auto [z, d] = pos->toRational(i);
824
0
    if (d == 0) {
825
0
#ifndef SUPPRESS_WARNINGS
826
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
827
0
#endif
828
0
      return;
829
0
    }
830
    // Hack: Need Value::toDouble
831
0
    deg[i] = static_cast<double>(z) / d;
832
0
  }
833
0
  double min = (deg[0] * 60.0) + deg[1] + (deg[2] / 60.0);
834
0
  auto ideg = static_cast<int>(min / 60.0);
835
0
  min -= ideg * 60;
836
0
  (*xmpData_)[to] = stringFormat("{},{:.7f}{}", ideg, min, refPos->toString().front());
837
838
0
  if (erase_)
839
0
    exifData_->erase(pos);
840
0
  if (erase_)
841
0
    exifData_->erase(refPos);
842
0
}
843
844
79.1k
void Converter::cnvXmpValue(const char* from, const char* to) {
845
79.1k
  auto pos = xmpData_->findKey(XmpKey(from));
846
79.1k
  if (pos == xmpData_->end())
847
79.0k
    return;
848
13
  if (!prepareExifTarget(to))
849
0
    return;
850
13
  std::string value;
851
13
  if (!getTextValue(value, pos)) {
852
0
#ifndef SUPPRESS_WARNINGS
853
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
854
0
#endif
855
0
    return;
856
0
  }
857
  // Todo: Escape non-ASCII characters in XMP text values
858
13
  ExifKey key(to);
859
13
  if (auto ed = Exifdatum(key); ed.setValue(value) == 0) {
860
1
    exifData_->add(ed);
861
1
  }
862
13
  if (erase_)
863
0
    xmpData_->erase(pos);
864
13
}
865
866
879
void Converter::cnvXmpComment(const char* from, const char* to) {
867
879
  if (!prepareExifTarget(to))
868
0
    return;
869
879
  auto pos = xmpData_->findKey(XmpKey(from));
870
879
  if (pos == xmpData_->end())
871
879
    return;
872
0
  std::string value;
873
0
  if (!getTextValue(value, pos)) {
874
0
#ifndef SUPPRESS_WARNINGS
875
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
876
0
#endif
877
0
    return;
878
0
  }
879
  // Assumes the XMP value is encoded in UTF-8, as it should be
880
0
  (*exifData_)[to] = "charset=Unicode " + value;
881
0
  if (erase_)
882
0
    xmpData_->erase(pos);
883
0
}
884
885
879
void Converter::cnvXmpArray(const char* from, const char* to) {
886
879
  if (!prepareExifTarget(to))
887
0
    return;
888
879
  auto pos = xmpData_->findKey(XmpKey(from));
889
879
  if (pos == xmpData_->end())
890
879
    return;
891
0
  std::string array;
892
0
  for (size_t i = 0; i < pos->count(); ++i) {
893
0
    std::string value = pos->toString(i);
894
0
    if (!pos->value().ok()) {
895
0
#ifndef SUPPRESS_WARNINGS
896
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
897
0
#endif
898
0
      return;
899
0
    }
900
0
    array += value;
901
0
    if (i != pos->count() - 1)
902
0
      array += " ";
903
0
  }
904
0
  (*exifData_)[to] = array;
905
0
  if (erase_)
906
0
    xmpData_->erase(pos);
907
0
}
908
909
3.51k
void Converter::cnvXmpDate(const char* from, const char* to) {
910
3.51k
  auto pos = xmpData_->findKey(XmpKey(from));
911
3.51k
  if (pos == xmpData_->end())
912
3.34k
    return;
913
176
  if (!prepareExifTarget(to))
914
0
    return;
915
176
#ifdef EXV_HAVE_XMP_TOOLKIT
916
176
  std::string value = pos->toString();
917
176
  if (!pos->value().ok()) {
918
0
#ifndef SUPPRESS_WARNINGS
919
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
920
0
#endif
921
0
    return;
922
0
  }
923
176
  XMP_DateTime datetime;
924
176
  try {
925
176
    SXMPUtils::ConvertToDate(value, &datetime);
926
176
    if (std::string(to) != "Exif.GPSInfo.GPSTimeStamp") {
927
61
      SXMPUtils::ConvertToLocalTime(&datetime);
928
929
61
      (*exifData_)[to] = stringFormat("{:4}:{:02}:{:02} {:02}:{:02}:{:02}", datetime.year, datetime.month, datetime.day,
930
61
                                      datetime.hour, datetime.minute, datetime.second);
931
932
61
      if (datetime.nanoSecond) {
933
5
        const char* subsecTag = nullptr;
934
5
        if (std::string(to) == "Exif.Image.DateTime") {
935
0
          subsecTag = "Exif.Photo.SubSecTime";
936
5
        } else if (std::string(to) == "Exif.Photo.DateTimeOriginal") {
937
0
          subsecTag = "Exif.Photo.SubSecTimeOriginal";
938
5
        } else if (std::string(to) == "Exif.Photo.DateTimeDigitized") {
939
5
          subsecTag = "Exif.Photo.SubSecTimeDigitized";
940
5
        }
941
5
        if (subsecTag) {
942
5
          prepareExifTarget(subsecTag, true);
943
5
          (*exifData_)[subsecTag] = std::to_string(datetime.nanoSecond);
944
5
        }
945
5
      }
946
115
    } else {  // "Exif.GPSInfo.GPSTimeStamp"
947
      // Ignore the time zone, assuming the time is in UTC as it should be
948
949
115
      URational rhour(datetime.hour, 1);
950
115
      URational rmin(datetime.minute, 1);
951
115
      URational rsec(datetime.second, 1);
952
115
      if (datetime.nanoSecond != 0) {
953
0
        if (datetime.second != 0) {
954
          // Add the seconds to rmin so that the ns fit into rsec
955
0
          rmin.second = 60;
956
0
          rmin.first *= 60;
957
0
          rmin.first += datetime.second;
958
0
        }
959
0
        rsec.second = 1000000000;
960
0
        rsec.first = datetime.nanoSecond;
961
0
      }
962
963
115
      std::ostringstream array;
964
115
      array << rhour << " " << rmin << " " << rsec;
965
115
      (*exifData_)[to] = array.str();
966
967
115
      prepareExifTarget("Exif.GPSInfo.GPSDateStamp", true);
968
115
      (*exifData_)["Exif.GPSInfo.GPSDateStamp"] =
969
115
          stringFormat("{:4}:{:02}:{:02}", datetime.year, datetime.month, datetime.day);
970
115
    }
971
176
  }
972
176
#ifndef SUPPRESS_WARNINGS
973
176
  catch (const XMP_Error& e) {
974
115
    EXV_WARNING << "Failed to convert " << from << " to " << to << " (" << e.GetErrMsg() << ")\n";
975
115
    return;
976
115
  }
977
#else
978
  catch (const XMP_Error&) {
979
    return;
980
  }
981
#endif  // SUPPRESS_WARNINGS
982
983
61
  if (erase_)
984
0
    xmpData_->erase(pos);
985
#else
986
#ifndef SUPPRESS_WARNINGS
987
  EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
988
#endif
989
#endif  // !EXV_HAVE_XMP_TOOLKIT
990
61
}
991
992
1.75k
void Converter::cnvXmpVersion(const char* from, const char* to) {
993
1.75k
  auto pos = xmpData_->findKey(XmpKey(from));
994
1.75k
  if (pos == xmpData_->end())
995
1.75k
    return;
996
0
  if (!prepareExifTarget(to))
997
0
    return;
998
0
  std::string value = pos->toString();
999
0
  if (!pos->value().ok() || value.length() < 4) {
1000
0
#ifndef SUPPRESS_WARNINGS
1001
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
1002
0
#endif
1003
0
    return;
1004
0
  }
1005
1006
0
  (*exifData_)[to] = stringFormat("{} {} {} {}", static_cast<int>(value[0]), static_cast<int>(value[1]),
1007
0
                                  static_cast<int>(value[2]), static_cast<int>(value[3]));
1008
0
  if (erase_)
1009
0
    xmpData_->erase(pos);
1010
0
}
1011
1012
879
void Converter::cnvXmpGPSVersion(const char* from, const char* to) {
1013
879
  auto pos = xmpData_->findKey(XmpKey(from));
1014
879
  if (pos == xmpData_->end())
1015
879
    return;
1016
0
  if (!prepareExifTarget(to))
1017
0
    return;
1018
0
  std::string value = pos->toString();
1019
0
  if (!pos->value().ok()) {
1020
0
#ifndef SUPPRESS_WARNINGS
1021
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
1022
0
#endif
1023
0
    return;
1024
0
  }
1025
1026
0
  std::replace(value.begin(), value.end(), '.', ' ');
1027
0
  (*exifData_)[to] = value;
1028
0
  if (erase_)
1029
0
    xmpData_->erase(pos);
1030
0
}
1031
1032
879
void Converter::cnvXmpFlash(const char* from, const char* to) {
1033
879
  auto pos = xmpData_->findKey(XmpKey(std::string(from) + "/exif:Fired"));
1034
879
  if (pos == xmpData_->end())
1035
879
    return;
1036
0
  if (!prepareExifTarget(to))
1037
0
    return;
1038
0
  unsigned short value = 0;
1039
1040
0
  if (pos != xmpData_->end() && pos->count() > 0) {
1041
0
    auto fired = pos->toUint32();
1042
0
    if (pos->value().ok())
1043
0
      value |= fired & 1;
1044
0
#ifndef SUPPRESS_WARNINGS
1045
0
    else
1046
0
      EXV_WARNING << "Failed to convert " << std::string(from) << "/exif:Fired"
1047
0
                  << " to " << to << "\n";
1048
0
#endif
1049
0
  }
1050
0
  pos = xmpData_->findKey(XmpKey(std::string(from) + "/exif:Return"));
1051
0
  if (pos != xmpData_->end() && pos->count() > 0) {
1052
0
    auto ret = pos->toUint32();
1053
0
    if (pos->value().ok())
1054
0
      value |= (ret & 3) << 1;
1055
0
#ifndef SUPPRESS_WARNINGS
1056
0
    else
1057
0
      EXV_WARNING << "Failed to convert " << std::string(from) << "/exif:Return"
1058
0
                  << " to " << to << "\n";
1059
0
#endif
1060
0
  }
1061
0
  pos = xmpData_->findKey(XmpKey(std::string(from) + "/exif:Mode"));
1062
0
  if (pos != xmpData_->end() && pos->count() > 0) {
1063
0
    auto mode = pos->toUint32();
1064
0
    if (pos->value().ok())
1065
0
      value |= (mode & 3) << 3;
1066
0
#ifndef SUPPRESS_WARNINGS
1067
0
    else
1068
0
      EXV_WARNING << "Failed to convert " << std::string(from) << "/exif:Mode"
1069
0
                  << " to " << to << "\n";
1070
0
#endif
1071
0
  }
1072
0
  pos = xmpData_->findKey(XmpKey(std::string(from) + "/exif:Function"));
1073
0
  if (pos != xmpData_->end() && pos->count() > 0) {
1074
0
    auto function = pos->toUint32();
1075
0
    if (pos->value().ok())
1076
0
      value |= (function & 1) << 5;
1077
0
#ifndef SUPPRESS_WARNINGS
1078
0
    else
1079
0
      EXV_WARNING << "Failed to convert " << std::string(from) << "/exif:Function"
1080
0
                  << " to " << to << "\n";
1081
0
#endif
1082
0
  }
1083
0
  pos = xmpData_->findKey(XmpKey(std::string(from) + "/exif:RedEyeMode"));
1084
0
  if (pos != xmpData_->end()) {
1085
0
    if (pos->count() > 0) {
1086
0
      auto red = pos->toUint32();
1087
0
      if (pos->value().ok())
1088
0
        value |= (red & 1) << 6;
1089
0
#ifndef SUPPRESS_WARNINGS
1090
0
      else
1091
0
        EXV_WARNING << "Failed to convert " << std::string(from) << "/exif:RedEyeMode"
1092
0
                    << " to " << to << "\n";
1093
0
#endif
1094
0
    }
1095
0
    if (erase_)
1096
0
      xmpData_->erase(pos);
1097
0
  }
1098
1099
0
  (*exifData_)[to] = value;
1100
0
}
1101
1102
3.51k
void Converter::cnvXmpGPSCoord(const char* from, const char* to) {
1103
3.51k
  auto pos = xmpData_->findKey(XmpKey(from));
1104
3.51k
  if (pos == xmpData_->end())
1105
3.51k
    return;
1106
0
  if (!prepareExifTarget(to))
1107
0
    return;
1108
0
  std::string value = pos->toString();
1109
0
  if (!pos->value().ok()) {
1110
0
#ifndef SUPPRESS_WARNINGS
1111
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
1112
0
#endif
1113
0
    return;
1114
0
  }
1115
0
  if (value.empty()) {
1116
0
#ifndef SUPPRESS_WARNINGS
1117
0
    EXV_WARNING << from << " is empty\n";
1118
0
#endif
1119
0
    return;
1120
0
  }
1121
1122
0
  double deg = 0.0;
1123
0
  double min = 0.0;
1124
0
  double sec = 0.0;
1125
0
  char ref = value.back();
1126
0
  char sep1 = '\0';
1127
0
  char sep2 = '\0';
1128
1129
0
  value.pop_back();
1130
1131
0
  std::istringstream in(value);
1132
1133
0
  in >> deg >> sep1 >> min >> sep2;
1134
1135
0
  if (sep2 == ',') {
1136
0
    in >> sec;
1137
0
  } else {
1138
0
    sec = (min - static_cast<int>(min)) * 60.0;
1139
0
    min = static_cast<double>(static_cast<int>(min));
1140
0
  }
1141
1142
0
  if (in.bad() || (ref != 'N' && ref != 'S' && ref != 'E' && ref != 'W') || sep1 != ',' || !in.eof()) {
1143
0
#ifndef SUPPRESS_WARNINGS
1144
0
    EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
1145
0
#endif
1146
0
    return;
1147
0
  }
1148
1149
0
  Rational rdeg = floatToRationalCast(static_cast<float>(deg));
1150
0
  Rational rmin = floatToRationalCast(static_cast<float>(min));
1151
0
  Rational rsec = floatToRationalCast(static_cast<float>(sec));
1152
1153
0
  std::ostringstream array;
1154
0
  array << rdeg << " " << rmin << " " << rsec;
1155
0
  (*exifData_)[to] = array.str();
1156
1157
0
  prepareExifTarget((std::string(to) + "Ref").c_str(), true);
1158
0
  char ref_str[2] = {ref, 0};
1159
0
  (*exifData_)[std::string(to) + "Ref"] = ref_str;
1160
1161
0
  if (erase_)
1162
0
    xmpData_->erase(pos);
1163
0
}
1164
1165
0
void Converter::cnvIptcValue(const char* from, const char* to) {
1166
0
  auto pos = iptcData_->findKey(IptcKey(from));
1167
0
  if (pos == iptcData_->end())
1168
0
    return;
1169
0
  if (!prepareXmpTarget(to))
1170
0
    return;
1171
0
  while (pos != iptcData_->end()) {
1172
0
    if (pos->key() == from) {
1173
0
      std::string value = pos->toString();
1174
0
      if (!pos->value().ok()) {
1175
0
#ifndef SUPPRESS_WARNINGS
1176
0
        EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
1177
0
#endif
1178
0
        ++pos;
1179
0
        continue;
1180
0
      }
1181
0
      if (iptcCharset_)
1182
0
        convertStringCharset(value, iptcCharset_, "UTF-8");
1183
0
      (*xmpData_)[to] = value;
1184
0
      if (erase_) {
1185
0
        pos = iptcData_->erase(pos);
1186
0
        continue;
1187
0
      }
1188
0
    }
1189
0
    ++pos;
1190
0
  }
1191
0
}
1192
1193
19.3k
void Converter::cnvXmpValueToIptc(const char* from, const char* to) {
1194
19.3k
  auto pos = xmpData_->findKey(XmpKey(from));
1195
19.3k
  if (pos == xmpData_->end())
1196
19.1k
    return;
1197
176
  if (!prepareIptcTarget(to))
1198
0
    return;
1199
1200
176
  if (pos->typeId() == langAlt || pos->typeId() == xmpText) {
1201
176
    std::string value;
1202
176
    if (!getTextValue(value, pos)) {
1203
0
#ifndef SUPPRESS_WARNINGS
1204
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
1205
0
#endif
1206
0
      return;
1207
0
    }
1208
176
    (*iptcData_)[to] = value;
1209
176
    (*iptcData_)["Iptc.Envelope.CharacterSet"] = "\033%G";  // indicate UTF-8 encoding
1210
176
    if (erase_)
1211
0
      xmpData_->erase(pos);
1212
176
    return;
1213
176
  }
1214
1215
0
  size_t count = pos->count();
1216
0
  bool added = false;
1217
0
  for (size_t i = 0; i < count; ++i) {
1218
0
    std::string value = pos->toString(i);
1219
0
    if (!pos->value().ok()) {
1220
0
#ifndef SUPPRESS_WARNINGS
1221
0
      EXV_WARNING << "Failed to convert " << from << " to " << to << "\n";
1222
0
#endif
1223
0
      continue;
1224
0
    }
1225
0
    IptcKey key(to);
1226
0
    Iptcdatum id(key);
1227
0
    id.setValue(value);
1228
0
    iptcData_->add(id);
1229
0
    added = true;
1230
0
  }
1231
0
  if (added)
1232
0
    (*iptcData_)["Iptc.Envelope.CharacterSet"] = "\033%G";  // indicate UTF-8 encoding
1233
0
  if (erase_)
1234
0
    xmpData_->erase(pos);
1235
0
}
1236
1237
#ifdef EXV_HAVE_XMP_TOOLKIT
1238
0
std::string Converter::computeExifDigest(bool tiff) {
1239
0
  std::string res;
1240
0
  MD5_CTX context;
1241
0
  unsigned char digest[16];
1242
1243
0
  MD5Init(&context);
1244
0
  for (auto&& c : conversion_) {
1245
0
    if (c.metadataId_ == mdExif) {
1246
0
      Exiv2::ExifKey key(c.key1_);
1247
0
      if (tiff && key.groupName() != "Image")
1248
0
        continue;
1249
0
      if (!tiff && key.groupName() == "Image")
1250
0
        continue;
1251
1252
0
      if (!res.empty())
1253
0
        res += ',';
1254
0
      res += std::to_string(key.tag());
1255
0
      auto pos = exifData_->findKey(key);
1256
0
      if (pos == exifData_->end())
1257
0
        continue;
1258
0
      DataBuf data(pos->size());
1259
0
      pos->copy(data.data(), littleEndian /* FIXME ? */);
1260
0
      MD5Update(&context, data.c_data(), static_cast<uint32_t>(data.size()));
1261
0
    }
1262
0
  }
1263
0
  MD5Final(digest, &context);
1264
0
  res += ';';
1265
0
  for (const auto& i : digest) {
1266
0
    res += stringFormat("{:02X}", i);
1267
0
  }
1268
0
  return res;
1269
0
}
1270
#else
1271
std::string Converter::computeExifDigest(bool) {
1272
  return {};
1273
}
1274
#endif
1275
1276
0
void Converter::writeExifDigest() {
1277
0
#ifdef EXV_HAVE_XMP_TOOLKIT
1278
0
  (*xmpData_)["Xmp.tiff.NativeDigest"] = computeExifDigest(true);
1279
0
  (*xmpData_)["Xmp.exif.NativeDigest"] = computeExifDigest(false);
1280
0
#endif
1281
0
}
1282
1283
0
void Converter::syncExifWithXmp() {
1284
0
  auto td = xmpData_->findKey(XmpKey("Xmp.tiff.NativeDigest"));
1285
0
  auto ed = xmpData_->findKey(XmpKey("Xmp.exif.NativeDigest"));
1286
0
  if (td != xmpData_->end() && ed != xmpData_->end()) {
1287
0
    if (td->value().toString() == computeExifDigest(true) && ed->value().toString() == computeExifDigest(false)) {
1288
      // We have both digests and the values match
1289
      // XMP is up-to-date, we should update Exif
1290
0
      setOverwrite(true);
1291
0
      setErase(false);
1292
1293
0
      cnvFromXmp();
1294
0
      writeExifDigest();
1295
0
      return;
1296
0
    }
1297
    // We have both digests and the values do not match
1298
    // Exif was modified after XMP, we should update XMP
1299
0
    setOverwrite(true);
1300
0
    setErase(false);
1301
1302
0
    cnvToXmp();
1303
0
    writeExifDigest();
1304
0
    return;
1305
0
  }
1306
  // We don't have both digests, it is probably the first conversion to XMP
1307
0
  setOverwrite(false);  // to be safe
1308
0
  setErase(false);
1309
1310
0
  cnvToXmp();
1311
0
  writeExifDigest();
1312
0
}
1313
1314
// *************************************************************************
1315
// free functions
1316
0
void copyExifToXmp(const ExifData& exifData, XmpData& xmpData) {
1317
  /// \todo the const_cast is "lying". We are modifying the input data. Check if this might have any bad side
1318
  /// effect
1319
0
  Converter converter(const_cast<ExifData&>(exifData), xmpData);
1320
0
  converter.cnvToXmp();
1321
0
}
1322
1323
/// \todo not used internally. We should at least have unit tests for this.
1324
0
void moveExifToXmp(ExifData& exifData, XmpData& xmpData) {
1325
0
  Converter converter(exifData, xmpData);
1326
0
  converter.setErase();
1327
0
  converter.cnvToXmp();
1328
0
}
1329
1330
879
void copyXmpToExif(const XmpData& xmpData, ExifData& exifData) {
1331
879
  Converter converter(exifData, const_cast<XmpData&>(xmpData));
1332
879
  converter.cnvFromXmp();
1333
879
}
1334
1335
/// \todo not used internally. We should at least have unit tests for this.
1336
0
void moveXmpToExif(XmpData& xmpData, ExifData& exifData) {
1337
0
  Converter converter(exifData, xmpData);
1338
0
  converter.setErase();
1339
0
  converter.cnvFromXmp();
1340
0
}
1341
1342
0
void syncExifWithXmp(ExifData& exifData, XmpData& xmpData) {
1343
0
  Converter converter(exifData, xmpData);
1344
0
  converter.syncExifWithXmp();
1345
0
}
1346
1347
0
void copyIptcToXmp(const IptcData& iptcData, XmpData& xmpData, const char* iptcCharset) {
1348
0
  if (!iptcCharset)
1349
0
    iptcCharset = iptcData.detectCharset();
1350
0
  if (!iptcCharset)
1351
0
    iptcCharset = "ISO-8859-1";
1352
1353
0
  Converter converter(const_cast<IptcData&>(iptcData), xmpData, iptcCharset);
1354
0
  converter.cnvToXmp();
1355
0
}
1356
1357
/// \todo not used internally. We should at least have unit tests for this.
1358
0
void moveIptcToXmp(IptcData& iptcData, XmpData& xmpData, const char* iptcCharset) {
1359
0
  if (!iptcCharset)
1360
0
    iptcCharset = iptcData.detectCharset();
1361
0
  if (!iptcCharset)
1362
0
    iptcCharset = "ISO-8859-1";
1363
0
  Converter converter(iptcData, xmpData, iptcCharset);
1364
0
  converter.setErase();
1365
0
  converter.cnvToXmp();
1366
0
}
1367
1368
879
void copyXmpToIptc(const XmpData& xmpData, IptcData& iptcData) {
1369
879
  Converter converter(iptcData, const_cast<XmpData&>(xmpData));
1370
879
  converter.cnvFromXmp();
1371
879
}
1372
1373
/// \todo not used internally. We should at least have unit tests for this.
1374
0
void moveXmpToIptc(XmpData& xmpData, IptcData& iptcData) {
1375
0
  Converter converter(iptcData, xmpData);
1376
0
  converter.setErase();
1377
0
  converter.cnvFromXmp();
1378
0
}
1379
1380
4.96k
bool convertStringCharset([[maybe_unused]] std::string& str, const char* from, const char* to) {
1381
4.96k
  if (0 == strcmp(from, to))
1382
60
    return true;  // nothing to do
1383
4.90k
#ifdef EXV_HAVE_ICONV
1384
4.90k
  return convertStringCharsetIconv(str, from, to);
1385
#elif defined _WIN32
1386
  return convertStringCharsetWindows(str, from, to);
1387
#else
1388
#ifndef SUPPRESS_WARNINGS
1389
  EXV_WARNING << "Charset conversion required but no character mapping functionality available.\n";
1390
#endif
1391
  return false;
1392
#endif
1393
4.96k
}
1394
}  // namespace Exiv2
1395
1396
// *****************************************************************************
1397
// local definitions
1398
namespace {
1399
using namespace Exiv2;
1400
1401
#ifdef EXV_HAVE_ICONV
1402
4.90k
bool convertStringCharsetIconv(std::string& str, std::string_view from, std::string_view to) {
1403
4.90k
  if (from == to)
1404
0
    return true;  // nothing to do
1405
1406
4.90k
  bool ret = true;
1407
4.90k
  auto cd = iconv_open(to.data(), from.data());
1408
4.90k
  if (cd == iconv_t(-1)) {
1409
0
#ifndef SUPPRESS_WARNINGS
1410
0
    EXV_WARNING << "iconv_open: " << strError() << "\n";
1411
0
#endif
1412
0
    return false;
1413
0
  }
1414
4.90k
  std::string outstr;
1415
#ifdef WINICONV_CONST
1416
  auto inptr = (WINICONV_CONST char*)(str.c_str());
1417
#else
1418
4.90k
  auto inptr = (EXV_ICONV_CONST char*)(str.c_str());
1419
4.90k
#endif
1420
4.90k
  size_t inbytesleft = str.length();
1421
12.6k
  while (inbytesleft) {
1422
10.1k
    char outbuf[256];
1423
10.1k
    char* outptr = outbuf;
1424
10.1k
    size_t outbytesleft = sizeof(outbuf);
1425
10.1k
    size_t rc = iconv(cd, &inptr, &inbytesleft, &outptr, &outbytesleft);
1426
10.1k
    const size_t outbytesProduced = sizeof(outbuf) - outbytesleft;
1427
10.1k
    if (rc == std::numeric_limits<size_t>::max() && errno != E2BIG) {
1428
2.38k
#ifndef SUPPRESS_WARNINGS
1429
2.38k
      EXV_WARNING << "iconv: " << strError() << " inbytesleft = " << inbytesleft << "\n";
1430
2.38k
#endif
1431
2.38k
      ret = false;
1432
2.38k
      break;
1433
2.38k
    }
1434
7.75k
    outstr.append(std::string(outbuf, outbytesProduced));
1435
7.75k
  }
1436
1437
4.90k
  if (cd)
1438
4.90k
    iconv_close(cd);
1439
1440
4.90k
  if (ret)
1441
2.52k
    str = std::move(outstr);
1442
4.90k
  return ret;
1443
4.90k
}
1444
1445
#elif defined(_WIN32)
1446
bool swapBytes(std::string& str) {
1447
  // Naive byte-swapping, I'm sure this can be done more efficiently
1448
  if (str.size() & 1) {
1449
#ifdef EXIV2_DEBUG_MESSAGES
1450
    EXV_DEBUG << "swapBytes: Size " << str.size() << " of input string is not even.\n";
1451
#endif
1452
    return false;
1453
  }
1454
  for (auto it = str.begin(); it != str.end(); it += 2)
1455
    std::iter_swap(it, std::next(it));
1456
  return true;
1457
}
1458
1459
bool mb2wc(UINT cp, std::string& str) {
1460
  if (str.empty())
1461
    return true;
1462
  int len = MultiByteToWideChar(cp, 0, str.c_str(), static_cast<int>(str.size()), nullptr, 0);
1463
  if (len == 0) {
1464
#ifdef EXIV2_DEBUG_MESSAGES
1465
    EXV_DEBUG << "mb2wc: Failed to determine required size of output buffer.\n";
1466
#endif
1467
    return false;
1468
  }
1469
  std::vector<std::string::value_type> out;
1470
  out.resize(len * 2);
1471
  int ret = MultiByteToWideChar(cp, 0, str.c_str(), static_cast<int>(str.size()), reinterpret_cast<LPWSTR>(out.data()),
1472
                                len * 2);
1473
  if (ret == 0) {
1474
#ifdef EXIV2_DEBUG_MESSAGES
1475
    EXV_DEBUG << "mb2wc: Failed to convert the input string to a wide character string.\n";
1476
#endif
1477
    return false;
1478
  }
1479
  str.assign(out.begin(), out.end());
1480
  return true;
1481
}
1482
1483
bool wc2mb(UINT cp, std::string& str) {
1484
  if (str.empty())
1485
    return true;
1486
  if (str.size() & 1) {
1487
#ifdef EXIV2_DEBUG_MESSAGES
1488
    EXV_DEBUG << "wc2mb: Size " << str.size() << " of input string is not even.\n";
1489
#endif
1490
    return false;
1491
  }
1492
  int len = WideCharToMultiByte(cp, 0, reinterpret_cast<LPCWSTR>(str.data()), static_cast<int>(str.size()) / 2, nullptr,
1493
                                0, nullptr, nullptr);
1494
  if (len == 0) {
1495
#ifdef EXIV2_DEBUG_MESSAGES
1496
    EXV_DEBUG << "wc2mb: Failed to determine required size of output buffer.\n";
1497
#endif
1498
    return false;
1499
  }
1500
  std::vector<std::string::value_type> out;
1501
  out.resize(len);
1502
  int ret = WideCharToMultiByte(cp, 0, reinterpret_cast<LPCWSTR>(str.data()), static_cast<int>(str.size()) / 2,
1503
                                static_cast<LPSTR>(out.data()), len, nullptr, nullptr);
1504
  if (ret == 0) {
1505
#ifdef EXIV2_DEBUG_MESSAGES
1506
    EXV_DEBUG << "wc2mb: Failed to convert the input string to a multi byte string.\n";
1507
#endif
1508
    return false;
1509
  }
1510
  str.assign(out.begin(), out.end());
1511
  return true;
1512
}
1513
1514
bool utf8ToUcs2be(std::string& str) {
1515
  bool ret = mb2wc(CP_UTF8, str);
1516
  if (ret)
1517
    ret = swapBytes(str);
1518
  return ret;
1519
}
1520
1521
bool utf8ToUcs2le(std::string& str) {
1522
  return mb2wc(CP_UTF8, str);
1523
}
1524
1525
bool ucs2beToUtf8(std::string& str) {
1526
  bool ret = swapBytes(str);
1527
  if (ret)
1528
    ret = wc2mb(CP_UTF8, str);
1529
  return ret;
1530
}
1531
1532
bool ucs2beToUcs2le(std::string& str) {
1533
  return swapBytes(str);
1534
}
1535
1536
bool ucs2leToUtf8(std::string& str) {
1537
  return wc2mb(CP_UTF8, str);
1538
}
1539
1540
bool ucs2leToUcs2be(std::string& str) {
1541
  return swapBytes(str);
1542
}
1543
1544
bool iso88591ToUtf8(std::string& str) {
1545
  bool ret = mb2wc(28591, str);
1546
  if (ret)
1547
    ret = wc2mb(CP_UTF8, str);
1548
  return ret;
1549
}
1550
1551
bool asciiToUtf8(std::string& /*str*/) {
1552
  // nothing to do
1553
  return true;
1554
}
1555
1556
using ConvFct = bool (*)(std::string&);
1557
1558
struct ConvFctList {
1559
  bool operator==(const std::pair<std::string_view, std::string_view>& fromTo) const {
1560
    return from_ == fromTo.first && to_ == fromTo.second;
1561
  }
1562
  std::string_view from_;
1563
  std::string_view to_;
1564
  ConvFct convFct_;
1565
};
1566
1567
constexpr ConvFctList convFctList[] = {
1568
    {"UTF-8", "UCS-2BE", &utf8ToUcs2be},      {"UTF-8", "UCS-2LE", &utf8ToUcs2le},
1569
    {"UCS-2BE", "UTF-8", &ucs2beToUtf8},      {"UCS-2BE", "UCS-2LE", &ucs2beToUcs2le},
1570
    {"UCS-2LE", "UTF-8", &ucs2leToUtf8},      {"UCS-2LE", "UCS-2BE", &ucs2leToUcs2be},
1571
    {"ISO-8859-1", "UTF-8", &iso88591ToUtf8}, {"ASCII", "UTF-8", &asciiToUtf8},
1572
    // Update the convertStringCharset() documentation if you add more here!
1573
};
1574
1575
bool convertStringCharsetWindows(std::string& str, std::string_view from, std::string_view to) {
1576
  bool ret = false;
1577
  std::string tmpstr = str;
1578
  if (auto p = Exiv2::find(convFctList, std::pair(from, to)))
1579
    ret = p->convFct_(tmpstr);
1580
#ifndef SUPPRESS_WARNINGS
1581
  else {
1582
    EXV_WARNING << "No Windows function to map character string from " << from.data() << " to " << to.data()
1583
                << " available.\n";
1584
  }
1585
#endif
1586
  if (ret)
1587
    str = std::move(tmpstr);
1588
  return ret;
1589
}
1590
1591
#endif  // EXV_HAVE_ICONV
1592
189
bool getTextValue(std::string& value, XmpData::iterator pos) {
1593
189
  if (pos->typeId() == langAlt) {
1594
    // get the default language entry without x-default qualifier
1595
0
    value = pos->toString(0);
1596
0
    if (!pos->value().ok() && pos->count() == 1) {
1597
      // If there is no default but exactly one entry, take that
1598
      // without the qualifier
1599
0
      value = pos->toString();
1600
0
      if (pos->value().ok() && value.starts_with("lang=")) {
1601
0
        const std::string::size_type first_space_pos = value.find_first_of(' ');
1602
0
        if (first_space_pos != std::string::npos) {
1603
0
          value = value.substr(first_space_pos + 1);
1604
0
        } else {
1605
0
          value.clear();
1606
0
        }
1607
0
      }
1608
0
    }
1609
189
  } else {
1610
189
    value = pos->toString();
1611
189
  }
1612
189
  return pos->value().ok();
1613
189
}
1614
1615
}  // namespace