KFileMetaData

taglibwriter.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Varun Joshi <varunj.1011@gmail.com>
3 SPDX-FileCopyrightText: 2018 Alexander Stippich <a.stippich@gmx.net>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#include "taglibwriter.h"
9#include "embeddedimagedata.h"
10#include "kfilemetadata_debug.h"
11
12#include <array>
13
14#include <taglib.h>
15#include <tfilestream.h>
16#include <tpropertymap.h>
17#include <tstring.h>
18#include <aifffile.h>
19#include <apefile.h>
20#include <apetag.h>
21#include <asffile.h>
22#include <asftag.h>
23#include <flacfile.h>
24#include <mp4file.h>
25#include <mp4tag.h>
26#include <mpcfile.h>
27#include <mpegfile.h>
28#include <id3v2tag.h>
29#include <oggfile.h>
30#include <opusfile.h>
31#include <vorbisfile.h>
32#include <speexfile.h>
33#include <wavpackfile.h>
34#include <wavfile.h>
35#include <popularimeterframe.h>
36#include <attachedpictureframe.h>
37
38namespace {
39
40const QStringList supportedMimeTypes = {
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"),
57};
58
59int id3v2RatingTranslation[11] = {
60 0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255
61};
62
63using namespace KFileMetaData;
64
65template<typename ImageType>
66EmbeddedImageData::ImageType mapTaglibType(const ImageType type)
67{
68 switch (type) {
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;
89 case ImageType::Band:
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;
111 default:
112 return EmbeddedImageData::Unknown;
113 }
114}
115
116template<typename ImageType>
117static const std::array<typename ImageType::Type, 21> allImageTypes = {
118 ImageType::FrontCover,
119 ImageType::Other,
120 ImageType::FileIcon,
121 ImageType::OtherFileIcon,
122 ImageType::BackCover,
123 ImageType::LeafletPage,
124 ImageType::Media,
125 ImageType::LeadArtist,
126 ImageType::Artist,
127 ImageType::Conductor,
128 ImageType::Band,
129 ImageType::Composer,
130 ImageType::Lyricist,
131 ImageType::RecordingLocation,
132 ImageType::DuringRecording,
133 ImageType::DuringPerformance,
134 ImageType::MovieScreenCapture,
135 ImageType::ColouredFish,
136 ImageType::Illustration,
137 ImageType::BandLogo,
138 ImageType::PublisherLogo,
139};
140
141TagLib::String determineMimeType(const QByteArray &pictureData)
142{
143 if (pictureData.startsWith(QByteArray::fromHex("89504E470D0A1A0A"))) {
144 return TagLib::String("image/png");
145 } else if (pictureData.startsWith(QByteArray::fromHex("FFD8FFDB")) ||
146 pictureData.startsWith(QByteArray::fromHex("FFD8FFE000104A4649460001")) ||
147 pictureData.startsWith(QByteArray::fromHex("FFD8FFEE")) ||
148 pictureData.startsWith(QByteArray::fromHex("FFD8FFE1"))) {
149 return TagLib::String("image/jpeg");
150 } else {
151 return TagLib::String();
152 }
153}
154
155void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags, const PropertyMultiMap &newProperties)
156{
157 if (newProperties.contains(Property::Rating)) {
158 int rating = newProperties.value(Property::Rating).toInt();
159 if (rating >= 0 && rating <= 10) {
160 id3Tags->removeFrames("POPM");
161 // ID3v2::Tag::addFrame takes ownership
162 auto ratingFrame = new TagLib::ID3v2::PopularimeterFrame;
163 ratingFrame->setEmail("org.kde.kfilemetadata");
164 ratingFrame->setRating(id3v2RatingTranslation[rating]);
165 id3Tags->addFrame(ratingFrame);
166 }
167 }
168}
169
170void writeID3v2Cover(TagLib::ID3v2::Tag *id3Tags,
172{
175 std::for_each(images.keyValueBegin(),images.keyValueEnd(),
176 [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
177 if (it.second.isEmpty()) {
178 removeTypes |= it.first;
179 } else {
180 wantedTypes |= it.first;
181 }
182 });
183
184 using PictureFrame = TagLib::ID3v2::AttachedPictureFrame;
185 auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
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);
192 }
193 };
194
195 // Update existing covers
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);
204 }
205 }
206 // Add new covers
207 for (const auto type : allImageTypes<PictureFrame>) {
208 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
209 if (kfmType & wantedTypes) {
210 // ID3v2::Tag::addFrame takes ownership
211 auto* coverFrame = new PictureFrame;
212 coverFrame->setType(type);
213 updateFrame(coverFrame, kfmType);
214 id3Tags->addFrame(coverFrame);
215 }
216 }
217}
218
219// Instantiated for either FLAC::File or
220// Ogg::XiphComment (Ogg::*::File::tag())
221template<typename Container>
222void writeFlacCover(Container* tags,
224{
227 std::for_each(images.keyValueBegin(),images.keyValueEnd(),
228 [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
229 if (it.second.isEmpty()) {
230 removeTypes |= it.first;
231 } else {
232 wantedTypes |= it.first;
233 }
234 });
235
236 using PictureFrame = TagLib::FLAC::Picture;
237 auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
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);
244 }
245 };
246
247 // Update existing covers
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);
255 }
256 }
257 // Add new covers
258 for (const auto type : allImageTypes<PictureFrame>) {
259 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
260 if (kfmType & wantedTypes) {
261 // FLAC::File::addPicture takes ownership (dito XiphComment)
262 auto* coverFrame = new PictureFrame;
263 coverFrame->setType(type);
264 updateFrame(coverFrame, kfmType);
265 tags->addPicture(coverFrame);
266 }
267 }
268}
269
270void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
271{
272 if (newProperties.contains(Property::Rating)) {
273 oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
274 }
275}
276
277void writeApeCover(TagLib::APE::Tag* apeTags,
279{
280 if (images.empty()) {
281 return;
282 }
283 auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
284 if ((images.size() > 1) || (imageIt == images.constEnd())) {
285 // TODO warn
286 }
287 if (imageIt == images.constEnd()) {
288 return;
289 }
290
291 const auto newCover = *imageIt;
292 if (newCover.isEmpty()) {
293 apeTags->removeItem("COVER ART (FRONT)");
294 return;
295 }
296
297 TagLib::ByteVector imageData;
298 if (determineMimeType(newCover) == TagLib::String("image/png")) {
299 imageData.setData("frontCover.png\0", 15);
300 } else {
301 imageData.setData("frontCover.jpeg\0", 16);
302 }
303 imageData.append(TagLib::ByteVector(newCover.constData(), newCover.size()));
304 apeTags->setData("COVER ART (FRONT)", imageData);
305}
306
307void writeVorbisTags(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
308{
309 if (newProperties.contains(Property::Rating)) {
310 oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
311 }
312}
313
314void writeAsfTags(TagLib::ASF::Tag *asfTags, const PropertyMultiMap &properties)
315{
316 if (properties.contains(Property::Rating)) {
317 //map the rating values of WMP to Baloo rating
318 //0->0, 1->2, 4->25, 6->50, 8->75, 10->99
319 int rating = properties.value(Property::Rating).toInt();
320 if (rating == 0) {
321 rating = 0;
322 } else if (rating <= 2) {
323 rating = 1;
324 } else if (rating == 10){
325 rating = 99;
326 } else {
327 rating = static_cast<int>(12.5 * rating - 25);
328 }
329 asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating));
330 }
331}
332
333void writeAsfCover(TagLib::ASF::Tag* asfTags,
335{
338 std::for_each(images.keyValueBegin(),images.keyValueEnd(),
339 [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
340 if (it.second.isEmpty()) {
341 removeTypes |= it.first;
342 } else {
343 wantedTypes |= it.first;
344 }
345 });
346
347 using PictureFrame = TagLib::ASF::Picture;
348 auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
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);
355 }
356 };
357
358 // Update existing covers
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);
365 ++it;
366 } else if (kfmType & removeTypes) {
367 it = lstPic.erase(it);
368 } else {
369 ++it;
370 }
371 }
372 // Add new covers
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);
380 }
381 }
382 asfTags->setAttribute("WM/Picture", lstPic);
383}
384void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMultiMap &newProperties)
385{
386 if (newProperties.contains(Property::Rating)) {
387 mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10)));
388 }
389}
390
391void writeMp4Cover(TagLib::MP4::Tag *mp4Tags,
393{
394 if (images.empty()) {
395 return;
396 }
397 auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
398 if ((images.size() > 1) || (imageIt == images.constEnd())) {
399 // TODO warn
400 }
401 if (imageIt == images.constEnd()) {
402 return;
403 }
404
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);
411 }
412 mp4Tags->setItem("covr", coverArtList);
413}
414
415} // anonymous namespace
416
417void writeGenericProperties(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
418{
419 if (newProperties.empty()) {
420 return;
421 }
422
423 if (newProperties.contains(Property::Title)) {
424 oldProperties.replace("TITLE", QStringToTString(newProperties.value(Property::Title).toString()));
425 }
426
427 if (newProperties.contains(Property::Artist)) {
428 oldProperties.replace("ARTIST", QStringToTString(newProperties.value(Property::Artist).toString()));
429 }
430
431 if (newProperties.contains(Property::AlbumArtist)) {
432 oldProperties.replace("ALBUMARTIST", QStringToTString(newProperties.value(Property::AlbumArtist).toString()));
433 }
434
435 if (newProperties.contains(Property::Album)) {
436 oldProperties.replace("ALBUM", QStringToTString(newProperties.value(Property::Album).toString()));
437 }
438
439 if (newProperties.contains(Property::TrackNumber)) {
440 int trackNumber = newProperties.value(Property::TrackNumber).toInt();
441 //taglib requires uint
442 if (trackNumber >= 0) {
443 oldProperties.replace("TRACKNUMBER", QStringToTString(newProperties.value(Property::TrackNumber).toString()));
444 }
445 }
446
447 if (newProperties.contains(Property::DiscNumber)) {
448 int discNumber = newProperties.value(Property::DiscNumber).toInt();
449 //taglib requires uint
450 if (discNumber >= 0) {
451 oldProperties.replace("DISCNUMBER", QStringToTString(newProperties.value(Property::DiscNumber).toString()));
452 }
453 }
454
455 if (newProperties.contains(Property::ReleaseYear)) {
456 int year = newProperties.value(Property::ReleaseYear).toInt();
457 //taglib requires uint
458 if (year >= 0) {
459 oldProperties.replace("DATE", QStringToTString(newProperties.value(Property::ReleaseYear).toString()));
460 }
461 }
462
463 if (newProperties.contains(Property::Genre)) {
464 oldProperties.replace("GENRE", QStringToTString(newProperties.value(Property::Genre).toString()));
465 }
466
467 if (newProperties.contains(Property::Comment)) {
468 oldProperties.replace("COMMENT", QStringToTString(newProperties.value(Property::Comment).toString()));
469 }
470
471 if (newProperties.contains(Property::Composer)) {
472 oldProperties.replace("COMPOSER", QStringToTString(newProperties.value(Property::Composer).toString()));
473 }
474
475 if (newProperties.contains(Property::Lyricist)) {
476 oldProperties.replace("LYRICIST", QStringToTString(newProperties.value(Property::Lyricist).toString()));
477 }
478
479 if (newProperties.contains(Property::Conductor)) {
480 oldProperties.replace("CONDUCTOR", QStringToTString(newProperties.value(Property::Conductor).toString()));
481 }
482
483 if (newProperties.contains(Property::Copyright)) {
484 oldProperties.replace("COPYRIGHT", QStringToTString(newProperties.value(Property::Copyright).toString()));
485 }
486
487 if (newProperties.contains(Property::Lyrics)) {
488 oldProperties.replace("LYRICS", QStringToTString(newProperties.value(Property::Lyrics).toString()));
489 }
490
491 if (newProperties.contains(Property::Language)) {
492 oldProperties.replace("LANGUAGE", QStringToTString(newProperties.value(Property::Language).toString()));
493 }
494}
495
496TagLibWriter::TagLibWriter(QObject* parent)
497 : WriterPlugin(parent)
498{
499}
500
501QStringList TagLibWriter::writeMimetypes() const
502{
503 return supportedMimeTypes;
504}
505
506void TagLibWriter::write(const WriteData& data)
507{
508 const QString fileUrl = data.inputUrl();
510 const QString mimeType = data.inputMimetype();
511
512#if defined Q_OS_WINDOWS
513 TagLib::FileStream stream(fileUrl.toLocal8Bit().constData(), false);
514#else
515 TagLib::FileStream stream(fileUrl.toUtf8().constData(), false);
516#endif
517 if (!stream.isOpen() || stream.readOnly()) {
518 qCWarning(KFILEMETADATA_LOG) << "Unable to open file in write mode: " << fileUrl;
519 return;
520 }
521
522 if (mimeType == QLatin1String("audio/mpeg")) {
523#if TAGLIB_MAJOR_VERSION >= 2
524 TagLib::MPEG::File file(&stream, false);
525#else
526 TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false);
527#endif
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());
535 }
536 file.save();
537 }
538 } else if (mimeType == QLatin1String("audio/x-aiff") || mimeType == QLatin1String("audio/x-aifc")) {
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());
545 if (id3Tags) {
546 writeID3v2Tags(id3Tags, properties);
547 writeID3v2Cover(id3Tags, data.imageData());
548 }
549 file.save();
550 }
551 } else if (mimeType == QLatin1String("audio/wav") ||
552 mimeType == QLatin1String("audio/vnd.wave") ||
553 mimeType == QLatin1String("audio/x-wav")) {
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();
560 if (id3Tags) {
561 writeID3v2Tags(id3Tags, properties);
562 writeID3v2Cover(id3Tags, data.imageData());
563 }
564 file.save();
565 }
566 } else if (mimeType == QLatin1String("audio/x-musepack")) {
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());
575 }
576 file.save();
577 }
578 } else if (mimeType == QLatin1String("audio/x-ape")) {
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());
587 }
588 file.save();
589 }
590 } else if (mimeType == QLatin1String("audio/x-wavpack")) {
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());
599 }
600 file.save();
601 }
602 } else if (mimeType == QLatin1String("audio/mp4")) {
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());
608 if (mp4Tags) {
609 writeMp4Tags(mp4Tags, properties);
610 writeMp4Cover(mp4Tags, data.imageData());
611 }
612 file.setProperties(savedProperties);
613 file.save();
614 }
615 } else if (mimeType == QLatin1String("audio/flac")) {
616#if TAGLIB_MAJOR_VERSION >= 2
617 TagLib::FLAC::File file(&stream, false);
618#else
619 TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false);
620#endif
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());
627 file.save();
628 }
629 } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) {
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());
637 file.save();
638 }
639 } else if (mimeType == QLatin1String("audio/x-opus+ogg")) {
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());
647 file.save();
648 }
649 } else if (mimeType == QLatin1String("audio/x-speex+ogg")) {
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());
657 file.save();
658 }
659 } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
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());
666 if (asfTags){
667 writeAsfTags(asfTags, properties);
668 writeAsfCover(asfTags, data.imageData());
669 }
670 file.save();
671 }
672 }
673}
674
675#include "moc_taglibwriter.cpp"
The WriteData class stores all the data to be written to a file.
Definition writedata.h:30
PropertyMultiMap properties() const
Definition writedata.cpp:85
The WriterPlugin is the base class for all file metadata writers.
KCALUTILS_EXPORT QString mimeType()
The KFileMetaData namespace.
KGuiItem properties()
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
bool empty() const const
key_value_iterator keyValueBegin()
key_value_iterator keyValueEnd()
size_type size() const const
bool contains(const Key &key) const const
bool empty() const const
T value(const Key &key, const T &defaultValue) const const
QByteArray toLocal8Bit() const const
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:12:41 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.