diff --git a/autotests/embeddedimagedatatest.cpp b/autotests/embeddedimagedatatest.cpp index baf25a8..ff85b43 100644 --- a/autotests/embeddedimagedatatest.cpp +++ b/autotests/embeddedimagedatatest.cpp @@ -1,107 +1,185 @@ /* * EmbeddedImageData tests. * * Copyright (C) 2018 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "embeddedimagedatatest.h" #include "embeddedimagedata.h" #include "indexerextractortestsconfig.h" #include #include +#include using namespace KFileMetaData; QString EmbeddedImageDataTest::testFilePath(const QString& fileName) const { return QLatin1String(INDEXER_TESTS_SAMPLE_FILES_PATH) + QLatin1Char('/') + fileName; } void EmbeddedImageDataTest::test() { QFETCH(QString, fileName); QMimeDatabase mimeDb; QString testAudioFile; EmbeddedImageData imageData; QMap images; QByteArray originalFrontCoverImage; QFile testFile(testFilePath("cover.jpg")); testFile.open(QIODevice::ReadOnly); originalFrontCoverImage = testFile.readAll(); testAudioFile = testFilePath(fileName); QVERIFY(imageData.mimeTypes().contains(mimeDb.mimeTypeForFile(testAudioFile).name())); images = imageData.imageData(testAudioFile); QCOMPARE(images.value(EmbeddedImageData::FrontCover), originalFrontCoverImage); } void EmbeddedImageDataTest::test_data() { QTest::addColumn("fileName"); QTest::addRow("aiff") << QStringLiteral("test.aif") ; QTest::addRow("ape") << QStringLiteral("test.ape") ; QTest::addRow("opus") << QStringLiteral("test.opus") ; QTest::addRow("ogg") << QStringLiteral("test.ogg") ; QTest::addRow("flac") << QStringLiteral("test.flac") ; QTest::addRow("mp3") << QStringLiteral("test.mp3") ; QTest::addRow("m4a") << QStringLiteral("test.m4a") ; QTest::addRow("mpc") << QStringLiteral("test.mpc") ; QTest::addRow("speex") << QStringLiteral("test.spx") ; QTest::addRow("wav") << QStringLiteral("test.wav") ; QTest::addRow("wavpack") << QStringLiteral("test.wv") ; QTest::addRow("wma") << QStringLiteral("test.wma") ; } +void EmbeddedImageDataTest::testWrite() +{ + QFETCH(QString, fileName); + EmbeddedImageData imageData; + + QString testFileName = testFilePath(QStringLiteral("writer") + fileName); + + QFile::copy(testFilePath(fileName), testFileName); + + QFile testFile(testFilePath("test.jpg")); + testFile.open(QIODevice::ReadOnly); + + QMap writeImages; + QMap readImages; + + writeImages.insert(EmbeddedImageData::ImageType::FrontCover, testFile.readAll()); + imageData.writeImageData(testFileName, writeImages); + readImages = imageData.imageData(testFileName); + + QCOMPARE(readImages.value(EmbeddedImageData::FrontCover), writeImages.value(EmbeddedImageData::FrontCover)); + + QFile::remove(testFileName); +} + +void EmbeddedImageDataTest::testWrite_data() +{ + QTest::addColumn("fileName"); + + QTest::addRow("aiff") + << QStringLiteral("test.aif") + ; + + QTest::addRow("ape") + << QStringLiteral("test.ape") + ; + + QTest::addRow("opus") + << QStringLiteral("test.opus") + ; + + QTest::addRow("ogg") + << QStringLiteral("test.ogg") + ; + + QTest::addRow("flac") + << QStringLiteral("test.flac") + ; + + QTest::addRow("mp3") + << QStringLiteral("test.mp3") + ; + + QTest::addRow("m4a") + << QStringLiteral("test.m4a") + ; + + QTest::addRow("mpc") + << QStringLiteral("test.mpc") + ; + + QTest::addRow("speex") + << QStringLiteral("test.spx") + ; + + QTest::addRow("wav") + << QStringLiteral("test.wav") + ; + + QTest::addRow("wavpack") + << QStringLiteral("test.wv") + ; + + QTest::addRow("wma") + << QStringLiteral("test.wma") + ; +} + QTEST_GUILESS_MAIN(EmbeddedImageDataTest) diff --git a/autotests/embeddedimagedatatest.h b/autotests/embeddedimagedatatest.h index 0d39c59..3d4326b 100644 --- a/autotests/embeddedimagedatatest.h +++ b/autotests/embeddedimagedatatest.h @@ -1,39 +1,41 @@ /* * EmbeddedImageData tests. * * Copyright (C) 2018 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef EMBEDDEDIMAGEDATATEST_H #define EMBEDDEDIMAGEDATATEST_H #include #include "properties.h" class EmbeddedImageDataTest : public QObject { Q_OBJECT private: QString testFilePath(const QString& fileName) const; private Q_SLOTS: void test(); void test_data(); + void testWrite(); + void testWrite_data(); }; #endif // EMBEDDEDIMAGEDATATEST_H diff --git a/src/embeddedimagedata.cpp b/src/embeddedimagedata.cpp index 876e5b5..c9ade14 100644 --- a/src/embeddedimagedata.cpp +++ b/src/embeddedimagedata.cpp @@ -1,289 +1,520 @@ /* * Copyright (C) 2018 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "embeddedimagedata.h" #include "kfilemetadata_debug.h" // Taglib includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include using namespace KFileMetaData; class Q_DECL_HIDDEN EmbeddedImageData::Private { public: QMimeDatabase mMimeDatabase; QByteArray getFrontCover(const QString &fileUrl, const QString &mimeType) const; QByteArray getFrontCoverFromID3(TagLib::ID3v2::Tag* Id3Tags) const; QByteArray getFrontCoverFromFlacPicture(TagLib::List lstPic) const; QByteArray getFrontCoverFromMp4(TagLib::MP4::Tag* mp4Tags) const; QByteArray getFrontCoverFromApe(TagLib::APE::Tag* apeTags) const; QByteArray getFrontCoverFromAsf(TagLib::ASF::Tag* asfTags) const; + void writeFrontCover(const QString &fileUrl, const QString &mimeType, const QByteArray &pictureData); + void writeFrontCoverToID3(TagLib::ID3v2::Tag* Id3Tags, const QByteArray &pictureData); + void writeFrontCoverToFlacPicture(TagLib::List lstPic, const QByteArray &pictureData); + void writeFrontCoverToMp4(TagLib::MP4::Tag* mp4Tags, const QByteArray &pictureData); + void writeFrontCoverToApe(TagLib::APE::Tag* apeTags, const QByteArray &pictureData); + void writeFrontCoverToAsf(TagLib::ASF::Tag* asfTags, const QByteArray &pictureData); + TagLib::String determineMimeType(const QByteArray &pictureData); static const QStringList mMimetypes; }; const QStringList EmbeddedImageData::Private::mMimetypes = { QStringLiteral("audio/flac"), QStringLiteral("audio/mp4"), QStringLiteral("audio/mpeg"), QStringLiteral("audio/mpeg3"), QStringLiteral("audio/ogg"), QStringLiteral("audio/opus"), QStringLiteral("audio/speex"), QStringLiteral("audio/wav"), QStringLiteral("audio/x-aiff"), QStringLiteral("audio/x-ape"), QStringLiteral("audio/x-mpeg"), QStringLiteral("audio/x-ms-wma"), QStringLiteral("audio/x-musepack"), QStringLiteral("audio/x-opus+ogg"), QStringLiteral("audio/x-speex"), QStringLiteral("audio/x-vorbis+ogg"), QStringLiteral("audio/x-wav"), QStringLiteral("audio/x-wavpack"), }; EmbeddedImageData::EmbeddedImageData() : d(std::unique_ptr(new Private())) { } EmbeddedImageData::~EmbeddedImageData() = default; QStringList EmbeddedImageData::mimeTypes() const { return d->mMimetypes; } QMap EmbeddedImageData::imageData(const QString &fileUrl, const EmbeddedImageData::ImageTypes types) const { QMap imageData; - const auto &fileMimeType = d->mMimeDatabase.mimeTypeForFile(fileUrl); + const auto fileMimeType = d->mMimeDatabase.mimeTypeForFile(fileUrl); if (fileMimeType.name().startsWith(QLatin1String("audio/"))) { if (types & EmbeddedImageData::FrontCover) { imageData.insert(EmbeddedImageData::FrontCover, d->getFrontCover(fileUrl, fileMimeType.name())); } } return imageData; } +void +EmbeddedImageData::writeImageData(const QString &fileUrl, + QMap &imageData) +{ + const auto fileMimeType = d->mMimeDatabase.mimeTypeForFile(fileUrl); + QMap::iterator frontCover = imageData.find(EmbeddedImageData::FrontCover); + if (fileMimeType.name().startsWith(QLatin1String("audio/"))) { + if (frontCover != imageData.end()) { + d->writeFrontCover(fileUrl, fileMimeType.name(), frontCover.value()); + } + } +} + QByteArray EmbeddedImageData::Private::getFrontCover(const QString &fileUrl, const QString &mimeType) const { TagLib::FileStream stream(fileUrl.toUtf8().constData(), true); if (!stream.isOpen()) { qCWarning(KFILEMETADATA_LOG) << "Unable to open file readonly: " << fileUrl; return QByteArray(); } if ((mimeType == QLatin1String("audio/mpeg")) || (mimeType == QLatin1String("audio/mpeg3")) || (mimeType == QLatin1String("audio/x-mpeg"))) { // Handling multiple tags in mpeg files. TagLib::MPEG::File mpegFile(&stream, TagLib::ID3v2::FrameFactory::instance(), true); if (mpegFile.ID3v2Tag()) { return getFrontCoverFromID3(mpegFile.ID3v2Tag()); } } else if (mimeType == QLatin1String("audio/x-aiff")) { TagLib::RIFF::AIFF::File aiffFile(&stream, true); if (aiffFile.hasID3v2Tag()) { return getFrontCoverFromID3(aiffFile.tag()); } } else if ((mimeType == QLatin1String("audio/wav")) || (mimeType == QLatin1String("audio/x-wav"))) { TagLib::RIFF::WAV::File wavFile(&stream, true); if (wavFile.hasID3v2Tag()) { return getFrontCoverFromID3(wavFile.ID3v2Tag()); } } else if (mimeType == QLatin1String("audio/mp4")) { TagLib::MP4::File mp4File(&stream, true); if (mp4File.tag()) { return getFrontCoverFromMp4(mp4File.tag()); } } else if (mimeType == QLatin1String("audio/x-musepack")) { TagLib::MPC::File mpcFile(&stream, true); if (mpcFile.APETag()) { return getFrontCoverFromApe(mpcFile.APETag()); } } else if (mimeType == QLatin1String("audio/x-ape")) { TagLib::APE::File apeFile(&stream, true); if (apeFile.hasAPETag()) { return getFrontCoverFromApe(apeFile.APETag()); } } else if (mimeType == QLatin1String("audio/x-wavpack")) { TagLib::WavPack::File wavpackFile(&stream, true); if (wavpackFile.hasAPETag()) { return getFrontCoverFromApe(wavpackFile.APETag()); } } else if (mimeType == QLatin1String("audio/x-ms-wma")) { TagLib::ASF::File asfFile(&stream, true); TagLib::ASF::Tag* asfTags = dynamic_cast(asfFile.tag()); if (asfTags) { return getFrontCoverFromAsf(asfTags); } } else if (mimeType == QLatin1String("audio/flac")) { TagLib::FLAC::File flacFile(&stream, TagLib::ID3v2::FrameFactory::instance(), true); return getFrontCoverFromFlacPicture(flacFile.pictureList()); } else if ((mimeType == QLatin1String("audio/ogg")) || (mimeType == QLatin1String("audio/x-vorbis+ogg"))) { TagLib::Ogg::Vorbis::File oggFile(&stream, true); if (oggFile.tag()) { return getFrontCoverFromFlacPicture(oggFile.tag()->pictureList()); } } else if ((mimeType == QLatin1String("audio/opus")) || (mimeType == QLatin1String("audio/x-opus+ogg"))) { TagLib::Ogg::Opus::File opusFile(&stream, true); if (opusFile.tag()) { return getFrontCoverFromFlacPicture(opusFile.tag()->pictureList()); } } else if (mimeType == QLatin1String("audio/speex") || mimeType == QLatin1String("audio/x-speex")) { TagLib::Ogg::Speex::File speexFile(&stream, true); if (speexFile.tag()) { return getFrontCoverFromFlacPicture(speexFile.tag()->pictureList()); } } return QByteArray(); } +void +EmbeddedImageData::Private::writeFrontCover(const QString &fileUrl, + const QString &mimeType, const QByteArray &pictureData) +{ + TagLib::FileStream stream(fileUrl.toUtf8().constData(), false); + if (!stream.isOpen()) { + qWarning() << "Unable to open file: " << fileUrl; + return; + } + + if ((mimeType == QLatin1String("audio/mpeg")) + || (mimeType == QLatin1String("audio/mpeg3")) + || (mimeType == QLatin1String("audio/x-mpeg"))) { + + // Handling multiple tags in mpeg files. + TagLib::MPEG::File mpegFile(&stream, TagLib::ID3v2::FrameFactory::instance(), false); + if (mpegFile.ID3v2Tag()) { + writeFrontCoverToID3(mpegFile.ID3v2Tag(), pictureData); + } + mpegFile.save(); + } else if (mimeType == QLatin1String("audio/x-aiff")) { + + TagLib::RIFF::AIFF::File aiffFile(&stream, false); + if (aiffFile.hasID3v2Tag()) { + writeFrontCoverToID3(aiffFile.tag(), pictureData); + } + aiffFile.save(); + } else if ((mimeType == QLatin1String("audio/wav")) + || (mimeType == QLatin1String("audio/x-wav"))) { + + TagLib::RIFF::WAV::File wavFile(&stream, false); + if (wavFile.hasID3v2Tag()) { + writeFrontCoverToID3(wavFile.ID3v2Tag(), pictureData); + } + wavFile.save(); + } else if (mimeType == QLatin1String("audio/mp4")) { + + TagLib::MP4::File mp4File(&stream, false); + if (mp4File.tag()) { + writeFrontCoverToMp4(mp4File.tag(), pictureData); + } + mp4File.save(); + } else if (mimeType == QLatin1String("audio/x-musepack")) { + + TagLib::MPC::File mpcFile(&stream, false); + if (mpcFile.APETag()) { + writeFrontCoverToApe(mpcFile.APETag(), pictureData); + } + mpcFile.save(); + } else if (mimeType == QLatin1String("audio/x-ape")) { + + TagLib::APE::File apeFile(&stream, false); + if (apeFile.hasAPETag()) { + writeFrontCoverToApe(apeFile.APETag(), pictureData); + } + apeFile.save(); + } else if (mimeType == QLatin1String("audio/x-wavpack")) { + + TagLib::WavPack::File wavpackFile(&stream, false); + if (wavpackFile.hasAPETag()) { + writeFrontCoverToApe(wavpackFile.APETag(), pictureData); + } + wavpackFile.save(); + } else if (mimeType == QLatin1String("audio/x-ms-wma")) { + + TagLib::ASF::File asfFile(&stream, false); + TagLib::ASF::Tag* asfTags = dynamic_cast(asfFile.tag()); + if (asfTags) { + writeFrontCoverToAsf(asfTags, pictureData); + } + asfFile.save(); + } else if (mimeType == QLatin1String("audio/flac")) { + + TagLib::FLAC::File flacFile(&stream, TagLib::ID3v2::FrameFactory::instance(), false); + writeFrontCoverToFlacPicture(flacFile.pictureList(), pictureData); + flacFile.save(); + } else if ((mimeType == QLatin1String("audio/ogg")) + || (mimeType == QLatin1String("audio/x-vorbis+ogg"))) { + + TagLib::Ogg::Vorbis::File oggFile(&stream, false); + if (oggFile.tag()) { + writeFrontCoverToFlacPicture(oggFile.tag()->pictureList(), pictureData); + } + oggFile.save(); + } + else if ((mimeType == QLatin1String("audio/opus")) + || (mimeType == QLatin1String("audio/x-opus+ogg"))) { + + TagLib::Ogg::Opus::File opusFile(&stream, false); + if (opusFile.tag()) { + writeFrontCoverToFlacPicture(opusFile.tag()->pictureList(), pictureData); + } + opusFile.save(); + } else if (mimeType == QLatin1String("audio/speex") || mimeType == QLatin1String("audio/x-speex")) { + + TagLib::Ogg::Speex::File speexFile(&stream, false); + if (speexFile.tag()) { + writeFrontCoverToFlacPicture(speexFile.tag()->pictureList(), pictureData); + } + speexFile.save(); + } +} + QByteArray EmbeddedImageData::Private::getFrontCoverFromID3(TagLib::ID3v2::Tag* Id3Tags) const { TagLib::ID3v2::FrameList lstID3v2; // Attached Front Picture. lstID3v2 = Id3Tags->frameListMap()["APIC"]; for (const auto& frame : qAsConst(lstID3v2)) { const auto *frontCoverFrame = static_cast(frame); if (frontCoverFrame->type() == frontCoverFrame->FrontCover) { return QByteArray(frontCoverFrame->picture().data(), frontCoverFrame->picture().size()); } } return QByteArray(); } +void +EmbeddedImageData::Private::writeFrontCoverToID3(TagLib::ID3v2::Tag* Id3Tags, const QByteArray &pictureData) +{ + // Try to update existing front cover frame first + TagLib::ID3v2::FrameList lstID3v2; + lstID3v2 = Id3Tags->frameListMap()["APIC"]; + for (auto& frame : qAsConst(lstID3v2)) + { + auto *frontCoverFrame = static_cast(frame); + if (frontCoverFrame->type() == frontCoverFrame->FrontCover) { + frontCoverFrame->setPicture(TagLib::ByteVector(static_cast(pictureData.data()), pictureData.size())); + frontCoverFrame->setMimeType(determineMimeType(pictureData)); + return; + } + } + auto pictureFrame = new TagLib::ID3v2::AttachedPictureFrame; + pictureFrame->setPicture(TagLib::ByteVector(pictureData.data(), pictureData.size())); + pictureFrame->setType(pictureFrame->FrontCover); + pictureFrame->setMimeType(determineMimeType(pictureData)); + Id3Tags->addFrame(pictureFrame); +} + QByteArray EmbeddedImageData::Private::getFrontCoverFromFlacPicture(TagLib::List lstPic) const { for (const auto &picture : qAsConst(lstPic)) { if (picture->type() == picture->FrontCover) { return QByteArray(picture->data().data(), picture->data().size()); } } return QByteArray(); } +void +EmbeddedImageData::Private::writeFrontCoverToFlacPicture(TagLib::List lstPic, const QByteArray &pictureData) +{ + for (const auto &picture : qAsConst(lstPic)) { + if (picture->type() == picture->FrontCover) { + picture->setData(TagLib::ByteVector(pictureData.data(), pictureData.size())); + picture->setMimeType(determineMimeType(pictureData)); + return ; + } + } + auto flacPicture = new TagLib::FLAC::Picture; + flacPicture->setMimeType(determineMimeType(pictureData)); + flacPicture->setData(TagLib::ByteVector(pictureData.data(), pictureData.size())); + lstPic.append(flacPicture); +} + QByteArray EmbeddedImageData::Private::getFrontCoverFromMp4(TagLib::MP4::Tag* mp4Tags) const { TagLib::MP4::Item coverArtItem = mp4Tags->item("covr"); if (coverArtItem.isValid()) { TagLib::MP4::CoverArtList coverArtList = coverArtItem.toCoverArtList(); TagLib::MP4::CoverArt& frontCover = coverArtList.front(); return QByteArray(frontCover.data().data(), frontCover.data().size()); } return QByteArray(); } +void +EmbeddedImageData::Private::writeFrontCoverToMp4(TagLib::MP4::Tag* mp4Tags, const QByteArray &pictureData) +{ + TagLib::MP4::Item coverArtItem = mp4Tags->item("covr"); + TagLib::MP4::CoverArtList coverArtList; + TagLib::MP4::CoverArt coverArt(TagLib::MP4::CoverArt::Format::Unknown, TagLib::ByteVector(pictureData.data(), pictureData.size())); + if (coverArtItem.isValid()) + { + coverArtList = coverArtItem.toCoverArtList(); + coverArtList.clear(); + } + coverArtList.append(coverArt); + mp4Tags->setItem("covr", coverArtList); +} + QByteArray EmbeddedImageData::Private::getFrontCoverFromApe(TagLib::APE::Tag* apeTags) const { TagLib::APE::ItemListMap lstApe = apeTags->itemListMap(); TagLib::APE::ItemListMap::ConstIterator itApe; /* The cover art tag for APEv2 tags starts with the filename as string * with zero termination followed by the actual picture data */ itApe = lstApe.find("COVER ART (FRONT)"); if (itApe != lstApe.end()) { TagLib::ByteVector pictureData = (*itApe).second.binaryData(); int position = pictureData.find('\0'); if (position >= 0) { position += 1; return QByteArray(pictureData.data() + position, pictureData.size() - position); } } return QByteArray(); } + +void +EmbeddedImageData::Private::writeFrontCoverToApe(TagLib::APE::Tag* apeTags, const QByteArray &pictureData) +{ + /* The cover art tag for APEv2 tags starts with the filename as string + * with zero termination followed by the actual picture data */ + TagLib::ByteVector imageData; + TagLib::String name; + if (determineMimeType(pictureData) == TagLib::String("image/png")) { + name = "frontCover.png"; + } else { + name = "frontCover.jpeg"; + } + imageData.append(name.data(TagLib::String::UTF8)); + imageData.append('\0'); + imageData.append(TagLib::ByteVector(pictureData.constData(), pictureData.size())); + apeTags->setData("COVER ART (FRONT)", imageData); +} + QByteArray EmbeddedImageData::Private::getFrontCoverFromAsf(TagLib::ASF::Tag* asfTags) const { TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/Picture"); for (const auto& attribute : qAsConst(lstASF)) { TagLib::ASF::Picture picture = attribute.toPicture(); if (picture.type() == TagLib::ASF::Picture::FrontCover) { TagLib::ByteVector pictureData = picture.picture(); return QByteArray(pictureData.data(), pictureData.size()); } } return QByteArray(); } + +void +EmbeddedImageData::Private::writeFrontCoverToAsf(TagLib::ASF::Tag* asfTags, const QByteArray &pictureData) +{ + TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/Picture"); + for (const auto& attribute : qAsConst(lstASF)) { + TagLib::ASF::Picture picture = attribute.toPicture(); + if (picture.type() == TagLib::ASF::Picture::FrontCover) { + picture.setPicture(TagLib::ByteVector(pictureData.constData(), pictureData.size())); + picture.setMimeType(determineMimeType(pictureData)); + TagLib::ByteVector pictureData = picture.picture(); + return; + } + } + TagLib::ASF::Picture picture; + picture.setPicture(TagLib::ByteVector(pictureData.constData(), pictureData.size())); + picture.setType(TagLib::ASF::Picture::FrontCover); + lstASF.append(picture); +} + + +TagLib::String EmbeddedImageData::Private::determineMimeType(const QByteArray &pictureData) +{ + if (pictureData.startsWith(QByteArray::fromHex("89504E470D0A1A0A"))) { + return TagLib::String("image/png"); + } else if (pictureData.startsWith(QByteArray::fromHex("FFD8FFDB")) || + pictureData.startsWith(QByteArray::fromHex("FFD8FFE000104A4649460001")) || + pictureData.startsWith(QByteArray::fromHex("FFD8FFEE")) || + pictureData.startsWith(QByteArray::fromHex("FFD8FFE1"))) { + return TagLib::String("image/jpeg"); + } else { + return TagLib::String(); + } +} + diff --git a/src/embeddedimagedata.h b/src/embeddedimagedata.h index 5bdc992..ca85904 100644 --- a/src/embeddedimagedata.h +++ b/src/embeddedimagedata.h @@ -1,72 +1,81 @@ /* * EmbeddedImageData extracts binary data of cover art files. * Copyright (C) 2018 Alexander Stippich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef KFILEMETADATA_EMBEDDEDIMAGEDATA_H #define KFILEMETADATA_EMBEDDEDIMAGEDATA_H #include "kfilemetadata_export.h" #include +#include #include #include namespace KFileMetaData { /** * \class EmbeddedImageData embeddedimagedata.h * - * \brief The EmbeddedImageData is a class which extracts the images stored - * in the metadata tags of files as byte arrays. For example, the front cover - * art of music albums may be extracted with this class. The byte array will - * mostly contain jpeg or png files, which can be loaded into e.g. QImage. + * \brief The EmbeddedImageData is a class which extracts and writes the images + * stored in the metadata tags of files as byte arrays. For example, the front + * cover art of music albums may be extracted with this class. The byte array + * will mostly contain jpeg or png files, which can be loaded into e.g. QImage. * * \author Alexander Stippich */ class KFILEMETADATA_EXPORT EmbeddedImageData { public: EmbeddedImageData(); virtual ~EmbeddedImageData(); enum ImageType { FrontCover = 0x01, AllImages = FrontCover }; Q_DECLARE_FLAGS(ImageTypes, ImageType) /** * Extracts the images stored in the metadata tags from a file. * By default, the front cover is extracted. */ QMap imageData(const QString &fileUrl, const EmbeddedImageData::ImageTypes types = FrontCover) const; /** * Provides a list of mimetypes which are supported for reading * of embedded images in e.g. audio files. * @since 5.52 */ QStringList mimeTypes() const; + /** + * Write the images to the metadata tags in a file. + * Currently, only the front cover is supported. + * Mimetype is only correctly set for jpeg and png files. + * @since 5.62 + */ + void writeImageData(const QString &fileUrl, QMap &imageData); + private: class Private; std::unique_ptr d; EmbeddedImageData& operator=(const EmbeddedImageData&); }; } #endif // KFILEMETADATA_EMBEDDEDIMAGEDATA_H