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 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());
531 }
532 file.save();
533 }
534 } else if (mimeType == QLatin1String("audio/x-aiff") || mimeType == QLatin1String("audio/x-aifc")) {
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());
541 if (id3Tags) {
542 writeID3v2Tags(id3Tags, properties);
543 writeID3v2Cover(id3Tags, data.imageData());
544 }
545 file.save();
546 }
547 } else if (mimeType == QLatin1String("audio/wav") ||
548 mimeType == QLatin1String("audio/vnd.wave") ||
549 mimeType == QLatin1String("audio/x-wav")) {
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();
556 if (id3Tags) {
557 writeID3v2Tags(id3Tags, properties);
558 writeID3v2Cover(id3Tags, data.imageData());
559 }
560 file.save();
561 }
562 } else if (mimeType == QLatin1String("audio/x-musepack")) {
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());
571 }
572 file.save();
573 }
574 } else if (mimeType == QLatin1String("audio/x-ape")) {
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());
583 }
584 file.save();
585 }
586 } else if (mimeType == QLatin1String("audio/x-wavpack")) {
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());
595 }
596 file.save();
597 }
598 } else if (mimeType == QLatin1String("audio/mp4")) {
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());
604 if (mp4Tags) {
605 writeMp4Tags(mp4Tags, properties);
606 writeMp4Cover(mp4Tags, data.imageData());
607 }
608 file.setProperties(savedProperties);
609 file.save();
610 }
611 } else if (mimeType == QLatin1String("audio/flac")) {
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());
619 file.save();
620 }
621 } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) {
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());
629 file.save();
630 }
631 } else if (mimeType == QLatin1String("audio/x-opus+ogg")) {
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());
639 file.save();
640 }
641 } else if (mimeType == QLatin1String("audio/x-speex+ogg")) {
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());
649 file.save();
650 }
651 } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
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());
658 if (asfTags){
659 writeAsfTags(asfTags, properties);
660 writeAsfCover(asfTags, data.imageData());
661 }
662 file.save();
663 }
664 }
665}
666
667#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 May 17 2024 11:49:15 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.