File indexing completed on 2025-03-16 12:49:37
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"