// Always creates a new metadata entry. // Passing invalidTypeId causes the type to be guessed. // Guessing types is accurate for IPTC, but not for EXIF. // Returns 0 on success EXIVSIMPLE_API int AddMeta(HIMAGE img, const char *key, const char *val, DllTypeId type) { assert(img && key && val); if (img==0 || key==0 || val==0) return -1; ImageWrapper *imgWrap = (ImageWrapper*)img; int rc = 2; Exiv2::IptcData &iptcData = imgWrap->image->iptcData(); Exiv2::ExifData &exifData = imgWrap->image->exifData(); std::string data(val); { size_t dataLen = data.length(); // if data starts and ends with quotes, remove them if (dataLen > 1 && *(data.begin()) == '\"' && *(data.rbegin()) == '\"') { data = data.substr(1, dataLen-2); } } try { Exiv2::IptcKey iptcKey(key); rc = 1; if (type == invalidTypeId) type = (DllTypeId)Exiv2::IptcDataSets::dataSetType(iptcKey.tag(), iptcKey.record()); Exiv2::Value::AutoPtr value = Exiv2::Value::create((Exiv2::TypeId)type); value->read(data); rc = iptcData.add(iptcKey, value.get()); } catch(const Exiv2::AnyError&) { } if (rc) { // Failed with iptc, so try exif try { Exiv2::ExifKey exifKey(key); rc = 1; // No way to get value type for exif... string is the most common if (type == invalidTypeId) type = asciiString; Exiv2::Value::AutoPtr value = Exiv2::Value::create((Exiv2::TypeId)type); value->read(data); exifData.add(exifKey, value.get()); rc = 0; } catch(const Exiv2::AnyError&) { } } return rc; }
bool KExiv2::setIptcTagsStringList(const char* iptcTagName, int maxSize, const QStringList& oldValues, const QStringList& newValues, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; try { QStringList oldvals = oldValues; QStringList newvals = newValues; kDebug() << d->filePath.toAscii().constData() << " : " << iptcTagName << " => " << newvals.join(",").toAscii().constData(); // Remove all old values. Exiv2::IptcData iptcData(d->iptcMetadata()); Exiv2::IptcData::iterator it = iptcData.begin(); while(it != iptcData.end()) { QString key = QString::fromLocal8Bit(it->key().c_str()); QString val(it->toString().c_str()); // Also remove new values to avoid duplicates. They will be added again below. if ( key == QString(iptcTagName) && (oldvals.contains(val) || newvals.contains(val)) ) it = iptcData.erase(it); else ++it; }; // Add new values. Exiv2::IptcKey iptcTag(iptcTagName); for (QStringList::iterator it = newvals.begin(); it != newvals.end(); ++it) { QString key = *it; key.truncate(maxSize); Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string); val->read(key.toLatin1().constData()); iptcData.add(iptcTag, val.get()); } d->iptcMetadata() = iptcData; return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString("Cannot set Iptc key '%1' into image using Exiv2 ") .arg(iptcTagName), e); } return false; }
bool KExiv2::setIptcKeywords(const QStringList& oldKeywords, const QStringList& newKeywords, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; try { QStringList oldkeys = oldKeywords; QStringList newkeys = newKeywords; kDebug() << d->filePath.toAscii().constData() << " ==> Iptc Keywords: " << newkeys.join(",").toAscii().constData(); // Remove all old keywords. Exiv2::IptcData iptcData(d->iptcMetadata()); Exiv2::IptcData::iterator it = iptcData.begin(); while(it != iptcData.end()) { QString key = QString::fromLocal8Bit(it->key().c_str()); QString val(it->toString().c_str()); // Also remove new keywords to avoid duplicates. They will be added again below. if ( key == QString("Iptc.Application2.Keywords") && (oldKeywords.contains(val) || newKeywords.contains(val)) ) it = iptcData.erase(it); else ++it; }; // Add new keywords. Note that Keywords Iptc tag is limited to 64 char but can be redondant. Exiv2::IptcKey iptcTag("Iptc.Application2.Keywords"); for (QStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it) { QString key = *it; key.truncate(64); Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string); val->read(key.toLatin1().constData()); iptcData.add(iptcTag, val.get()); } d->iptcMetadata() = iptcData; return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError("Cannot set Iptc Keywords into image using Exiv2 ", e); } return false; }
bool KExiv2::setIptcSubCategories(const QStringList& oldSubCategories, const QStringList& newSubCategories, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; try { QStringList oldkeys = oldSubCategories; QStringList newkeys = newSubCategories; // Remove all old Sub Categories. Exiv2::IptcData iptcData(d->iptcMetadata()); Exiv2::IptcData::iterator it = iptcData.begin(); while(it != iptcData.end()) { QString key = QString::fromLocal8Bit(it->key().c_str()); QString val(it->toString().c_str()); if (key == QString("Iptc.Application2.SuppCategory") && oldSubCategories.contains(val)) it = iptcData.erase(it); else ++it; }; // Add new Sub Categories. Note that SubCategories Iptc tag is limited to 32 // characters but can be redondant. Exiv2::IptcKey iptcTag("Iptc.Application2.SuppCategory"); for (QStringList::iterator it = newkeys.begin(); it != newkeys.end(); ++it) { QString key = *it; key.truncate(32); Exiv2::Value::AutoPtr val = Exiv2::Value::create(Exiv2::string); val->read(key.toLatin1().constData()); iptcData.add(iptcTag, val.get()); } d->iptcMetadata() = iptcData; return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError("Cannot set Iptc Sub Categories into image using Exiv2 ", e); } return false; }
bool ImageTags::writeTagsToImage(QString &imageFileName, QSet<QString> &newTags) { QSet<QString> imageTags; Exiv2::Image::AutoPtr exifImage; try { exifImage = Exiv2::ImageFactory::open(imageFileName.toStdString()); exifImage->readMetadata(); Exiv2::IptcData newIptcData; /* copy existing data */ Exiv2::IptcData &iptcData = exifImage->iptcData(); if (!iptcData.empty()) { QString key; Exiv2::IptcData::iterator end = iptcData.end(); for (Exiv2::IptcData::iterator iptcIt = iptcData.begin(); iptcIt != end; ++iptcIt) { if (iptcIt->tagName() != "Keywords") { newIptcData.add(*iptcIt); } } } /* add new tags */ QSetIterator<QString> newTagsIt(newTags); while (newTagsIt.hasNext()) { QString tag = newTagsIt.next(); Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::string); value->read(tag.toStdString()); Exiv2::IptcKey key("Iptc.Application2.Keywords"); newIptcData.add(key, value.get()); } exifImage->setIptcData(newIptcData); exifImage->writeMetadata(); } catch (Exiv2::Error &error) { QMessageBox msgBox; msgBox.critical(this, tr("Error"), tr("Failed to save tags to ") + imageFileName); return false; } return true; }
int WriteFixedDatestamp(const char* File, time_t Time) { // Write the GPS data to the file... struct stat statbuf; struct stat statbuf2; struct utimbuf utb; stat(File, &statbuf); Exiv2::Image::AutoPtr Image; try { Image = Exiv2::ImageFactory::open(File); } catch (Exiv2::Error e) { DEBUGLOG("Failed to open file %s.\n", File); return 0; } Image->readMetadata(); if (Image.get() == NULL) { // It failed if we got here. DEBUGLOG("Failed to read file %s %s.\n", File, Exiv2::strError().c_str()); return 0; } Exiv2::ExifData &ExifToWrite = Image->exifData(); const struct tm TimeStamp = *gmtime(&Time); char ScratchBuf[100]; snprintf(ScratchBuf, sizeof(ScratchBuf), "%04d:%02d:%02d", TimeStamp.tm_year + 1900, TimeStamp.tm_mon + 1, TimeStamp.tm_mday); ExifToWrite.erase(ExifToWrite.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSDateStamp"))); ExifToWrite["Exif.GPSInfo.GPSDateStamp"] = ScratchBuf; Exiv2::Value::AutoPtr Value = Exiv2::Value::create(Exiv2::unsignedRational); snprintf(ScratchBuf, sizeof(ScratchBuf), "%d/1 %d/1 %d/1", TimeStamp.tm_hour, TimeStamp.tm_min, TimeStamp.tm_sec); Value->read(ScratchBuf); ExifToWrite.erase(ExifToWrite.findKey(Exiv2::ExifKey("Exif.GPSInfo.GPSTimeStamp"))); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSTimeStamp"), Value.get()); try { Image->writeMetadata(); } catch (Exiv2::Error e) { DEBUGLOG("Failed to write to file %s.\n", File); return 0; } // Reset the mtime. stat(File, &statbuf2); utb.actime = statbuf2.st_atime; utb.modtime = statbuf.st_mtime; utime(File, &utb); return 1; }
int WriteGPSData(const char* File, const struct GPSPoint* Point, const char* Datum, int NoChangeMtime, int DegMinSecs) { // Write the GPS data to the file... struct stat statbuf; struct stat statbuf2; struct utimbuf utb; if (NoChangeMtime) stat(File, &statbuf); Exiv2::Image::AutoPtr Image; try { Image = Exiv2::ImageFactory::open(File); } catch (Exiv2::Error e) { DEBUGLOG("Failed to open file %s.\n", File); return 0; } Image->readMetadata(); if (Image.get() == NULL) { // It failed if we got here. DEBUGLOG("Failed to read file %s %s.\n", File, Exiv2::strError().c_str()); return 0; } Exiv2::ExifData &ExifToWrite = Image->exifData(); // Make sure we're starting from a clean GPS IFD. // There might be lots of GPS tags existing here, since only the // presence of the GPSLatitude tag causes correlation to stop with // "GPS Already Present" error. EraseGpsTags(ExifToWrite); char ScratchBuf[100]; // Do all the easy constant ones first. // GPSVersionID tag: standard says it should be four bytes: 02 02 00 00 // (and, must be present). Exiv2::Value::AutoPtr Value = Exiv2::Value::create(Exiv2::unsignedByte); Value->read("2 2 0 0"); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), Value.get()); // Datum: the datum of the measured data. The default is WGS-84. if (*Datum) ExifToWrite["Exif.GPSInfo.GPSMapDatum"] = Datum; // Now start adding data. // ALTITUDE. // If no altitude was found in the GPX file, ElevDecimals will be -1 if (Point->ElevDecimals >= 0) { // Altitude reference: byte "00" meaning "sea level". // Or "01" if the altitude value is negative. Value = Exiv2::Value::create(Exiv2::unsignedByte); if (Point->Elev >= 0) { Value->read("0"); } else { Value->read("1"); } ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), Value.get()); // And the actual altitude. Value = Exiv2::Value::create(Exiv2::unsignedRational); // 3 decimal points is beyond the limit of current GPS technology int Decimals = MIN(Point->ElevDecimals, 3); ConvertToRational(fabs(Point->Elev), Decimals, ScratchBuf, sizeof(ScratchBuf)); /* printf("Altitude: %f -> %s\n", Point->Elev, ScratchBuf); */ Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitude"), Value.get()); } // LATITUDE // Latitude reference: "N" or "S". if (Point->Lat < 0) { // Less than Zero: ie, minus: means // Southern hemisphere. Where I live. ExifToWrite["Exif.GPSInfo.GPSLatitudeRef"] = "S"; } else { // More than Zero: ie, plus: means // Northern hemisphere. ExifToWrite["Exif.GPSInfo.GPSLatitudeRef"] = "N"; } // Now the actual latitude itself. // The original comment read: // This is done as three rationals. // I choose to do it as: // dd/1 - degrees. // mmmm/100 - minutes // 0/1 - seconds // Exif standard says you can do it with minutes // as mm/1 and then seconds as ss/1, but its // (slightly) more accurate to do it as // mmmm/100 than to split it. // We also absolute the value (with fabs()) // as the sign is encoded in LatRef. // Further note: original code did not translate between // dd.dddddd to dd mm.mm - that's why we now multiply // by 60*N - x60 to get minutes, xN to get to mmmm/N. // N is 10^S where S is the number of significant decimal // places. // // Rereading the EXIF standard, it's quite ok to do DD MM SS.SS // Which is much more accurate. This is the new default, unless otherwise // set. Value = Exiv2::Value::create(Exiv2::unsignedRational); if (DegMinSecs) { ConvertToLatLongRational(Point->Lat, Point->LatDecimals, ScratchBuf, sizeof(ScratchBuf)); } else { ConvertToOldLatLongRational(Point->Lat, ScratchBuf, sizeof(ScratchBuf)); } Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSLatitude"), Value.get()); // LONGITUDE // Longitude reference: "E" or "W". if (Point->Long < 0) { // Less than Zero: ie, minus: means // Western hemisphere. ExifToWrite["Exif.GPSInfo.GPSLongitudeRef"] = "W"; } else { // More than Zero: ie, plus: means // Eastern hemisphere. Where I live. ExifToWrite["Exif.GPSInfo.GPSLongitudeRef"] = "E"; } // Now the actual longitude itself, in the same way as latitude Value = Exiv2::Value::create(Exiv2::unsignedRational); if (DegMinSecs) { ConvertToLatLongRational(Point->Long, Point->LongDecimals, ScratchBuf, sizeof(ScratchBuf)); } else { ConvertToOldLatLongRational(Point->Long, ScratchBuf, sizeof(ScratchBuf)); } Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSLongitude"), Value.get()); // The timestamp. // Make up the timestamp... // The timestamp is taken as the UTC time of the photo. // If interpolation occurred, then this time is the time of the photo. struct tm TimeStamp = *gmtime(&(Point->Time)); Value = Exiv2::Value::create(Exiv2::unsignedRational); snprintf(ScratchBuf, sizeof(ScratchBuf), "%d/1 %d/1 %d/1", TimeStamp.tm_hour, TimeStamp.tm_min, TimeStamp.tm_sec); Value->read(ScratchBuf); ExifToWrite.add(Exiv2::ExifKey("Exif.GPSInfo.GPSTimeStamp"), Value.get()); // And we should also do a datestamp. snprintf(ScratchBuf, sizeof(ScratchBuf), "%04d:%02d:%02d", TimeStamp.tm_year + 1900, TimeStamp.tm_mon + 1, TimeStamp.tm_mday); ExifToWrite["Exif.GPSInfo.GPSDateStamp"] = ScratchBuf; // Write the data to file. try { Image->writeMetadata(); } catch (Exiv2::Error e) { DEBUGLOG("Failed to write to file %s.\n", File); return 0; } if (NoChangeMtime) { stat(File, &statbuf2); utb.actime = statbuf2.st_atime; utb.modtime = statbuf.st_mtime; utime(File, &utb); } return 1; }
int main() try { // The XMP property container Exiv2::XmpData xmpData; // ------------------------------------------------------------------------- // Teaser: Setting XMP properties doesn't get much easier than this: xmpData["Xmp.dc.source"] = "xmpsample.cpp"; // a simple text value xmpData["Xmp.dc.subject"] = "Palmtree"; // an array item xmpData["Xmp.dc.subject"] = "Rubbertree"; // add a 2nd array item // a language alternative with two entries and without default xmpData["Xmp.dc.title"] = "lang=de-DE Sonnenuntergang am Strand"; xmpData["Xmp.dc.title"] = "lang=en-US Sunset on the beach"; // ------------------------------------------------------------------------- // Any properties can be set provided the namespace is known. Values of any // type can be assigned to an Xmpdatum, if they have an output operator. The // default XMP value type for unknown properties is a simple text value. xmpData["Xmp.dc.one"] = -1; xmpData["Xmp.dc.two"] = 3.1415; xmpData["Xmp.dc.three"] = Exiv2::Rational(5, 7); xmpData["Xmp.dc.four"] = uint16_t(255); xmpData["Xmp.dc.five"] = int32_t(256); xmpData["Xmp.dc.six"] = false; // In addition, there is a dedicated assignment operator for Exiv2::Value Exiv2::XmpTextValue val("Seven"); xmpData["Xmp.dc.seven"] = val; xmpData["Xmp.dc.eight"] = true; // Extracting values assert(xmpData["Xmp.dc.one"].toLong() == -1); assert(xmpData["Xmp.dc.one"].value().ok()); const Exiv2::Value &getv1 = xmpData["Xmp.dc.one"].value(); assert(isEqual(getv1.toFloat(), -1)); assert(getv1.ok()); assert(getv1.toRational() == Exiv2::Rational(-1, 1)); assert(getv1.ok()); const Exiv2::Value &getv2 = xmpData["Xmp.dc.two"].value(); assert(isEqual(getv2.toFloat(), 3.1415f)); assert(getv2.ok()); assert(getv2.toLong() == 3); assert(getv2.ok()); Exiv2::Rational R = getv2.toRational(); assert(getv2.ok()); assert(isEqual(static_cast<float>(R.first) / R.second, 3.1415f )); const Exiv2::Value &getv3 = xmpData["Xmp.dc.three"].value(); assert(isEqual(getv3.toFloat(), 5.0f/7.0f)); assert(getv3.ok()); assert(getv3.toLong() == 0); // long(5.0 / 7.0) assert(getv3.ok()); assert(getv3.toRational() == Exiv2::Rational(5, 7)); assert(getv3.ok()); const Exiv2::Value &getv6 = xmpData["Xmp.dc.six"].value(); assert(getv6.toLong() == 0); assert(getv6.ok()); assert(getv6.toFloat() == 0.0); assert(getv6.ok()); assert(getv6.toRational() == Exiv2::Rational(0, 1)); assert(getv6.ok()); const Exiv2::Value &getv7 = xmpData["Xmp.dc.seven"].value(); getv7.toLong(); // this should fail assert(!getv7.ok()); const Exiv2::Value &getv8 = xmpData["Xmp.dc.eight"].value(); assert(getv8.toLong() == 1); assert(getv8.ok()); assert(getv8.toFloat() == 1.0); assert(getv8.ok()); assert(getv8.toRational() == Exiv2::Rational(1, 1)); assert(getv8.ok()); // Deleting an XMP property Exiv2::XmpData::iterator pos = xmpData.findKey(Exiv2::XmpKey("Xmp.dc.eight")); if (pos == xmpData.end()) throw Exiv2::Error(1, "Key not found"); xmpData.erase(pos); // ------------------------------------------------------------------------- // Exiv2 has specialized values for simple XMP properties, arrays of simple // properties and language alternatives. // Add a simple XMP property in a known namespace Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::xmpText); v->read("image/jpeg"); xmpData.add(Exiv2::XmpKey("Xmp.dc.format"), v.get()); // Add an ordered array of text values. v = Exiv2::Value::create(Exiv2::xmpSeq); // or xmpBag or xmpAlt. v->read("1) The first creator"); // The sequence in which the array v->read("2) The second creator"); // elements are added is their v->read("3) And another one"); // order in the array. xmpData.add(Exiv2::XmpKey("Xmp.dc.creator"), v.get()); // Add a language alternative property v = Exiv2::Value::create(Exiv2::langAlt); v->read("lang=de-DE Hallo, Welt"); // The default doesn't need a v->read("Hello, World"); // qualifier xmpData.add(Exiv2::XmpKey("Xmp.dc.description"), v.get()); // According to the XMP specification, Xmp.tiff.ImageDescription is an // alias for Xmp.dc.description. Exiv2 treats an alias just like any // other property and leaves it to the application to implement specific // behaviour if desired. xmpData["Xmp.tiff.ImageDescription"] = "TIFF image description"; xmpData["Xmp.tiff.ImageDescription"] = "lang=de-DE TIFF Bildbeschreibung"; // ------------------------------------------------------------------------- // Register a namespace which Exiv2 doesn't know yet. This is only needed // when properties are added manually. If the XMP metadata is read from an // image, namespaces are decoded and registered at the same time. Exiv2::XmpProperties::registerNs("myNamespace/", "ns"); // ------------------------------------------------------------------------- // Add a property in the new custom namespace. xmpData["Xmp.ns.myProperty"] = "myValue"; // ------------------------------------------------------------------------- // There are no specialized values for structures, qualifiers and nested // types. However, these can be added by using an XmpTextValue and a path as // the key. // Add a structure Exiv2::XmpTextValue tv("16"); xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:w"), &tv); tv.read("9"); xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:h"), &tv); tv.read("inch"); xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:unit"), &tv); // Add an element with a qualifier (using the namespace registered above) xmpData["Xmp.dc.publisher"] = "James Bond"; // creates an unordered array xmpData["Xmp.dc.publisher[1]/?ns:role"] = "secret agent"; // Add a qualifer to an array element of Xmp.dc.creator (added above) tv.read("programmer"); xmpData.add(Exiv2::XmpKey("Xmp.dc.creator[2]/?ns:role"), &tv); // Add an array of structures tv.read(""); // Clear the value tv.setXmpArrayType(Exiv2::XmpValue::xaBag); xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef"), &tv); // Set the array type. tv.setXmpArrayType(Exiv2::XmpValue::xaNone); tv.read("Birthday party"); xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[1]/stJob:name"), &tv); tv.read("Photographer"); xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[1]/stJob:role"), &tv); tv.read("Wedding ceremony"); xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[2]/stJob:name"), &tv); tv.read("Best man"); xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[2]/stJob:role"), &tv); // ------------------------------------------------------------------------- // Output XMP properties for (Exiv2::XmpData::const_iterator md = xmpData.begin(); md != xmpData.end(); ++md) { std::cout << std::setfill(' ') << std::left << std::setw(44) << md->key() << " " << std::setw(9) << std::setfill(' ') << std::left << md->typeName() << " " << std::dec << std::setw(3) << std::setfill(' ') << std::right << md->count() << " " << std::dec << md->value() << std::endl; } // ------------------------------------------------------------------------- // Serialize the XMP data and output the XMP packet std::string xmpPacket; if (0 != Exiv2::XmpParser::encode(xmpPacket, xmpData)) { throw Exiv2::Error(1, "Failed to serialize XMP data"); } std::cout << xmpPacket << "\n"; // Cleanup Exiv2::XmpParser::terminate(); return 0; } catch (Exiv2::AnyError& e) { std::cout << "Caught Exiv2 exception '" << e << "'\n"; return -1; }
bool KExiv2::setImageDateTime(const QDateTime& dateTime, bool setDateTimeDigitized, bool setProgramName) const { if(!dateTime.isValid()) return false; if (!setProgramId(setProgramName)) return false; try { // In first we write date & time into Exif. // DateTimeDigitized is set by slide scanners etc. when a picture is digitized. // DateTimeOriginal specifies the date/time when the picture was taken. // For digital cameras, these dates should be both set, and identical. // Reference: http://www.exif.org/Exif2-2.PDF, chapter 4.6.5, table 4, section F. const std::string &exifdatetime(dateTime.toString(QString("yyyy:MM:dd hh:mm:ss")).toAscii().constData()); d->exifMetadata()["Exif.Image.DateTime"] = exifdatetime; d->exifMetadata()["Exif.Photo.DateTimeOriginal"] = exifdatetime; if(setDateTimeDigitized) d->exifMetadata()["Exif.Photo.DateTimeDigitized"] = exifdatetime; #ifdef _XMP_SUPPORT_ // In second we write date & time into Xmp. const std::string &xmpdatetime(dateTime.toString(Qt::ISODate).toAscii().constData()); Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::xmpText); xmpTxtVal->read(xmpdatetime); d->xmpMetadata().add(Exiv2::XmpKey("Xmp.exif.DateTimeOriginal"), xmpTxtVal.get()); d->xmpMetadata().add(Exiv2::XmpKey("Xmp.photoshop.DateCreated"), xmpTxtVal.get()); d->xmpMetadata().add(Exiv2::XmpKey("Xmp.tiff.DateTime"), xmpTxtVal.get()); d->xmpMetadata().add(Exiv2::XmpKey("Xmp.xmp.CreateDate"), xmpTxtVal.get()); d->xmpMetadata().add(Exiv2::XmpKey("Xmp.xmp.MetadataDate"), xmpTxtVal.get()); d->xmpMetadata().add(Exiv2::XmpKey("Xmp.xmp.ModifyDate"), xmpTxtVal.get()); if(setDateTimeDigitized) d->xmpMetadata().add(Exiv2::XmpKey("Xmp.exif.DateTimeDigitized"), xmpTxtVal.get()); // Tag not updated: // "Xmp.dc.DateTime" is a sequence of date relevant of dublin core change. // This is not the picture date as well #endif // _XMP_SUPPORT_ // In third we write date & time into Iptc. const std::string &iptcdate(dateTime.date().toString(Qt::ISODate).toAscii().constData()); const std::string &iptctime(dateTime.time().toString(Qt::ISODate).toAscii().constData()); d->iptcMetadata()["Iptc.Application2.DateCreated"] = iptcdate; d->iptcMetadata()["Iptc.Application2.TimeCreated"] = iptctime; if(setDateTimeDigitized) { d->iptcMetadata()["Iptc.Application2.DigitizationDate"] = iptcdate; d->iptcMetadata()["Iptc.Application2.DigitizationTime"] = iptctime; } return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError("Cannot set Date & Time into image using Exiv2 ", e); } catch(...) { kError() << "Default exception from Exiv2"; } return false; }
// Overwrites existing value if found, otherwise creates a new one. // Passing invalidTypeId causes the type to be guessed. // Guessing types is accurate for IPTC, but not for EXIF. // Returns 0 on success EXIVSIMPLE_API int ModifyMeta(HIMAGE img, const char *key, const char *val, DllTypeId type) { assert(img && key && val); if (img==0 || key==0 || val==0) return -1; ImageWrapper *imgWrap = (ImageWrapper*)img; int rc = 2; Exiv2::IptcData &iptcData = imgWrap->image->iptcData(); Exiv2::ExifData &exifData = imgWrap->image->exifData(); std::string data(val); // if data starts and ends with quotes, remove them if (data.at(0) == '\"' && data.at(data.size()-1) == '\"') { data = data.substr(1, data.size()-2); } try { Exiv2::IptcKey iptcKey(key); rc = 1; if (type == invalidTypeId) type = (DllTypeId)Exiv2::IptcDataSets::dataSetType(iptcKey.tag(), iptcKey.record()); Exiv2::Value::AutoPtr value = Exiv2::Value::create((Exiv2::TypeId)type); value->read(data); Exiv2::IptcData::iterator iter = iptcData.findKey(iptcKey); if (iter != iptcData.end()) { iter->setValue(value.get()); rc = 0; } else { rc = iptcData.add(iptcKey, value.get()); } } catch(const Exiv2::AnyError&) { } if (rc) { // Failed with iptc, so try exif try { Exiv2::ExifKey exifKey(key); rc = 1; // No way to get value type for exif... string is the most common if (type == invalidTypeId) type = asciiString; Exiv2::Value::AutoPtr value = Exiv2::Value::create((Exiv2::TypeId)type); value->read(data); Exiv2::ExifData::iterator iter = exifData.findKey(exifKey); if (iter != exifData.end()) { iter->setValue(value.get()); rc = 0; } else { exifData.add(exifKey, value.get()); rc = 0; } } catch(const Exiv2::AnyError&) { } } return rc; }
void AsfVideo::metadataHandler(int meta) { DataBuf buf(5000); io_->read(buf.pData_, 2); int recordCount = Exiv2::getUShort(buf.pData_, littleEndian), nameLength = 0, dataLength = 0, dataType = 0; Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::xmpSeq); byte guidBuf[16]; char fileID[37] = ""; while(recordCount--) { std::memset(buf.pData_, 0x0, buf.size_); if(meta == 1 || meta == 3) { io_->read(buf.pData_, 4); io_->read(buf.pData_, 2); nameLength = Exiv2::getUShort(buf.pData_, littleEndian); io_->read(buf.pData_, 2); dataType = Exiv2::getUShort(buf.pData_, littleEndian); io_->read(buf.pData_, 4); dataLength = Exiv2::getULong(buf.pData_, littleEndian); if (nameLength > 5000) { #ifndef SUPPRESS_WARNINGS EXV_ERROR << "Xmp.video.Metadata nameLength was found to be larger than 5000 " << " entries considered invalid; not read.\n"; #endif io_->seek(io_->tell() + nameLength, BasicIo::beg); } else io_->read(buf.pData_, nameLength); v->read(toString16(buf)); if(dataType == 6) { io_->read(guidBuf, 16); getGUID(guidBuf, fileID); } else // Sanity check with an "unreasonably" large number if (dataLength > 5000) { #ifndef SUPPRESS_WARNINGS EXV_ERROR << "Xmp.video.Metadata dataLength was found to be larger than 5000 " << " entries considered invalid; not read.\n"; #endif io_->seek(io_->tell() + dataLength, BasicIo::beg); } else io_->read(buf.pData_, dataLength); } else if(meta == 2) { io_->read(buf.pData_, 2); nameLength = Exiv2::getUShort(buf.pData_, littleEndian); if (nameLength > 5000) { #ifndef SUPPRESS_WARNINGS EXV_ERROR << "Xmp.video.Metadata nameLength was found to be larger than 5000 " << " entries considered invalid; not read.\n"; #endif io_->seek(io_->tell() + nameLength, BasicIo::beg); } else io_->read(buf.pData_, nameLength); v->read(toString16(buf)); io_->read(buf.pData_, 2); dataType = Exiv2::getUShort(buf.pData_, littleEndian); io_->read(buf.pData_, 2); dataLength = Exiv2::getUShort(buf.pData_, littleEndian); // Sanity check with an "unreasonably" large number if (dataLength > 5000) { #ifndef SUPPRESS_WARNINGS EXV_ERROR << "Xmp.video.Metadata dataLength was found to be larger than 5000 " << " entries considered invalid; not read.\n"; #endif io_->seek(io_->tell() + dataLength, BasicIo::beg); } else io_->read(buf.pData_, dataLength); } if(dataType == 0) { // Unicode String v->read(toString16(buf)); } else if(dataType == 2 || dataType == 5) { // 16-bit Unsigned Integer v->read( Exiv2::toString( Exiv2::getUShort(buf.pData_, littleEndian))); } else if(dataType == 3) { // 32-bit Unsigned Integer v->read( Exiv2::toString( Exiv2::getULong( buf.pData_, littleEndian))); } else if(dataType == 4) { // 64-bit Unsigned Integer v->read(Exiv2::toString(getUint64_t(buf))); } else if(dataType == 6) { // 128-bit GUID v->read(Exiv2::toString(fileID)); } else { // Byte array v->read( Exiv2::toString(buf.pData_)); } } if(meta == 1) { xmpData_.add(Exiv2::XmpKey("Xmp.video.Metadata"), v.get()); } else if(meta == 2) { xmpData_.add(Exiv2::XmpKey("Xmp.video.ExtendedContentDescription"), v.get()); } else { xmpData_.add(Exiv2::XmpKey("Xmp.video.MetadataLibrary"), v.get()); } } // AsfVideo::metadataHandler
void AsfVideo::tagDecoder(const TagVocabulary *tv, uint64_t size) { uint64_t cur_pos = io_->tell(); DataBuf buf(1000); unsigned long count = 0, tempLength = 0; buf.pData_[4] = '\0' ; Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::xmpSeq); if(compareTag( exvGettext(tv->label_), "Header")) { localPosition_ = 0; io_->read(buf.pData_, 4); io_->read(buf.pData_, 2); while(localPosition_ < cur_pos + size) decodeBlock(); } else if(compareTag( exvGettext(tv->label_), "File_Properties")) fileProperties(); else if(compareTag( exvGettext(tv->label_), "Stream_Properties")) streamProperties(); else if(compareTag( exvGettext(tv->label_), "Metadata")) metadataHandler(1); else if(compareTag( exvGettext(tv->label_), "Extended_Content_Description")) metadataHandler(2); else if(compareTag( exvGettext(tv->label_), "Metadata_Library")) metadataHandler(3); else if(compareTag( exvGettext(tv->label_), "Codec_List")) codecList(); else if(compareTag( exvGettext(tv->label_), "Content_Description")) contentDescription(size); else if(compareTag( exvGettext(tv->label_), "Extended_Stream_Properties")) extendedStreamProperties(size); else if(compareTag( exvGettext(tv->label_), "Header_Extension")) { localPosition_ = 0; headerExtension(size); } else if(compareTag( exvGettext(tv->label_), "Language_List")) { std::memset(buf.pData_, 0x0, buf.size_); io_->read(buf.pData_, 2); count = Exiv2::getUShort(buf.pData_, littleEndian); while(count--) { std::memset(buf.pData_, 0x0, buf.size_); io_->read(buf.pData_, 1); tempLength = (int)buf.pData_[0]; io_->read(buf.pData_, tempLength); v->read(toString16(buf)); } xmpData_.add(Exiv2::XmpKey("Xmp.video.TrackLang"), v.get()); } io_->seek(cur_pos + size, BasicIo::beg); localPosition_ = io_->tell(); } // AsfVideo::tagDecoder