Blame src/xmpsidecar.cpp

Packit 01d647
// ***************************************************************** -*- C++ -*-
Packit 01d647
/*
Packit 01d647
 * Copyright (C) 2004-2018 Exiv2 authors
Packit 01d647
 * This program is part of the Exiv2 distribution.
Packit 01d647
 *
Packit 01d647
 * This program is free software; you can redistribute it and/or
Packit 01d647
 * modify it under the terms of the GNU General Public License
Packit 01d647
 * as published by the Free Software Foundation; either version 2
Packit 01d647
 * of the License, or (at your option) any later version.
Packit 01d647
 *
Packit 01d647
 * This program is distributed in the hope that it will be useful,
Packit 01d647
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 01d647
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 01d647
 * GNU General Public License for more details.
Packit 01d647
 *
Packit 01d647
 * You should have received a copy of the GNU General Public License
Packit 01d647
 * along with this program; if not, write to the Free Software
Packit 01d647
 * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
Packit 01d647
 */
Packit 01d647
/*
Packit 01d647
  File:      xmpsidecar.cpp
Packit 01d647
  Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
Packit 01d647
  History:   07-Mar-08, ahu: created
Packit 01d647
  Credits:   See header file
Packit 01d647
 */
Packit 01d647
// *****************************************************************************
Packit 01d647
// included header files
Packit 01d647
#include "config.h"
Packit 01d647
Packit 01d647
#include "xmpsidecar.hpp"
Packit 01d647
#include "image.hpp"
Packit 01d647
#include "basicio.hpp"
Packit 01d647
#include "error.hpp"
Packit 01d647
#include "xmp_exiv2.hpp"
Packit 01d647
#include "futils.hpp"
Packit 01d647
#include "convert.hpp"
Packit 01d647
Packit 01d647
// + standard includes
Packit 01d647
#include <string>
Packit 01d647
#include <iostream>
Packit 01d647
#include <cassert>
Packit 01d647
Packit 01d647
// *****************************************************************************
Packit 01d647
namespace {
Packit 01d647
    const char* xmlHeader = "\n";
Packit 01d647
    const long  xmlHdrCnt = (long) std::strlen(xmlHeader); // without the trailing 0-character
Packit 01d647
    const char* xmlFooter = "";
Packit 01d647
}
Packit 01d647
Packit 01d647
// class member definitions
Packit 01d647
namespace Exiv2 {
Packit 01d647
Packit 01d647
Packit 01d647
    XmpSidecar::XmpSidecar(BasicIo::AutoPtr io, bool create)
Packit 01d647
        : Image(ImageType::xmp, mdXmp, io)
Packit 01d647
    {
Packit 01d647
        if (create) {
Packit 01d647
            if (io_->open() == 0) {
Packit 01d647
                IoCloser closer(*io_);
Packit 01d647
                io_->write(reinterpret_cast<const byte*>(xmlHeader), xmlHdrCnt);
Packit 01d647
            }
Packit 01d647
        }
Packit 01d647
    } // XmpSidecar::XmpSidecar
Packit 01d647
Packit 01d647
    std::string XmpSidecar::mimeType() const
Packit 01d647
    {
Packit 01d647
        return "application/rdf+xml";
Packit 01d647
    }
Packit 01d647
Packit 01d647
    void XmpSidecar::setComment(const std::string& /*comment*/)
Packit 01d647
    {
Packit 01d647
        // not supported
Packit 01d647
        throw(Error(kerInvalidSettingForImage, "Image comment", "XMP"));
Packit 01d647
    }
Packit 01d647
Packit 01d647
    void XmpSidecar::readMetadata()
Packit 01d647
    {
Packit 01d647
#ifdef EXIV2_DEBUG_MESSAGES
Packit 01d647
        std::cerr << "Reading XMP file " << io_->path() << "\n";
Packit 01d647
#endif
Packit 01d647
        if (io_->open() != 0) {
Packit 01d647
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
Packit 01d647
        }
Packit 01d647
        IoCloser closer(*io_);
Packit 01d647
        // Ensure that this is the correct image type
Packit 01d647
        if (!isXmpType(*io_, false)) {
Packit 01d647
            if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
Packit 01d647
            throw Error(kerNotAnImage, "XMP");
Packit 01d647
        }
Packit 01d647
        // Read the XMP packet from the IO stream
Packit 01d647
        std::string xmpPacket;
Packit 01d647
        const long len = 64 * 1024;
Packit 01d647
        byte buf[len];
Packit 01d647
        long l;
Packit 01d647
        while ((l = io_->read(buf, len)) > 0) {
Packit 01d647
            xmpPacket.append(reinterpret_cast<char*>(buf), l);
Packit 01d647
        }
Packit 01d647
        if (io_->error()) throw Error(kerFailedToReadImageData);
Packit 01d647
        clearMetadata();
Packit 01d647
        xmpPacket_ = xmpPacket;
Packit 01d647
        if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_)) {
Packit 01d647
#ifndef SUPPRESS_WARNINGS
Packit 01d647
            EXV_WARNING << "Failed to decode XMP metadata.\n";
Packit 01d647
#endif
Packit 01d647
        }
Packit 01d647
Packit 01d647
        // #1112 - store dates to deal with loss of TZ information during conversions
Packit 01d647
        for (Exiv2::XmpData::const_iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) {
Packit 01d647
            std::string  key(it->key());
Packit 01d647
            if ( key.find("Date") != std::string::npos ) {
Packit 01d647
                std::string value(it->value().toString());
Packit 01d647
                dates_[key] = value;
Packit 01d647
            }
Packit 01d647
        }
Packit 01d647
Packit 01d647
        copyXmpToIptc(xmpData_, iptcData_);
Packit 01d647
        copyXmpToExif(xmpData_, exifData_);
Packit 01d647
    } // XmpSidecar::readMetadata
Packit 01d647
Packit 01d647
    // lower case string
Packit 01d647
    static std::string toLowerCase(std::string a)
Packit 01d647
    {
Packit 01d647
        for(size_t i=0 ; i < a.length() ; i++)
Packit 01d647
        {
Packit 01d647
            a[i]=tolower(a[i]);
Packit 01d647
        }
Packit 01d647
        return a;
Packit 01d647
    }
Packit 01d647
Packit 01d647
    static bool matchi(const std::string key,const char* substr)
Packit 01d647
    {
Packit 01d647
        return toLowerCase(key).find(substr) != std::string::npos;
Packit 01d647
    }
Packit 01d647
Packit 01d647
    void XmpSidecar::writeMetadata()
Packit 01d647
    {
Packit 01d647
        if (io_->open() != 0) {
Packit 01d647
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
Packit 01d647
        }
Packit 01d647
        IoCloser closer(*io_);
Packit 01d647
Packit 01d647
Packit 01d647
        if (writeXmpFromPacket() == false) {
Packit 01d647
            // #589 copy XMP tags
Packit 01d647
            Exiv2::XmpData  copy   ;
Packit 01d647
            for (Exiv2::XmpData::const_iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) {
Packit 01d647
                if ( !matchi(it->key(),"exif") && !matchi(it->key(),"iptc") ) {
Packit 01d647
                    copy[it->key()] = it->value();
Packit 01d647
                }
Packit 01d647
            }
Packit 01d647
Packit 01d647
            // run the convertors
Packit 01d647
            copyExifToXmp(exifData_, xmpData_);
Packit 01d647
            copyIptcToXmp(iptcData_, xmpData_);
Packit 01d647
Packit 01d647
            // #589 - restore tags which were modified by the convertors
Packit 01d647
            for (Exiv2::XmpData::const_iterator it = copy.begin(); it != copy.end(); ++it) {
Packit 01d647
                xmpData_[it->key()] = it->value() ;
Packit 01d647
            }
Packit 01d647
Packit 01d647
            // #1112 - restore dates if they lost their TZ info
Packit 01d647
            for ( Exiv2::Dictionary_i it = dates_.begin() ; it != dates_.end() ; ++it ) {
Packit 01d647
                std::string   sKey = it->first;
Packit 01d647
                Exiv2::XmpKey key(sKey);
Packit 01d647
                if ( xmpData_.findKey(key) != xmpData_.end() ) {
Packit 01d647
                    std::string value_orig(it->second);
Packit 01d647
                    std::string value_now(xmpData_[sKey].value().toString());
Packit 01d647
                    // std::cout << key << " -> " << value_now << " => " << value_orig << std::endl;
Packit 01d647
                    if ( value_orig.find(value_now.substr(0,10)) != std::string::npos ) {
Packit 01d647
                        xmpData_[sKey] = value_orig ;
Packit 01d647
                    }
Packit 01d647
                }
Packit 01d647
            }
Packit 01d647
Packit 01d647
            if (XmpParser::encode(xmpPacket_, xmpData_,
Packit 01d647
                                  XmpParser::omitPacketWrapper|XmpParser::useCompactFormat) > 1) {
Packit 01d647
#ifndef SUPPRESS_WARNINGS
Packit 01d647
                EXV_ERROR << "Failed to encode XMP metadata.\n";
Packit 01d647
#endif
Packit 01d647
            }
Packit 01d647
        }
Packit 01d647
        if (xmpPacket_.size() > 0) {
Packit 01d647
            if (xmpPacket_.substr(0, 5)  != "
Packit 01d647
                xmpPacket_ = xmlHeader + xmpPacket_ + xmlFooter;
Packit 01d647
            }
Packit 01d647
            BasicIo::AutoPtr tempIo(new MemIo);
Packit 01d647
            assert(tempIo.get() != 0);
Packit 01d647
            // Write XMP packet
Packit 01d647
            if (   tempIo->write(reinterpret_cast<const byte*>(xmpPacket_.data()),
Packit 01d647
                                 static_cast<long>(xmpPacket_.size()))
Packit 01d647
                != static_cast<long>(xmpPacket_.size())) throw Error(kerImageWriteFailed);
Packit 01d647
            if (tempIo->error()) throw Error(kerImageWriteFailed);
Packit 01d647
            io_->close();
Packit 01d647
            io_->transfer(*tempIo); // may throw
Packit 01d647
        }
Packit 01d647
    } // XmpSidecar::writeMetadata
Packit 01d647
Packit 01d647
    // *************************************************************************
Packit 01d647
    // free functions
Packit 01d647
    Image::AutoPtr newXmpInstance(BasicIo::AutoPtr io, bool create)
Packit 01d647
    {
Packit 01d647
        Image::AutoPtr image(new XmpSidecar(io, create));
Packit 01d647
        if (!image->good()) {
Packit 01d647
            image.reset();
Packit 01d647
        }
Packit 01d647
        return image;
Packit 01d647
    }
Packit 01d647
Packit 01d647
    bool isXmpType(BasicIo& iIo, bool advance)
Packit 01d647
    {
Packit 01d647
        /*
Packit 01d647
          Check if the file starts with an optional XML declaration followed by
Packit 01d647
          either an XMP header () or an <x:xmpmeta> element.
Packit 01d647
Packit 01d647
          In addition, in order for empty XmpSidecar objects as created by
Packit 01d647
          Exiv2 to pass the test, just an XML header is also considered ok.
Packit 01d647
         */
Packit 01d647
        const int32_t len = 80;
Packit 01d647
        byte buf[len];
Packit 01d647
        iIo.read(buf, xmlHdrCnt + 1);
Packit 01d647
        if (   iIo.eof()
Packit 01d647
            && 0 == strncmp(reinterpret_cast<const char*>(buf), xmlHeader, xmlHdrCnt)) {
Packit 01d647
            return true;
Packit 01d647
        }
Packit 01d647
        if (iIo.error() || iIo.eof()) {
Packit 01d647
            return false;
Packit 01d647
        }
Packit 01d647
        iIo.read(buf + xmlHdrCnt + 1, len - xmlHdrCnt - 1);
Packit 01d647
        if (iIo.error() || iIo.eof()) {
Packit 01d647
            return false;
Packit 01d647
        }
Packit 01d647
        // Skip leading BOM
Packit 01d647
        int32_t start = 0;
Packit 01d647
        if (0 == strncmp(reinterpret_cast<const char*>(buf), "\xef\xbb\xbf", 3)) {
Packit 01d647
            start = 3;
Packit 01d647
        }
Packit 01d647
        bool rc = false;
Packit 01d647
        std::string head(reinterpret_cast<const char*>(buf + start), len - start);
Packit 01d647
        if (head.substr(0, 5)  == "
Packit 01d647
            // Forward to the next tag
Packit 01d647
            for (unsigned i = 5; i < head.size(); ++i) {
Packit 01d647
                if (head[i] == '<') {
Packit 01d647
                    head = head.substr(i);
Packit 01d647
                    break;
Packit 01d647
                }
Packit 01d647
            }
Packit 01d647
        }
Packit 01d647
        if (   head.size() > 9
Packit 01d647
            && (   head.substr(0, 9)  == "
Packit 01d647
                || head.substr(0, 10) == "
Packit 01d647
            rc = true;
Packit 01d647
        }
Packit 01d647
        if (!advance || !rc) {
Packit 01d647
            iIo.seek(-(len - start), BasicIo::cur); // Swallow the BOM
Packit 01d647
        }
Packit 01d647
        return rc;
Packit 01d647
Packit 01d647
    }
Packit 01d647
Packit 01d647
}                                       // namespace Exiv2