8#include "taglibwriter.h"
9#include "embeddedimagedata.h"
10#include "kfilemetadata_debug.h"
15#include <tfilestream.h>
16#include <tpropertymap.h>
31#include <vorbisfile.h>
33#include <wavpackfile.h>
35#include <popularimeterframe.h>
36#include <attachedpictureframe.h>
41 QStringLiteral(
"audio/flac"),
42 QStringLiteral(
"audio/mp4"),
43 QStringLiteral(
"audio/mpeg"),
44 QStringLiteral(
"audio/ogg"),
45 QStringLiteral(
"audio/wav"),
46 QStringLiteral(
"audio/vnd.wave"),
47 QStringLiteral(
"audio/x-aiff"),
48 QStringLiteral(
"audio/x-aifc"),
49 QStringLiteral(
"audio/x-ape"),
50 QStringLiteral(
"audio/x-ms-wma"),
51 QStringLiteral(
"audio/x-musepack"),
52 QStringLiteral(
"audio/x-opus+ogg"),
53 QStringLiteral(
"audio/x-speex+ogg"),
54 QStringLiteral(
"audio/x-vorbis+ogg"),
55 QStringLiteral(
"audio/x-wav"),
56 QStringLiteral(
"audio/x-wavpack"),
59int id3v2RatingTranslation[11] = {
60 0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255
65template<
typename ImageType>
69 case ImageType::FrontCover:
70 return EmbeddedImageData::FrontCover;
71 case ImageType::Other:
72 return EmbeddedImageData::Other;
73 case ImageType::FileIcon:
74 return EmbeddedImageData::FileIcon;
75 case ImageType::OtherFileIcon:
76 return EmbeddedImageData::OtherFileIcon;
77 case ImageType::BackCover:
78 return EmbeddedImageData::BackCover;
79 case ImageType::LeafletPage:
80 return EmbeddedImageData::LeafletPage;
81 case ImageType::Media:
82 return EmbeddedImageData::Media;
83 case ImageType::LeadArtist:
84 return EmbeddedImageData::LeadArtist;
85 case ImageType::Artist:
86 return EmbeddedImageData::Artist;
87 case ImageType::Conductor:
88 return EmbeddedImageData::Conductor;
90 return EmbeddedImageData::Band;
91 case ImageType::Composer:
92 return EmbeddedImageData::Composer;
93 case ImageType::Lyricist:
94 return EmbeddedImageData::Lyricist;
95 case ImageType::RecordingLocation:
96 return EmbeddedImageData::RecordingLocation;
97 case ImageType::DuringRecording:
98 return EmbeddedImageData::DuringRecording;
99 case ImageType::DuringPerformance:
100 return EmbeddedImageData::DuringPerformance;
101 case ImageType::MovieScreenCapture:
102 return EmbeddedImageData::MovieScreenCapture;
103 case ImageType::ColouredFish:
104 return EmbeddedImageData::ColouredFish;
105 case ImageType::Illustration:
106 return EmbeddedImageData::Illustration;
107 case ImageType::BandLogo:
108 return EmbeddedImageData::BandLogo;
109 case ImageType::PublisherLogo:
110 return EmbeddedImageData::PublisherLogo;
112 return EmbeddedImageData::Unknown;
116template<
typename ImageType>
117static const std::array<typename ImageType::Type, 21> allImageTypes = {
118 ImageType::FrontCover,
121 ImageType::OtherFileIcon,
122 ImageType::BackCover,
123 ImageType::LeafletPage,
125 ImageType::LeadArtist,
127 ImageType::Conductor,
131 ImageType::RecordingLocation,
132 ImageType::DuringRecording,
133 ImageType::DuringPerformance,
134 ImageType::MovieScreenCapture,
135 ImageType::ColouredFish,
136 ImageType::Illustration,
138 ImageType::PublisherLogo,
141TagLib::String determineMimeType(
const QByteArray &pictureData)
144 return TagLib::String(
"image/png");
149 return TagLib::String(
"image/jpeg");
151 return TagLib::String();
155void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags,
const PropertyMultiMap &newProperties)
157 if (newProperties.
contains(Property::Rating)) {
158 int rating = newProperties.
value(Property::Rating).toInt();
159 if (rating >= 0 && rating <= 10) {
160 id3Tags->removeFrames(
"POPM");
162 auto ratingFrame =
new TagLib::ID3v2::PopularimeterFrame;
163 ratingFrame->setEmail(
"org.kde.kfilemetadata");
164 ratingFrame->setRating(id3v2RatingTranslation[rating]);
165 id3Tags->addFrame(ratingFrame);
170void writeID3v2Cover(TagLib::ID3v2::Tag *id3Tags,
176 [&](
const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
177 if (it.second.isEmpty()) {
178 removeTypes |= it.first;
180 wantedTypes |= it.first;
184 using PictureFrame = TagLib::ID3v2::AttachedPictureFrame;
186 wantedTypes &= ~kfmType;
187 auto newCover = images[kfmType];
188 auto newMimeType = determineMimeType(newCover);
189 if (!newMimeType.isEmpty()) {
190 coverFrame->setPicture(TagLib::ByteVector(
static_cast<const char *
>(newCover.data()), newCover.size()));
191 coverFrame->setMimeType(newMimeType);
196 TagLib::ID3v2::FrameList lstID3v2 = id3Tags->frameListMap()[
"APIC"];
197 for (
auto& frame : std::as_const(lstID3v2)) {
198 auto* coverFrame =
static_cast<PictureFrame *
>(frame);
199 const auto kfmType = mapTaglibType<PictureFrame::Type>(coverFrame->type());
200 if (kfmType & wantedTypes) {
201 updateFrame(coverFrame, kfmType);
202 }
else if (kfmType & removeTypes) {
203 id3Tags->removeFrame(coverFrame);
207 for (
const auto type : allImageTypes<PictureFrame>) {
208 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
209 if (kfmType & wantedTypes) {
211 auto* coverFrame =
new PictureFrame;
212 coverFrame->setType(type);
213 updateFrame(coverFrame, kfmType);
214 id3Tags->addFrame(coverFrame);
221template<
typename Container>
222void writeFlacCover(Container* tags,
228 [&](
const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
229 if (it.second.isEmpty()) {
230 removeTypes |= it.first;
232 wantedTypes |= it.first;
236 using PictureFrame = TagLib::FLAC::Picture;
238 wantedTypes &= ~kfmType;
239 auto newCover = images[kfmType];
240 auto newMimeType = determineMimeType(newCover);
241 if (!newMimeType.isEmpty()) {
242 coverFrame->setData(TagLib::ByteVector(
static_cast<const char *
>(newCover.data()), newCover.size()));
243 coverFrame->setMimeType(newMimeType);
248 auto lstPic = tags->pictureList();
249 for (
auto it = lstPic.begin(); it != lstPic.end(); ++it) {
250 const auto kfmType = mapTaglibType<PictureFrame::Type>((*it)->type());
251 if (kfmType & wantedTypes) {
252 updateFrame((*it), kfmType);
253 }
else if (kfmType & removeTypes) {
254 tags->removePicture(*it);
258 for (
const auto type : allImageTypes<PictureFrame>) {
259 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
260 if (kfmType & wantedTypes) {
262 auto* coverFrame =
new PictureFrame;
263 coverFrame->setType(type);
264 updateFrame(coverFrame, kfmType);
265 tags->addPicture(coverFrame);
270void writeApeTags(TagLib::PropertyMap &oldProperties,
const PropertyMultiMap &newProperties)
272 if (newProperties.
contains(Property::Rating)) {
273 oldProperties.replace(
"RATING", TagLib::String::number(newProperties.
value(Property::Rating).toInt() * 10));
277void writeApeCover(TagLib::APE::Tag* apeTags,
280 if (images.
empty()) {
283 auto imageIt = images.
constFind(EmbeddedImageData::FrontCover);
284 if ((images.
size() > 1) || (imageIt == images.
constEnd())) {
291 const auto newCover = *imageIt;
292 if (newCover.isEmpty()) {
293 apeTags->removeItem(
"COVER ART (FRONT)");
297 TagLib::ByteVector imageData;
298 if (determineMimeType(newCover) == TagLib::String(
"image/png")) {
299 imageData.setData(
"frontCover.png\0", 15);
301 imageData.setData(
"frontCover.jpeg\0", 16);
303 imageData.append(TagLib::ByteVector(newCover.constData(), newCover.size()));
304 apeTags->setData(
"COVER ART (FRONT)", imageData);
307void writeVorbisTags(TagLib::PropertyMap &oldProperties,
const PropertyMultiMap &newProperties)
309 if (newProperties.
contains(Property::Rating)) {
310 oldProperties.replace(
"RATING", TagLib::String::number(newProperties.
value(Property::Rating).toInt() * 10));
314void writeAsfTags(TagLib::ASF::Tag *asfTags,
const PropertyMultiMap &properties)
319 int rating =
properties.value(Property::Rating).toInt();
322 }
else if (rating <= 2) {
324 }
else if (rating == 10){
327 rating =
static_cast<int>(12.5 * rating - 25);
329 asfTags->setAttribute(
"WM/SharedUserRating", TagLib::String::number(rating));
333void writeAsfCover(TagLib::ASF::Tag* asfTags,
339 [&](
const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
340 if (it.second.isEmpty()) {
341 removeTypes |= it.first;
343 wantedTypes |= it.first;
347 using PictureFrame = TagLib::ASF::Picture;
349 wantedTypes &= ~kfmType;
350 auto newCover = images[kfmType];
351 auto newMimeType = determineMimeType(newCover);
352 if (!newMimeType.isEmpty()) {
353 coverFrame->setPicture(TagLib::ByteVector(
static_cast<const char *
>(newCover.data()), newCover.size()));
354 coverFrame->setMimeType(newMimeType);
359 TagLib::ASF::AttributeList lstPic = asfTags->attribute(
"WM/Picture");
360 for (
auto it = lstPic.begin(); it != lstPic.end(); ) {
361 PictureFrame picture = (*it).toPicture();
362 const auto kfmType = mapTaglibType<PictureFrame::Type>(picture.type());
363 if (kfmType & wantedTypes) {
364 updateFrame(&picture, kfmType);
366 }
else if (kfmType & removeTypes) {
367 it = lstPic.erase(it);
373 for (
const auto type : allImageTypes<PictureFrame>) {
374 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
375 if (kfmType & wantedTypes) {
376 PictureFrame coverFrame;
377 updateFrame(&coverFrame, kfmType);
378 coverFrame.setType(type);
379 lstPic.append(coverFrame);
382 asfTags->setAttribute(
"WM/Picture", lstPic);
384void writeMp4Tags(TagLib::MP4::Tag *mp4Tags,
const PropertyMultiMap &newProperties)
386 if (newProperties.
contains(Property::Rating)) {
387 mp4Tags->setItem(
"rate", TagLib::StringList(TagLib::String::number(newProperties.
value(Property::Rating).toInt() * 10)));
391void writeMp4Cover(TagLib::MP4::Tag *mp4Tags,
394 if (images.
empty()) {
397 auto imageIt = images.
constFind(EmbeddedImageData::FrontCover);
398 if ((images.
size() > 1) || (imageIt == images.
constEnd())) {
405 TagLib::MP4::CoverArtList coverArtList;
406 const auto newCover = *imageIt;
407 if (!newCover.isEmpty()) {
408 TagLib::ByteVector imageData(newCover.data(), newCover.size());
409 TagLib::MP4::CoverArt coverArt(TagLib::MP4::CoverArt::Format::Unknown, imageData);
410 coverArtList.append(coverArt);
412 mp4Tags->setItem(
"covr", coverArtList);
417void writeGenericProperties(TagLib::PropertyMap &oldProperties,
const PropertyMultiMap &newProperties)
419 if (newProperties.
empty()) {
423 if (newProperties.
contains(Property::Title)) {
424 oldProperties.replace(
"TITLE", QStringToTString(newProperties.
value(Property::Title).toString()));
427 if (newProperties.
contains(Property::Artist)) {
428 oldProperties.replace(
"ARTIST", QStringToTString(newProperties.
value(Property::Artist).toString()));
431 if (newProperties.
contains(Property::AlbumArtist)) {
432 oldProperties.replace(
"ALBUMARTIST", QStringToTString(newProperties.
value(Property::AlbumArtist).toString()));
435 if (newProperties.
contains(Property::Album)) {
436 oldProperties.replace(
"ALBUM", QStringToTString(newProperties.
value(Property::Album).toString()));
439 if (newProperties.
contains(Property::TrackNumber)) {
440 int trackNumber = newProperties.
value(Property::TrackNumber).toInt();
442 if (trackNumber >= 0) {
443 oldProperties.replace(
"TRACKNUMBER", QStringToTString(newProperties.
value(Property::TrackNumber).toString()));
447 if (newProperties.
contains(Property::DiscNumber)) {
448 int discNumber = newProperties.
value(Property::DiscNumber).toInt();
450 if (discNumber >= 0) {
451 oldProperties.replace(
"DISCNUMBER", QStringToTString(newProperties.
value(Property::DiscNumber).toString()));
455 if (newProperties.
contains(Property::ReleaseYear)) {
456 int year = newProperties.
value(Property::ReleaseYear).toInt();
459 oldProperties.replace(
"DATE", QStringToTString(newProperties.
value(Property::ReleaseYear).toString()));
463 if (newProperties.
contains(Property::Genre)) {
464 oldProperties.replace(
"GENRE", QStringToTString(newProperties.
value(Property::Genre).toString()));
467 if (newProperties.
contains(Property::Comment)) {
468 oldProperties.replace(
"COMMENT", QStringToTString(newProperties.
value(Property::Comment).toString()));
471 if (newProperties.
contains(Property::Composer)) {
472 oldProperties.replace(
"COMPOSER", QStringToTString(newProperties.
value(Property::Composer).toString()));
475 if (newProperties.
contains(Property::Lyricist)) {
476 oldProperties.replace(
"LYRICIST", QStringToTString(newProperties.
value(Property::Lyricist).toString()));
479 if (newProperties.
contains(Property::Conductor)) {
480 oldProperties.replace(
"CONDUCTOR", QStringToTString(newProperties.
value(Property::Conductor).toString()));
483 if (newProperties.
contains(Property::Copyright)) {
484 oldProperties.replace(
"COPYRIGHT", QStringToTString(newProperties.
value(Property::Copyright).toString()));
487 if (newProperties.
contains(Property::Lyrics)) {
488 oldProperties.replace(
"LYRICS", QStringToTString(newProperties.
value(Property::Lyrics).toString()));
491 if (newProperties.
contains(Property::Language)) {
492 oldProperties.replace(
"LANGUAGE", QStringToTString(newProperties.
value(Property::Language).toString()));
496TagLibWriter::TagLibWriter(
QObject* parent)
503 return supportedMimeTypes;
506void TagLibWriter::write(
const WriteData& data)
508 const QString fileUrl = data.inputUrl();
512#if defined Q_OS_WINDOWS
517 if (!stream.isOpen() || stream.readOnly()) {
518 qCWarning(KFILEMETADATA_LOG) <<
"Unable to open file in write mode: " << fileUrl;
523#if TAGLIB_MAJOR_VERSION >= 2
524 TagLib::MPEG::File file(&stream,
false);
526 TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(),
false);
528 if (file.isValid()) {
529 auto savedProperties = file.properties();
530 writeGenericProperties(savedProperties, properties);
531 file.setProperties(savedProperties);
532 if (file.hasID3v2Tag()) {
533 writeID3v2Tags(file.ID3v2Tag(), properties);
534 writeID3v2Cover(file.ID3v2Tag(), data.imageData());
539 TagLib::RIFF::AIFF::File file(&stream,
false);
540 if (file.isValid()) {
541 auto savedProperties = file.properties();
542 writeGenericProperties(savedProperties, properties);
543 file.setProperties(savedProperties);
544 auto id3Tags =
dynamic_cast<TagLib::ID3v2::Tag*
>(file.tag());
546 writeID3v2Tags(id3Tags, properties);
547 writeID3v2Cover(id3Tags, data.imageData());
554 TagLib::RIFF::WAV::File file(&stream,
false);
555 if (file.isValid()) {
556 auto savedProperties = file.properties();
557 writeGenericProperties(savedProperties, properties);
558 file.setProperties(savedProperties);
559 auto id3Tags = file.ID3v2Tag();
561 writeID3v2Tags(id3Tags, properties);
562 writeID3v2Cover(id3Tags, data.imageData());
567 TagLib::MPC::File file(&stream,
false);
568 if (file.isValid()) {
569 auto savedProperties = file.properties();
570 writeGenericProperties(savedProperties, properties);
571 writeApeTags(savedProperties, properties);
572 file.setProperties(savedProperties);
573 if (file.hasAPETag()) {
574 writeApeCover(file.APETag(), data.imageData());
579 TagLib::APE::File file(&stream,
false);
580 if (file.isValid()) {
581 auto savedProperties = file.properties();
582 writeGenericProperties(savedProperties, properties);
583 writeApeTags(savedProperties, properties);
584 file.setProperties(savedProperties);
585 if (file.hasAPETag()) {
586 writeApeCover(file.APETag(), data.imageData());
591 TagLib::WavPack::File file(&stream,
false);
592 if (file.isValid()) {
593 auto savedProperties = file.properties();
594 writeGenericProperties(savedProperties, properties);
595 writeApeTags(savedProperties, properties);
596 file.setProperties(savedProperties);
597 if (file.hasAPETag()) {
598 writeApeCover(file.APETag(), data.imageData());
603 TagLib::MP4::File file(&stream,
false);
604 if (file.isValid()) {
605 auto savedProperties = file.properties();
606 writeGenericProperties(savedProperties, properties);
607 auto mp4Tags =
dynamic_cast<TagLib::MP4::Tag*
>(file.tag());
609 writeMp4Tags(mp4Tags, properties);
610 writeMp4Cover(mp4Tags, data.imageData());
612 file.setProperties(savedProperties);
616#if TAGLIB_MAJOR_VERSION >= 2
617 TagLib::FLAC::File file(&stream,
false);
619 TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(),
false);
621 if (file.isValid()) {
622 auto savedProperties = file.properties();
623 writeGenericProperties(savedProperties, properties);
624 writeVorbisTags(savedProperties, properties);
625 file.setProperties(savedProperties);
626 writeFlacCover(&file, data.imageData());
630 TagLib::Ogg::Vorbis::File file(&stream,
false);
631 if (file.isValid()) {
632 auto savedProperties = file.properties();
633 writeGenericProperties(savedProperties, properties);
634 writeVorbisTags(savedProperties, properties);
635 file.setProperties(savedProperties);
636 writeFlacCover(file.tag(), data.imageData());
640 TagLib::Ogg::Opus::File file(&stream,
false);
641 if (file.isValid()) {
642 auto savedProperties = file.properties();
643 writeGenericProperties(savedProperties, properties);
644 writeVorbisTags(savedProperties, properties);
645 file.setProperties(savedProperties);
646 writeFlacCover(file.tag(), data.imageData());
650 TagLib::Ogg::Speex::File file(&stream,
false);
651 if (file.isValid()) {
652 auto savedProperties = file.properties();
653 writeGenericProperties(savedProperties, properties);
654 writeVorbisTags(savedProperties, properties);
655 file.setProperties(savedProperties);
656 writeFlacCover(file.tag(), data.imageData());
660 TagLib::ASF::File file(&stream,
false);
661 if (file.isValid()) {
662 auto savedProperties = file.properties();
663 writeGenericProperties(savedProperties, properties);
664 file.setProperties(savedProperties);
665 auto asfTags =
dynamic_cast<TagLib::ASF::Tag*
>(file.tag());
667 writeAsfTags(asfTags, properties);
668 writeAsfCover(asfTags, data.imageData());
675#include "moc_taglibwriter.cpp"
KCALUTILS_EXPORT QString mimeType()
const char * constData() const const
QByteArray fromHex(const QByteArray &hexEncoded)
bool startsWith(QByteArrayView bv) const const
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
key_value_iterator keyValueBegin()
key_value_iterator keyValueEnd()
size_type size() const const
bool contains(const Key &key) const const
T value(const Key &key, const T &defaultValue) const const
QByteArray toLocal8Bit() const const
QByteArray toUtf8() const const