File indexing completed on 2024-05-19 03:56:46

0001 /*
0002     SPDX-FileCopyrightText: 2016 Varun Joshi <varunj.1011@gmail.com>
0003     SPDX-FileCopyrightText: 2018 Alexander Stippich <a.stippich@gmx.net>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "taglibwriter.h"
0009 #include "embeddedimagedata.h"
0010 #include "kfilemetadata_debug.h"
0011 
0012 #include <array>
0013 
0014 #include <taglib.h>
0015 #include <tfilestream.h>
0016 #include <tpropertymap.h>
0017 #include <tstring.h>
0018 #include <aifffile.h>
0019 #include <apefile.h>
0020 #include <apetag.h>
0021 #include <asffile.h>
0022 #include <asftag.h>
0023 #include <flacfile.h>
0024 #include <mp4file.h>
0025 #include <mp4tag.h>
0026 #include <mpcfile.h>
0027 #include <mpegfile.h>
0028 #include <id3v2tag.h>
0029 #include <oggfile.h>
0030 #include <opusfile.h>
0031 #include <vorbisfile.h>
0032 #include <speexfile.h>
0033 #include <wavpackfile.h>
0034 #include <wavfile.h>
0035 #include <popularimeterframe.h>
0036 #include <attachedpictureframe.h>
0037 
0038 namespace {
0039 
0040 const QStringList supportedMimeTypes = {
0041     QStringLiteral("audio/flac"),
0042     QStringLiteral("audio/mp4"),
0043     QStringLiteral("audio/mpeg"),
0044     QStringLiteral("audio/mpeg3"),
0045     QStringLiteral("audio/ogg"),
0046     QStringLiteral("audio/opus"),
0047     QStringLiteral("audio/wav"),
0048     QStringLiteral("audio/vnd.wave"),
0049     QStringLiteral("audio/x-aiff"),
0050     QStringLiteral("audio/x-aifc"),
0051     QStringLiteral("audio/x-ape"),
0052     QStringLiteral("audio/x-mpeg"),
0053     QStringLiteral("audio/x-ms-wma"),
0054     QStringLiteral("audio/x-musepack"),
0055     QStringLiteral("audio/x-opus+ogg"),
0056     QStringLiteral("audio/x-speex+ogg"),
0057     QStringLiteral("audio/x-vorbis+ogg"),
0058     QStringLiteral("audio/x-wav"),
0059     QStringLiteral("audio/x-wavpack"),
0060 };
0061 
0062 int id3v2RatingTranslation[11] = {
0063     0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255
0064 };
0065 
0066 using namespace KFileMetaData;
0067 
0068 template<typename ImageType>
0069 EmbeddedImageData::ImageType mapTaglibType(const ImageType type)
0070 {
0071     switch (type) {
0072         case ImageType::FrontCover:
0073              return EmbeddedImageData::FrontCover;
0074          case ImageType::Other:
0075              return EmbeddedImageData::Other;
0076          case ImageType::FileIcon:
0077              return EmbeddedImageData::FileIcon;
0078          case ImageType::OtherFileIcon:
0079              return EmbeddedImageData::OtherFileIcon;
0080          case ImageType::BackCover:
0081              return EmbeddedImageData::BackCover;
0082          case ImageType::LeafletPage:
0083              return EmbeddedImageData::LeafletPage;
0084          case ImageType::Media:
0085              return EmbeddedImageData::Media;
0086          case ImageType::LeadArtist:
0087              return EmbeddedImageData::LeadArtist;
0088          case ImageType::Artist:
0089              return EmbeddedImageData::Artist;
0090          case ImageType::Conductor:
0091              return EmbeddedImageData::Conductor;
0092          case ImageType::Band:
0093              return EmbeddedImageData::Band;
0094          case ImageType::Composer:
0095              return EmbeddedImageData::Composer;
0096          case ImageType::Lyricist:
0097              return EmbeddedImageData::Lyricist;
0098          case ImageType::RecordingLocation:
0099              return EmbeddedImageData::RecordingLocation;
0100          case ImageType::DuringRecording:
0101              return EmbeddedImageData::DuringRecording;
0102          case ImageType::DuringPerformance:
0103              return EmbeddedImageData::DuringPerformance;
0104          case ImageType::MovieScreenCapture:
0105              return EmbeddedImageData::MovieScreenCapture;
0106          case ImageType::ColouredFish:
0107              return EmbeddedImageData::ColouredFish;
0108          case ImageType::Illustration:
0109              return EmbeddedImageData::Illustration;
0110          case ImageType::BandLogo:
0111              return EmbeddedImageData::BandLogo;
0112          case ImageType::PublisherLogo:
0113              return EmbeddedImageData::PublisherLogo;
0114          default:
0115              return EmbeddedImageData::Unknown;
0116     }
0117 }
0118 
0119 template<typename ImageType>
0120 static const std::array<typename ImageType::Type, 21> allImageTypes = {
0121     ImageType::FrontCover,
0122     ImageType::Other,
0123     ImageType::FileIcon,
0124     ImageType::OtherFileIcon,
0125     ImageType::BackCover,
0126     ImageType::LeafletPage,
0127     ImageType::Media,
0128     ImageType::LeadArtist,
0129     ImageType::Artist,
0130     ImageType::Conductor,
0131     ImageType::Band,
0132     ImageType::Composer,
0133     ImageType::Lyricist,
0134     ImageType::RecordingLocation,
0135     ImageType::DuringRecording,
0136     ImageType::DuringPerformance,
0137     ImageType::MovieScreenCapture,
0138     ImageType::ColouredFish,
0139     ImageType::Illustration,
0140     ImageType::BandLogo,
0141     ImageType::PublisherLogo,
0142 };
0143 
0144 TagLib::String determineMimeType(const QByteArray &pictureData)
0145 {
0146     if (pictureData.startsWith(QByteArray::fromHex("89504E470D0A1A0A"))) {
0147         return TagLib::String("image/png");
0148     } else if (pictureData.startsWith(QByteArray::fromHex("FFD8FFDB")) ||
0149                pictureData.startsWith(QByteArray::fromHex("FFD8FFE000104A4649460001")) ||
0150                pictureData.startsWith(QByteArray::fromHex("FFD8FFEE")) ||
0151                pictureData.startsWith(QByteArray::fromHex("FFD8FFE1"))) {
0152         return TagLib::String("image/jpeg");
0153     } else {
0154         return TagLib::String();
0155     }
0156 }
0157 
0158 void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags, const PropertyMultiMap &newProperties)
0159 {
0160     if (newProperties.contains(Property::Rating)) {
0161         int rating = newProperties.value(Property::Rating).toInt();
0162         if (rating >= 0 && rating <= 10) {
0163             id3Tags->removeFrames("POPM");
0164             // ID3v2::Tag::addFrame takes ownership
0165             auto ratingFrame = new TagLib::ID3v2::PopularimeterFrame;
0166             ratingFrame->setEmail("org.kde.kfilemetadata");
0167             ratingFrame->setRating(id3v2RatingTranslation[rating]);
0168             id3Tags->addFrame(ratingFrame);
0169         }
0170     }
0171 }
0172 
0173 void writeID3v2Cover(TagLib::ID3v2::Tag *id3Tags,
0174                      const QMap<EmbeddedImageData::ImageType, QByteArray> images)
0175 {
0176     EmbeddedImageData::ImageTypes wantedTypes;
0177     EmbeddedImageData::ImageTypes removeTypes;
0178     std::for_each(images.keyValueBegin(),images.keyValueEnd(),
0179         [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
0180             if (it.second.isEmpty()) {
0181                 removeTypes |= it.first;
0182             } else {
0183                 wantedTypes |= it.first;
0184             }
0185         });
0186 
0187     using PictureFrame = TagLib::ID3v2::AttachedPictureFrame;
0188     auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
0189         wantedTypes &= ~kfmType;
0190         auto newCover = images[kfmType];
0191         auto newMimeType = determineMimeType(newCover);
0192         if (!newMimeType.isEmpty()) {
0193             coverFrame->setPicture(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
0194             coverFrame->setMimeType(newMimeType);
0195         }
0196     };
0197 
0198     // Update existing covers
0199     TagLib::ID3v2::FrameList lstID3v2 = id3Tags->frameListMap()["APIC"];
0200     for (auto& frame : std::as_const(lstID3v2)) {
0201         auto* coverFrame = static_cast<PictureFrame *>(frame);
0202         const auto kfmType = mapTaglibType<PictureFrame::Type>(coverFrame->type());
0203         if (kfmType & wantedTypes) {
0204             updateFrame(coverFrame, kfmType);
0205         } else if (kfmType & removeTypes) {
0206             id3Tags->removeFrame(coverFrame);
0207         }
0208     }
0209     // Add new covers
0210     for (const auto type : allImageTypes<PictureFrame>) {
0211         const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
0212         if (kfmType & wantedTypes) {
0213             // ID3v2::Tag::addFrame takes ownership
0214             auto* coverFrame = new PictureFrame;
0215             coverFrame->setType(type);
0216             updateFrame(coverFrame, kfmType);
0217             id3Tags->addFrame(coverFrame);
0218         }
0219     }
0220 }
0221 
0222 // Instantiated for either FLAC::File or
0223 // Ogg::XiphComment (Ogg::*::File::tag())
0224 template<typename Container>
0225 void writeFlacCover(Container* tags,
0226                     const QMap<EmbeddedImageData::ImageType, QByteArray> images)
0227 {
0228     EmbeddedImageData::ImageTypes wantedTypes;
0229     EmbeddedImageData::ImageTypes removeTypes;
0230     std::for_each(images.keyValueBegin(),images.keyValueEnd(),
0231         [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
0232             if (it.second.isEmpty()) {
0233                 removeTypes |= it.first;
0234             } else {
0235                 wantedTypes |= it.first;
0236             }
0237         });
0238 
0239     using PictureFrame = TagLib::FLAC::Picture;
0240     auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
0241         wantedTypes &= ~kfmType;
0242         auto newCover = images[kfmType];
0243         auto newMimeType = determineMimeType(newCover);
0244         if (!newMimeType.isEmpty()) {
0245             coverFrame->setData(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
0246             coverFrame->setMimeType(newMimeType);
0247         }
0248     };
0249 
0250     // Update existing covers
0251     auto lstPic = tags->pictureList();
0252     for (auto it = lstPic.begin(); it != lstPic.end(); ++it) {
0253         const auto kfmType = mapTaglibType<PictureFrame::Type>((*it)->type());
0254         if (kfmType & wantedTypes) {
0255             updateFrame((*it), kfmType);
0256         } else if (kfmType & removeTypes) {
0257             tags->removePicture(*it);
0258         }
0259     }
0260     // Add new covers
0261     for (const auto type : allImageTypes<PictureFrame>) {
0262         const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
0263         if (kfmType & wantedTypes) {
0264             // FLAC::File::addPicture takes ownership (dito XiphComment)
0265             auto* coverFrame = new PictureFrame;
0266             coverFrame->setType(type);
0267             updateFrame(coverFrame, kfmType);
0268             tags->addPicture(coverFrame);
0269         }
0270     }
0271 }
0272 
0273 void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
0274 {
0275     if (newProperties.contains(Property::Rating)) {
0276         oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
0277     }
0278 }
0279 
0280 void writeApeCover(TagLib::APE::Tag* apeTags,
0281                    const QMap<EmbeddedImageData::ImageType, QByteArray> images)
0282 {
0283     if (images.empty()) {
0284         return;
0285     }
0286     auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
0287     if ((images.size() > 1) || (imageIt == images.constEnd())) {
0288         // TODO warn
0289     }
0290     if (imageIt == images.constEnd()) {
0291         return;
0292     }
0293 
0294     const auto newCover = *imageIt;
0295     if (newCover.isEmpty()) {
0296         apeTags->removeItem("COVER ART (FRONT)");
0297         return;
0298     }
0299 
0300     TagLib::ByteVector imageData;
0301     if (determineMimeType(newCover) == TagLib::String("image/png")) {
0302         imageData.setData("frontCover.png\0", 15);
0303     } else {
0304         imageData.setData("frontCover.jpeg\0", 16);
0305     }
0306     imageData.append(TagLib::ByteVector(newCover.constData(), newCover.size()));
0307     apeTags->setData("COVER ART (FRONT)", imageData);
0308 }
0309 
0310 void writeVorbisTags(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
0311 {
0312     if (newProperties.contains(Property::Rating)) {
0313         oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
0314     }
0315 }
0316 
0317 void writeAsfTags(TagLib::ASF::Tag *asfTags, const PropertyMultiMap &properties)
0318 {
0319     if (properties.contains(Property::Rating)) {
0320         //map the rating values of WMP to Baloo rating
0321         //0->0, 1->2, 4->25, 6->50, 8->75, 10->99
0322         int rating = properties.value(Property::Rating).toInt();
0323         if (rating == 0) {
0324             rating = 0;
0325         } else if (rating <= 2) {
0326             rating = 1;
0327         } else if (rating == 10){
0328             rating = 99;
0329         } else {
0330             rating = static_cast<int>(12.5 * rating - 25);
0331         }
0332         asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating));
0333     }
0334 }
0335 
0336 void writeAsfCover(TagLib::ASF::Tag* asfTags,
0337                    const QMap<EmbeddedImageData::ImageType, QByteArray> images)
0338 {
0339     EmbeddedImageData::ImageTypes wantedTypes;
0340     EmbeddedImageData::ImageTypes removeTypes;
0341     std::for_each(images.keyValueBegin(),images.keyValueEnd(),
0342         [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
0343             if (it.second.isEmpty()) {
0344                 removeTypes |= it.first;
0345             } else {
0346                 wantedTypes |= it.first;
0347             }
0348         });
0349 
0350     using PictureFrame = TagLib::ASF::Picture;
0351     auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
0352         wantedTypes &= ~kfmType;
0353         auto newCover = images[kfmType];
0354         auto newMimeType = determineMimeType(newCover);
0355         if (!newMimeType.isEmpty()) {
0356             coverFrame->setPicture(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
0357             coverFrame->setMimeType(newMimeType);
0358         }
0359     };
0360 
0361     // Update existing covers
0362     TagLib::ASF::AttributeList lstPic = asfTags->attribute("WM/Picture");
0363     for (auto it = lstPic.begin(); it != lstPic.end(); ) {
0364         PictureFrame picture = (*it).toPicture();
0365         const auto kfmType = mapTaglibType<PictureFrame::Type>(picture.type());
0366         if (kfmType & wantedTypes) {
0367             updateFrame(&picture, kfmType);
0368             ++it;
0369         } else if (kfmType & removeTypes) {
0370             it = lstPic.erase(it);
0371         } else {
0372             ++it;
0373         }
0374     }
0375     // Add new covers
0376     for (const auto type : allImageTypes<PictureFrame>) {
0377         const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
0378         if (kfmType & wantedTypes) {
0379             PictureFrame coverFrame;
0380             updateFrame(&coverFrame, kfmType);
0381             coverFrame.setType(type);
0382             lstPic.append(coverFrame);
0383         }
0384     }
0385     asfTags->setAttribute("WM/Picture", lstPic);
0386 }
0387 void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMultiMap &newProperties)
0388 {
0389     if (newProperties.contains(Property::Rating)) {
0390         mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10)));
0391     }
0392 }
0393 
0394 void writeMp4Cover(TagLib::MP4::Tag *mp4Tags,
0395                    const QMap<EmbeddedImageData::ImageType, QByteArray> images)
0396 {
0397     if (images.empty()) {
0398         return;
0399     }
0400     auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
0401     if ((images.size() > 1) || (imageIt == images.constEnd())) {
0402         // TODO warn
0403     }
0404     if (imageIt == images.constEnd()) {
0405         return;
0406     }
0407 
0408     TagLib::MP4::CoverArtList coverArtList;
0409     const auto newCover = *imageIt;
0410     if (!newCover.isEmpty()) {
0411         TagLib::ByteVector imageData(newCover.data(), newCover.size());
0412         TagLib::MP4::CoverArt coverArt(TagLib::MP4::CoverArt::Format::Unknown, imageData);
0413         coverArtList.append(coverArt);
0414     }
0415     mp4Tags->setItem("covr", coverArtList);
0416 }
0417 
0418 } // anonymous namespace
0419 
0420 void writeGenericProperties(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
0421 {
0422     if (newProperties.empty()) {
0423         return;
0424     }
0425 
0426     if (newProperties.contains(Property::Title)) {
0427         oldProperties.replace("TITLE", QStringToTString(newProperties.value(Property::Title).toString()));
0428     }
0429 
0430     if (newProperties.contains(Property::Artist)) {
0431         oldProperties.replace("ARTIST", QStringToTString(newProperties.value(Property::Artist).toString()));
0432     }
0433 
0434     if (newProperties.contains(Property::AlbumArtist)) {
0435         oldProperties.replace("ALBUMARTIST", QStringToTString(newProperties.value(Property::AlbumArtist).toString()));
0436     }
0437 
0438     if (newProperties.contains(Property::Album)) {
0439         oldProperties.replace("ALBUM", QStringToTString(newProperties.value(Property::Album).toString()));
0440     }
0441 
0442     if (newProperties.contains(Property::TrackNumber)) {
0443         int trackNumber = newProperties.value(Property::TrackNumber).toInt();
0444         //taglib requires uint
0445         if (trackNumber >= 0) {
0446             oldProperties.replace("TRACKNUMBER", QStringToTString(newProperties.value(Property::TrackNumber).toString()));
0447         }
0448     }
0449 
0450     if (newProperties.contains(Property::DiscNumber)) {
0451         int discNumber = newProperties.value(Property::DiscNumber).toInt();
0452         //taglib requires uint
0453         if (discNumber >= 0) {
0454             oldProperties.replace("DISCNUMBER", QStringToTString(newProperties.value(Property::DiscNumber).toString()));
0455         }
0456     }
0457 
0458     if (newProperties.contains(Property::ReleaseYear)) {
0459         int year = newProperties.value(Property::ReleaseYear).toInt();
0460         //taglib requires uint
0461         if (year >= 0) {
0462             oldProperties.replace("DATE", QStringToTString(newProperties.value(Property::ReleaseYear).toString()));
0463         }
0464     }
0465 
0466     if (newProperties.contains(Property::Genre)) {
0467         oldProperties.replace("GENRE", QStringToTString(newProperties.value(Property::Genre).toString()));
0468     }
0469 
0470     if (newProperties.contains(Property::Comment)) {
0471         oldProperties.replace("COMMENT", QStringToTString(newProperties.value(Property::Comment).toString()));
0472     }
0473 
0474     if (newProperties.contains(Property::Composer)) {
0475         oldProperties.replace("COMPOSER", QStringToTString(newProperties.value(Property::Composer).toString()));
0476     }
0477 
0478     if (newProperties.contains(Property::Lyricist)) {
0479         oldProperties.replace("LYRICIST", QStringToTString(newProperties.value(Property::Lyricist).toString()));
0480     }
0481 
0482     if (newProperties.contains(Property::Conductor)) {
0483         oldProperties.replace("CONDUCTOR", QStringToTString(newProperties.value(Property::Conductor).toString()));
0484     }
0485 
0486     if (newProperties.contains(Property::Copyright)) {
0487         oldProperties.replace("COPYRIGHT", QStringToTString(newProperties.value(Property::Copyright).toString()));
0488     }
0489 
0490     if (newProperties.contains(Property::Lyrics)) {
0491         oldProperties.replace("LYRICS", QStringToTString(newProperties.value(Property::Lyrics).toString()));
0492     }
0493 
0494     if (newProperties.contains(Property::Language)) {
0495         oldProperties.replace("LANGUAGE", QStringToTString(newProperties.value(Property::Language).toString()));
0496     }
0497 }
0498 
0499 TagLibWriter::TagLibWriter(QObject* parent)
0500     : WriterPlugin(parent)
0501 {
0502 }
0503 
0504 QStringList TagLibWriter::writeMimetypes() const
0505 {
0506     return supportedMimeTypes;
0507 }
0508 
0509 void TagLibWriter::write(const WriteData& data)
0510 {
0511     const QString fileUrl = data.inputUrl();
0512     const PropertyMultiMap properties = data.properties();
0513     const QString mimeType = data.inputMimetype();
0514 
0515 #if defined Q_OS_WINDOWS
0516     TagLib::FileStream stream(fileUrl.toLocal8Bit().constData(), false);
0517 #else
0518     TagLib::FileStream stream(fileUrl.toUtf8().constData(), false);
0519 #endif
0520     if (!stream.isOpen() || stream.readOnly()) {
0521         qCWarning(KFILEMETADATA_LOG) << "Unable to open file in write mode: " << fileUrl;
0522         return;
0523     }
0524 
0525     if (mimeType == QLatin1String("audio/mpeg") || mimeType == QLatin1String("audio/mpeg3")
0526             || mimeType == QLatin1String("audio/x-mpeg")) {
0527         TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false);
0528         if (file.isValid()) {
0529             auto savedProperties = file.properties();
0530             writeGenericProperties(savedProperties, properties);
0531             file.setProperties(savedProperties);
0532             if (file.hasID3v2Tag()) {
0533                 writeID3v2Tags(file.ID3v2Tag(), properties);
0534                 writeID3v2Cover(file.ID3v2Tag(), data.imageData());
0535             }
0536             file.save();
0537         }
0538     } else if (mimeType == QLatin1String("audio/x-aiff") || mimeType == QLatin1String("audio/x-aifc"))  {
0539         TagLib::RIFF::AIFF::File file(&stream, false);
0540         if (file.isValid()) {
0541             auto savedProperties = file.properties();
0542             writeGenericProperties(savedProperties, properties);
0543             file.setProperties(savedProperties);
0544             auto id3Tags = dynamic_cast<TagLib::ID3v2::Tag*>(file.tag());
0545             if (id3Tags) {
0546                 writeID3v2Tags(id3Tags, properties);
0547                 writeID3v2Cover(id3Tags, data.imageData());
0548             }
0549             file.save();
0550         }
0551     } else if (mimeType == QLatin1String("audio/wav") ||
0552                mimeType == QLatin1String("audio/vnd.wave") ||
0553                mimeType == QLatin1String("audio/x-wav")) {
0554         TagLib::RIFF::WAV::File file(&stream, false);
0555         if (file.isValid()) {
0556             auto savedProperties = file.properties();
0557             writeGenericProperties(savedProperties, properties);
0558             file.setProperties(savedProperties);
0559             auto id3Tags = file.ID3v2Tag();
0560             if (id3Tags) {
0561                 writeID3v2Tags(id3Tags, properties);
0562                 writeID3v2Cover(id3Tags, data.imageData());
0563             }
0564             file.save();
0565         }
0566     } else if (mimeType == QLatin1String("audio/x-musepack")) {
0567         TagLib::MPC::File file(&stream, false);
0568         if (file.isValid()) {
0569             auto savedProperties = file.properties();
0570             writeGenericProperties(savedProperties, properties);
0571             writeApeTags(savedProperties, properties);
0572             file.setProperties(savedProperties);
0573             if (file.hasAPETag()) {
0574                 writeApeCover(file.APETag(), data.imageData());
0575             }
0576             file.save();
0577         }
0578     } else if (mimeType == QLatin1String("audio/x-ape")) {
0579         TagLib::APE::File file(&stream, false);
0580         if (file.isValid()) {
0581             auto savedProperties = file.properties();
0582             writeGenericProperties(savedProperties, properties);
0583             writeApeTags(savedProperties, properties);
0584             file.setProperties(savedProperties);
0585             if (file.hasAPETag()) {
0586                 writeApeCover(file.APETag(), data.imageData());
0587             }
0588             file.save();
0589         }
0590     } else if (mimeType == QLatin1String("audio/x-wavpack")) {
0591         TagLib::WavPack::File file(&stream, false);
0592         if (file.isValid()) {
0593             auto savedProperties = file.properties();
0594             writeGenericProperties(savedProperties, properties);
0595             writeApeTags(savedProperties, properties);
0596             file.setProperties(savedProperties);
0597             if (file.hasAPETag()) {
0598                 writeApeCover(file.APETag(), data.imageData());
0599             }
0600             file.save();
0601         }
0602     } else if (mimeType == QLatin1String("audio/mp4")) {
0603         TagLib::MP4::File file(&stream, false);
0604         if (file.isValid()) {
0605             auto savedProperties = file.properties();
0606             writeGenericProperties(savedProperties, properties);
0607             auto mp4Tags = dynamic_cast<TagLib::MP4::Tag*>(file.tag());
0608             if (mp4Tags) {
0609                 writeMp4Tags(mp4Tags, properties);
0610                 writeMp4Cover(mp4Tags, data.imageData());
0611             }
0612             file.setProperties(savedProperties);
0613             file.save();
0614         }
0615     } else if (mimeType == QLatin1String("audio/flac")) {
0616         TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false);
0617         if (file.isValid()) {
0618             auto savedProperties = file.properties();
0619             writeGenericProperties(savedProperties, properties);
0620             writeVorbisTags(savedProperties, properties);
0621             file.setProperties(savedProperties);
0622             writeFlacCover(&file, data.imageData());
0623             file.save();
0624         }
0625     } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) {
0626         TagLib::Ogg::Vorbis::File file(&stream, false);
0627         if (file.isValid()) {
0628             auto savedProperties = file.properties();
0629             writeGenericProperties(savedProperties, properties);
0630             writeVorbisTags(savedProperties, properties);
0631             file.setProperties(savedProperties);
0632             writeFlacCover(file.tag(), data.imageData());
0633             file.save();
0634         }
0635     } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) {
0636         TagLib::Ogg::Opus::File file(&stream, false);
0637         if (file.isValid()) {
0638             auto savedProperties = file.properties();
0639             writeGenericProperties(savedProperties, properties);
0640             writeVorbisTags(savedProperties, properties);
0641             file.setProperties(savedProperties);
0642             writeFlacCover(file.tag(), data.imageData());
0643             file.save();
0644         }
0645     } else if (mimeType == QLatin1String("audio/x-speex+ogg")) {
0646         TagLib::Ogg::Speex::File file(&stream, false);
0647         if (file.isValid()) {
0648             auto savedProperties = file.properties();
0649             writeGenericProperties(savedProperties, properties);
0650             writeVorbisTags(savedProperties, properties);
0651             file.setProperties(savedProperties);
0652             writeFlacCover(file.tag(), data.imageData());
0653             file.save();
0654         }
0655     } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
0656         TagLib::ASF::File file(&stream, false);
0657         if (file.isValid()) {
0658             auto savedProperties = file.properties();
0659             writeGenericProperties(savedProperties, properties);
0660             file.setProperties(savedProperties);
0661             auto asfTags = dynamic_cast<TagLib::ASF::Tag*>(file.tag());
0662             if (asfTags){
0663                 writeAsfTags(asfTags, properties);
0664                 writeAsfCover(asfTags, data.imageData());
0665             }
0666             file.save();
0667         }
0668     }
0669 }
0670 
0671 #include "moc_taglibwriter.cpp"