/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 |