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 TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(),
false);
524 if (file.isValid()) {
525 auto savedProperties = file.properties();
526 writeGenericProperties(savedProperties, properties);
527 file.setProperties(savedProperties);
528 if (file.hasID3v2Tag()) {
529 writeID3v2Tags(file.ID3v2Tag(), properties);
530 writeID3v2Cover(file.ID3v2Tag(), data.imageData());
535 TagLib::RIFF::AIFF::File file(&stream,
false);
536 if (file.isValid()) {
537 auto savedProperties = file.properties();
538 writeGenericProperties(savedProperties, properties);
539 file.setProperties(savedProperties);
540 auto id3Tags =
dynamic_cast<TagLib::ID3v2::Tag*
>(file.tag());
542 writeID3v2Tags(id3Tags, properties);
543 writeID3v2Cover(id3Tags, data.imageData());
550 TagLib::RIFF::WAV::File file(&stream,
false);
551 if (file.isValid()) {
552 auto savedProperties = file.properties();
553 writeGenericProperties(savedProperties, properties);
554 file.setProperties(savedProperties);
555 auto id3Tags = file.ID3v2Tag();
557 writeID3v2Tags(id3Tags, properties);
558 writeID3v2Cover(id3Tags, data.imageData());
563 TagLib::MPC::File file(&stream,
false);
564 if (file.isValid()) {
565 auto savedProperties = file.properties();
566 writeGenericProperties(savedProperties, properties);
567 writeApeTags(savedProperties, properties);
568 file.setProperties(savedProperties);
569 if (file.hasAPETag()) {
570 writeApeCover(file.APETag(), data.imageData());
575 TagLib::APE::File file(&stream,
false);
576 if (file.isValid()) {
577 auto savedProperties = file.properties();
578 writeGenericProperties(savedProperties, properties);
579 writeApeTags(savedProperties, properties);
580 file.setProperties(savedProperties);
581 if (file.hasAPETag()) {
582 writeApeCover(file.APETag(), data.imageData());
587 TagLib::WavPack::File file(&stream,
false);
588 if (file.isValid()) {
589 auto savedProperties = file.properties();
590 writeGenericProperties(savedProperties, properties);
591 writeApeTags(savedProperties, properties);
592 file.setProperties(savedProperties);
593 if (file.hasAPETag()) {
594 writeApeCover(file.APETag(), data.imageData());
599 TagLib::MP4::File file(&stream,
false);
600 if (file.isValid()) {
601 auto savedProperties = file.properties();
602 writeGenericProperties(savedProperties, properties);
603 auto mp4Tags =
dynamic_cast<TagLib::MP4::Tag*
>(file.tag());
605 writeMp4Tags(mp4Tags, properties);
606 writeMp4Cover(mp4Tags, data.imageData());
608 file.setProperties(savedProperties);
612 TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(),
false);
613 if (file.isValid()) {
614 auto savedProperties = file.properties();
615 writeGenericProperties(savedProperties, properties);
616 writeVorbisTags(savedProperties, properties);
617 file.setProperties(savedProperties);
618 writeFlacCover(&file, data.imageData());
622 TagLib::Ogg::Vorbis::File file(&stream,
false);
623 if (file.isValid()) {
624 auto savedProperties = file.properties();
625 writeGenericProperties(savedProperties, properties);
626 writeVorbisTags(savedProperties, properties);
627 file.setProperties(savedProperties);
628 writeFlacCover(file.tag(), data.imageData());
632 TagLib::Ogg::Opus::File file(&stream,
false);
633 if (file.isValid()) {
634 auto savedProperties = file.properties();
635 writeGenericProperties(savedProperties, properties);
636 writeVorbisTags(savedProperties, properties);
637 file.setProperties(savedProperties);
638 writeFlacCover(file.tag(), data.imageData());
642 TagLib::Ogg::Speex::File file(&stream,
false);
643 if (file.isValid()) {
644 auto savedProperties = file.properties();
645 writeGenericProperties(savedProperties, properties);
646 writeVorbisTags(savedProperties, properties);
647 file.setProperties(savedProperties);
648 writeFlacCover(file.tag(), data.imageData());
652 TagLib::ASF::File file(&stream,
false);
653 if (file.isValid()) {
654 auto savedProperties = file.properties();
655 writeGenericProperties(savedProperties, properties);
656 file.setProperties(savedProperties);
657 auto asfTags =
dynamic_cast<TagLib::ASF::Tag*
>(file.tag());
659 writeAsfTags(asfTags, properties);
660 writeAsfCover(asfTags, data.imageData());
667#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