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> 32 #include <speexfile.h> 33 #include <wavpackfile.h> 35 #include <popularimeterframe.h> 40 QStringLiteral(
"audio/flac"),
41 QStringLiteral(
"audio/mp4"),
42 QStringLiteral(
"audio/mpeg"),
43 QStringLiteral(
"audio/mpeg3"),
44 QStringLiteral(
"audio/ogg"),
45 QStringLiteral(
"audio/opus"),
46 QStringLiteral(
"audio/wav"),
47 QStringLiteral(
"audio/x-aiff"),
48 QStringLiteral(
"audio/x-aifc"),
49 QStringLiteral(
"audio/x-ape"),
50 QStringLiteral(
"audio/x-mpeg"),
51 QStringLiteral(
"audio/x-ms-wma"),
52 QStringLiteral(
"audio/x-musepack"),
53 QStringLiteral(
"audio/x-opus+ogg"),
54 QStringLiteral(
"audio/x-speex+ogg"),
55 QStringLiteral(
"audio/x-vorbis+ogg"),
56 QStringLiteral(
"audio/x-wav"),
57 QStringLiteral(
"audio/x-wavpack"),
60 int id3v2RatingTranslation[11] = {
61 0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255
66 template<
typename ImageType>
70 case ImageType::FrontCover:
71 return EmbeddedImageData::FrontCover;
72 case ImageType::Other:
73 return EmbeddedImageData::Other;
74 case ImageType::FileIcon:
75 return EmbeddedImageData::FileIcon;
76 case ImageType::OtherFileIcon:
77 return EmbeddedImageData::OtherFileIcon;
78 case ImageType::BackCover:
79 return EmbeddedImageData::BackCover;
80 case ImageType::LeafletPage:
81 return EmbeddedImageData::LeafletPage;
82 case ImageType::Media:
83 return EmbeddedImageData::Media;
84 case ImageType::LeadArtist:
85 return EmbeddedImageData::LeadArtist;
86 case ImageType::Artist:
87 return EmbeddedImageData::Artist;
88 case ImageType::Conductor:
89 return EmbeddedImageData::Conductor;
91 return EmbeddedImageData::Band;
92 case ImageType::Composer:
93 return EmbeddedImageData::Composer;
94 case ImageType::Lyricist:
95 return EmbeddedImageData::Lyricist;
96 case ImageType::RecordingLocation:
97 return EmbeddedImageData::RecordingLocation;
98 case ImageType::DuringRecording:
99 return EmbeddedImageData::DuringRecording;
100 case ImageType::DuringPerformance:
101 return EmbeddedImageData::DuringPerformance;
102 case ImageType::MovieScreenCapture:
103 return EmbeddedImageData::MovieScreenCapture;
104 case ImageType::ColouredFish:
105 return EmbeddedImageData::ColouredFish;
106 case ImageType::Illustration:
107 return EmbeddedImageData::Illustration;
108 case ImageType::BandLogo:
109 return EmbeddedImageData::BandLogo;
110 case ImageType::PublisherLogo:
111 return EmbeddedImageData::PublisherLogo;
113 return EmbeddedImageData::Unknown;
117 template<
typename ImageType>
118 static const std::array<typename ImageType::Type, 21> allImageTypes = {
119 ImageType::FrontCover,
122 ImageType::OtherFileIcon,
123 ImageType::BackCover,
124 ImageType::LeafletPage,
126 ImageType::LeadArtist,
128 ImageType::Conductor,
132 ImageType::RecordingLocation,
133 ImageType::DuringRecording,
134 ImageType::DuringPerformance,
135 ImageType::MovieScreenCapture,
136 ImageType::ColouredFish,
137 ImageType::Illustration,
139 ImageType::PublisherLogo,
142 TagLib::String determineMimeType(
const QByteArray &pictureData)
145 return TagLib::String(
"image/png");
150 return TagLib::String(
"image/jpeg");
152 return TagLib::String();
156 void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags,
const PropertyMap &newProperties)
158 if (newProperties.
contains(Property::Rating)) {
159 int rating = newProperties.
value(Property::Rating).toInt();
160 if (rating >= 0 && rating <= 10) {
161 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);
170 void 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 : qAsConst(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) {
210 auto* coverFrame =
new PictureFrame;
211 coverFrame->setType(type);
212 updateFrame(coverFrame, kfmType);
213 id3Tags->addFrame(coverFrame);
220 template<
typename Container>
221 void writeFlacCover(Container* tags,
227 [&](
const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
228 if (it.second.isEmpty()) {
229 removeTypes |= it.first;
231 wantedTypes |= it.first;
235 using PictureFrame = TagLib::FLAC::Picture;
237 wantedTypes &= ~kfmType;
238 auto newCover = images[kfmType];
239 auto newMimeType = determineMimeType(newCover);
240 if (!newMimeType.isEmpty()) {
241 coverFrame->setData(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
242 coverFrame->setMimeType(newMimeType);
247 auto lstPic = tags->pictureList();
248 for (
auto it = lstPic.begin(); it != lstPic.end(); ++it) {
249 const auto kfmType = mapTaglibType<PictureFrame::Type>((*it)->type());
250 if (kfmType & wantedTypes) {
251 updateFrame((*it), kfmType);
252 }
else if (kfmType & removeTypes) {
253 tags->removePicture(*it);
257 for (
const auto type : allImageTypes<PictureFrame>) {
258 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
259 if (kfmType & wantedTypes) {
260 auto* coverFrame =
new PictureFrame;
261 coverFrame->setType(type);
262 updateFrame(coverFrame, kfmType);
263 tags->addPicture(coverFrame);
268 void writeApeTags(TagLib::PropertyMap &oldProperties,
const PropertyMap &newProperties)
270 if (newProperties.
contains(Property::Rating)) {
271 oldProperties.replace(
"RATING", TagLib::String::number(newProperties.
value(Property::Rating).toInt() * 10));
275 void writeApeCover(TagLib::APE::Tag* apeTags,
278 if (images.
empty()) {
281 auto imageIt = images.
constFind(EmbeddedImageData::FrontCover);
282 if ((images.
size() > 1) || (imageIt == images.
constEnd())) {
289 const auto newCover = *imageIt;
290 if (newCover.isEmpty()) {
291 apeTags->removeItem(
"COVER ART (FRONT)");
295 TagLib::ByteVector imageData;
296 if (determineMimeType(newCover) == TagLib::String(
"image/png")) {
297 imageData.setData(
"frontCover.png\0", 15);
299 imageData.setData(
"frontCover.jpeg\0", 16);
301 imageData.append(TagLib::ByteVector(newCover.constData(), newCover.size()));
302 apeTags->setData(
"COVER ART (FRONT)", imageData);
305 void writeVorbisTags(TagLib::PropertyMap &oldProperties,
const PropertyMap &newProperties)
307 if (newProperties.
contains(Property::Rating)) {
308 oldProperties.replace(
"RATING", TagLib::String::number(newProperties.
value(Property::Rating).toInt() * 10));
312 void writeAsfTags(TagLib::ASF::Tag *asfTags,
const PropertyMap &properties)
314 if (properties.
contains(Property::Rating)) {
317 int rating = properties.
value(Property::Rating).toInt();
320 }
else if (rating <= 2) {
322 }
else if (rating == 10){
325 rating =
static_cast<int>(12.5 * rating - 25);
327 asfTags->setAttribute(
"WM/SharedUserRating", TagLib::String::number(rating));
331 void writeAsfCover(TagLib::ASF::Tag* asfTags,
337 [&](
const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
338 if (it.second.isEmpty()) {
339 removeTypes |= it.first;
341 wantedTypes |= it.first;
345 using PictureFrame = TagLib::ASF::Picture;
347 wantedTypes &= ~kfmType;
348 auto newCover = images[kfmType];
349 auto newMimeType = determineMimeType(newCover);
350 if (!newMimeType.isEmpty()) {
351 coverFrame->setPicture(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
352 coverFrame->setMimeType(newMimeType);
357 TagLib::ASF::AttributeList lstPic = asfTags->attribute(
"WM/Picture");
358 for (
auto it = lstPic.begin(); it != lstPic.end(); ) {
359 PictureFrame picture = (*it).toPicture();
360 const auto kfmType = mapTaglibType<PictureFrame::Type>(picture.type());
361 if (kfmType & wantedTypes) {
362 updateFrame(&picture, kfmType);
364 }
else if (kfmType & removeTypes) {
365 it = lstPic.erase(it);
371 for (
const auto type : allImageTypes<PictureFrame>) {
372 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
373 if (kfmType & wantedTypes) {
374 auto* coverFrame =
new PictureFrame;
375 updateFrame(coverFrame, kfmType);
376 coverFrame->setType(type);
377 lstPic.append(*coverFrame);
380 asfTags->setAttribute(
"WM/Picture", lstPic);
382 void writeMp4Tags(TagLib::MP4::Tag *mp4Tags,
const PropertyMap &newProperties)
384 if (newProperties.
contains(Property::Rating)) {
385 mp4Tags->setItem(
"rate", TagLib::StringList(TagLib::String::number(newProperties.
value(Property::Rating).toInt() * 10)));
389 void writeMp4Cover(TagLib::MP4::Tag *mp4Tags,
392 if (images.
empty()) {
395 auto imageIt = images.
constFind(EmbeddedImageData::FrontCover);
396 if ((images.
size() > 1) || (imageIt == images.
constEnd())) {
403 TagLib::MP4::CoverArtList coverArtList;
404 const auto newCover = *imageIt;
405 if (!newCover.isEmpty()) {
406 TagLib::ByteVector imageData(newCover.data(), newCover.size());
407 TagLib::MP4::CoverArt coverArt(TagLib::MP4::CoverArt::Format::Unknown, imageData);
408 coverArtList.append(coverArt);
410 mp4Tags->setItem(
"covr", coverArtList);
415 void writeGenericProperties(TagLib::PropertyMap &oldProperties,
const PropertyMap &newProperties)
417 if (newProperties.
empty()) {
421 if (newProperties.
contains(Property::Title)) {
422 oldProperties.replace(
"TITLE", QStringToTString(newProperties.
value(Property::Title).toString()));
425 if (newProperties.
contains(Property::Artist)) {
426 oldProperties.replace(
"ARTIST", QStringToTString(newProperties.
value(Property::Artist).toString()));
429 if (newProperties.
contains(Property::AlbumArtist)) {
430 oldProperties.replace(
"ALBUMARTIST", QStringToTString(newProperties.
value(Property::AlbumArtist).toString()));
433 if (newProperties.
contains(Property::Album)) {
434 oldProperties.replace(
"ALBUM", QStringToTString(newProperties.
value(Property::Album).toString()));
437 if (newProperties.
contains(Property::TrackNumber)) {
438 int trackNumber = newProperties.
value(Property::TrackNumber).toInt();
440 if (trackNumber >= 0) {
441 oldProperties.replace(
"TRACKNUMBER", QStringToTString(newProperties.
value(Property::TrackNumber).toString()));
445 if (newProperties.
contains(Property::DiscNumber)) {
446 int discNumber = newProperties.
value(Property::DiscNumber).toInt();
448 if (discNumber >= 0) {
449 oldProperties.replace(
"DISCNUMBER", QStringToTString(newProperties.
value(Property::DiscNumber).toString()));
453 if (newProperties.
contains(Property::ReleaseYear)) {
454 int year = newProperties.
value(Property::ReleaseYear).toInt();
457 oldProperties.replace(
"DATE", QStringToTString(newProperties.
value(Property::ReleaseYear).toString()));
461 if (newProperties.
contains(Property::Genre)) {
462 oldProperties.replace(
"GENRE", QStringToTString(newProperties.
value(Property::Genre).toString()));
465 if (newProperties.
contains(Property::Comment)) {
466 oldProperties.replace(
"COMMENT", QStringToTString(newProperties.
value(Property::Comment).toString()));
469 if (newProperties.
contains(Property::Composer)) {
470 oldProperties.replace(
"COMPOSER", QStringToTString(newProperties.
value(Property::Composer).toString()));
473 if (newProperties.
contains(Property::Lyricist)) {
474 oldProperties.replace(
"LYRICIST", QStringToTString(newProperties.
value(Property::Lyricist).toString()));
477 if (newProperties.
contains(Property::Conductor)) {
478 oldProperties.replace(
"CONDUCTOR", QStringToTString(newProperties.
value(Property::Conductor).toString()));
481 if (newProperties.
contains(Property::Copyright)) {
482 oldProperties.replace(
"COPYRIGHT", QStringToTString(newProperties.
value(Property::Copyright).toString()));
485 if (newProperties.
contains(Property::Lyrics)) {
486 oldProperties.replace(
"LYRICS", QStringToTString(newProperties.
value(Property::Lyrics).toString()));
489 if (newProperties.
contains(Property::Language)) {
490 oldProperties.replace(
"LANGUAGE", QStringToTString(newProperties.
value(Property::Language).toString()));
494 TagLibWriter::TagLibWriter(
QObject* parent)
495 : WriterPlugin(parent)
501 return supportedMimeTypes;
504 void TagLibWriter::write(
const WriteData& data)
506 const QString fileUrl = data.inputUrl();
507 const PropertyMap properties = data.getAllProperties();
510 #if defined Q_OS_WINDOWS 515 if (!stream.isOpen() || stream.readOnly()) {
516 qCWarning(KFILEMETADATA_LOG) <<
"Unable to open file in write mode: " << fileUrl;
522 TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(),
false);
523 if (file.isValid()) {
524 auto savedProperties = file.properties();
525 writeGenericProperties(savedProperties, properties);
526 file.setProperties(savedProperties);
527 if (file.hasID3v2Tag()) {
528 writeID3v2Tags(file.ID3v2Tag(), properties);
529 writeID3v2Cover(file.ID3v2Tag(), data.imageData());
534 TagLib::RIFF::AIFF::File file(&stream,
false);
535 if (file.isValid()) {
536 auto savedProperties = file.properties();
537 writeGenericProperties(savedProperties, properties);
538 file.setProperties(savedProperties);
539 auto id3Tags =
dynamic_cast<TagLib::ID3v2::Tag*
>(file.tag());
541 writeID3v2Tags(id3Tags, properties);
542 writeID3v2Cover(id3Tags, data.imageData());
547 TagLib::RIFF::WAV::File file(&stream,
false);
548 if (file.isValid()) {
549 auto savedProperties = file.properties();
550 writeGenericProperties(savedProperties, properties);
551 file.setProperties(savedProperties);
552 auto id3Tags =
dynamic_cast<TagLib::ID3v2::Tag*
>(file.tag());
554 writeID3v2Tags(id3Tags, properties);
555 writeID3v2Cover(id3Tags, data.imageData());
560 TagLib::MPC::File file(&stream,
false);
561 if (file.isValid()) {
562 auto savedProperties = file.properties();
563 writeGenericProperties(savedProperties, properties);
564 writeApeTags(savedProperties, properties);
565 file.setProperties(savedProperties);
566 if (file.hasAPETag()) {
567 writeApeCover(file.APETag(), data.imageData());
572 TagLib::APE::File file(&stream,
false);
573 if (file.isValid()) {
574 auto savedProperties = file.properties();
575 writeGenericProperties(savedProperties, properties);
576 writeApeTags(savedProperties, properties);
577 file.setProperties(savedProperties);
578 if (file.hasAPETag()) {
579 writeApeCover(file.APETag(), data.imageData());
584 TagLib::WavPack::File file(&stream,
false);
585 if (file.isValid()) {
586 auto savedProperties = file.properties();
587 writeGenericProperties(savedProperties, properties);
588 writeApeTags(savedProperties, properties);
589 file.setProperties(savedProperties);
590 if (file.hasAPETag()) {
591 writeApeCover(file.APETag(), data.imageData());
596 TagLib::MP4::File file(&stream,
false);
597 if (file.isValid()) {
598 auto savedProperties = file.properties();
599 writeGenericProperties(savedProperties, properties);
600 auto mp4Tags =
dynamic_cast<TagLib::MP4::Tag*
>(file.tag());
602 writeMp4Tags(mp4Tags, properties);
603 writeMp4Cover(mp4Tags, data.imageData());
605 file.setProperties(savedProperties);
609 TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(),
false);
610 if (file.isValid()) {
611 auto savedProperties = file.properties();
612 writeGenericProperties(savedProperties, properties);
613 writeVorbisTags(savedProperties, properties);
614 file.setProperties(savedProperties);
615 writeFlacCover(&file, data.imageData());
619 TagLib::Ogg::Vorbis::File file(&stream,
false);
620 if (file.isValid()) {
621 auto savedProperties = file.properties();
622 writeGenericProperties(savedProperties, properties);
623 writeVorbisTags(savedProperties, properties);
624 file.setProperties(savedProperties);
625 writeFlacCover(file.tag(), data.imageData());
629 TagLib::Ogg::Opus::File file(&stream,
false);
630 if (file.isValid()) {
631 auto savedProperties = file.properties();
632 writeGenericProperties(savedProperties, properties);
633 writeVorbisTags(savedProperties, properties);
634 file.setProperties(savedProperties);
635 writeFlacCover(file.tag(), data.imageData());
639 TagLib::Ogg::Speex::File file(&stream,
false);
640 if (file.isValid()) {
641 auto savedProperties = file.properties();
642 writeGenericProperties(savedProperties, properties);
643 writeVorbisTags(savedProperties, properties);
644 file.setProperties(savedProperties);
645 writeFlacCover(file.tag(), data.imageData());
649 TagLib::ASF::File file(&stream,
false);
650 if (file.isValid()) {
651 auto savedProperties = file.properties();
652 writeGenericProperties(savedProperties, properties);
653 file.setProperties(savedProperties);
654 auto asfTags =
dynamic_cast<TagLib::ASF::Tag*
>(file.tag());
656 writeAsfTags(asfTags, properties);
657 writeAsfCover(asfTags, data.imageData());
bool contains(const Key &key) const const
bool startsWith(const QByteArray &ba) const const
QMap::const_iterator constFind(const Key &key) const const
QMap::const_iterator constEnd() const const
const char * constData() const const
QByteArray fromHex(const QByteArray &hexEncoded)
QByteArray toLocal8Bit() const const
QMap::key_value_iterator keyValueBegin()
QMap::key_value_iterator keyValueEnd()
const T value(const Key &key, const T &defaultValue) const const
QByteArray toUtf8() const const