diff --git a/autotests/taglibwritertest.cpp b/autotests/taglibwritertest.cpp --- a/autotests/taglibwritertest.cpp +++ b/autotests/taglibwritertest.cpp @@ -80,9 +80,15 @@ data.add(Property::Comment, QString(QStringLiteral("Comment1") + stringSuffix)); data.add(Property::Copyright, QString(QStringLiteral("Copyright1") + stringSuffix)); + QFile testFile(testFilePath("test.jpg")); + testFile.open(QIODevice::ReadOnly); + const auto pictureData = testFile.readAll(); + + data.add(Property::FrontCover, pictureData); + writerPlugin.write(data); - KFileMetaData::SimpleExtractionResult result(temporaryFileName, mimeType, KFileMetaData::ExtractionResult::ExtractMetaData); + KFileMetaData::SimpleExtractionResult result(temporaryFileName, mimeType, KFileMetaData::ExtractionResult::ExtractEverythingIncludingBinary); extractResult(mimeType, result); QCOMPARE(result.properties().value(Property::Title), QVariant(QStringLiteral("Title1") + stringSuffix)); @@ -96,6 +102,7 @@ QCOMPARE(result.properties().value(Property::Genre), QVariant(QStringLiteral("Genre1") + stringSuffix)); QCOMPARE(result.properties().value(Property::Comment), QVariant(QStringLiteral("Comment1") + stringSuffix)); QCOMPARE(result.properties().value(Property::Copyright), QVariant(QStringLiteral("Copyright1") + stringSuffix)); + QCOMPARE(result.properties().value(Property::FrontCover), pictureData); QFile::remove(temporaryFileName); } diff --git a/src/writers/taglibwriter.cpp b/src/writers/taglibwriter.cpp --- a/src/writers/taglibwriter.cpp +++ b/src/writers/taglibwriter.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,21 @@ using namespace KFileMetaData; +TagLib::String determineMimeTypeOfPicture(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(); + } +} + + void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags, const PropertyMap &newProperties) { if (newProperties.contains(Property::Rating)) { @@ -86,6 +102,26 @@ id3Tags->addFrame(ratingFrame); } } + if (newProperties.contains(Property::FrontCover)) { + // Try to update existing front cover frame first + TagLib::ID3v2::FrameList lstID3v2; + lstID3v2 = id3Tags->frameListMap()["APIC"]; + const auto pictureData = newProperties.value(Property::FrontCover).toByteArray(); + 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(determineMimeTypeOfPicture(pictureData)); + return; + } + } + auto pictureFrame = new TagLib::ID3v2::AttachedPictureFrame; + pictureFrame->setPicture(TagLib::ByteVector(pictureData.data(), pictureData.size())); + pictureFrame->setType(pictureFrame->FrontCover); + pictureFrame->setMimeType(determineMimeTypeOfPicture(pictureData)); + id3Tags->addFrame(pictureFrame); + } } void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties) @@ -119,13 +155,79 @@ } asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating)); } + if (properties.contains(Property::FrontCover)) { + TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/Picture"); + const auto pictureData = properties.value(Property::FrontCover).toByteArray(); + 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(determineMimeTypeOfPicture(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); + } } void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMap &newProperties) { if (newProperties.contains(Property::Rating)) { mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10))); } + if (newProperties.contains(Property::FrontCover)) { + TagLib::MP4::Item coverArtItem = mp4Tags->item("covr"); + TagLib::MP4::CoverArtList coverArtList; + const auto pictureData = newProperties.value(Property::FrontCover).toByteArray(); + 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); + } +} + +void writeFlacPicture(TagLib::List lstPic, const PropertyMap &newProperties) { + if (newProperties.contains(Property::FrontCover)) { + const auto pictureData = newProperties.value(Property::FrontCover).toByteArray(); + for (const auto &picture : qAsConst(lstPic)) { + if (picture->type() == picture->FrontCover) { + picture->setData(TagLib::ByteVector(pictureData.data(), pictureData.size())); + picture->setMimeType(determineMimeTypeOfPicture(pictureData)); + return ; + } + } + auto flacPicture = new TagLib::FLAC::Picture; + flacPicture->setMimeType(determineMimeTypeOfPicture(pictureData)); + flacPicture->setData(TagLib::ByteVector(pictureData.data(), pictureData.size())); + lstPic.append(flacPicture); + } +} + +void writeApePicture(TagLib::APE::Tag* apeTags, const PropertyMap &newProperties) { + if (newProperties.contains(Property::FrontCover)) { + /* 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; + const auto pictureData = newProperties.value(Property::FrontCover).toByteArray(); + if (determineMimeTypeOfPicture(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); + } } } // anonymous namespace @@ -269,6 +371,9 @@ auto savedProperties = file.properties(); writeGenericProperties(savedProperties, properties); writeApeTags(savedProperties, properties); + if (file.hasAPETag()) { + writeApePicture(file.APETag(), properties); + } file.setProperties(savedProperties); file.save(); } @@ -278,6 +383,9 @@ auto savedProperties = file.properties(); writeGenericProperties(savedProperties, properties); writeApeTags(savedProperties, properties); + if (file.hasAPETag()) { + writeApePicture(file.APETag(), properties); + } file.setProperties(savedProperties); file.save(); } @@ -287,6 +395,9 @@ auto savedProperties = file.properties(); writeGenericProperties(savedProperties, properties); writeApeTags(savedProperties, properties); + if (file.hasAPETag()) { + writeApePicture(file.APETag(), properties); + } file.setProperties(savedProperties); file.save(); } @@ -308,6 +419,7 @@ auto savedProperties = file.properties(); writeGenericProperties(savedProperties, properties); writeVorbisTags(savedProperties, properties); + writeFlacPicture(file.pictureList(), properties); file.setProperties(savedProperties); file.save(); } @@ -317,6 +429,9 @@ auto savedProperties = file.properties(); writeGenericProperties(savedProperties, properties); writeVorbisTags(savedProperties, properties); + if (file.tag()) { + writeFlacPicture(file.tag()->pictureList(), properties); + } file.setProperties(savedProperties); file.save(); } @@ -326,6 +441,9 @@ auto savedProperties = file.properties(); writeGenericProperties(savedProperties, properties); writeVorbisTags(savedProperties, properties); + if (file.tag()) { + writeFlacPicture(file.tag()->pictureList(), properties); + } file.setProperties(savedProperties); file.save(); } @@ -335,6 +453,9 @@ auto savedProperties = file.properties(); writeGenericProperties(savedProperties, properties); writeVorbisTags(savedProperties, properties); + if (file.tag()) { + writeFlacPicture(file.tag()->pictureList(), properties); + } file.setProperties(savedProperties); file.save(); }