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/mpeg3"),
45 QStringLiteral("audio/ogg"),
46 QStringLiteral("audio/opus"),
47 QStringLiteral("audio/wav"),
48 QStringLiteral("audio/vnd.wave"),
49 QStringLiteral("audio/x-aiff"),
50 QStringLiteral("audio/x-aifc"),
51 QStringLiteral("audio/x-ape"),
52 QStringLiteral("audio/x-mpeg"),
53 QStringLiteral("audio/x-ms-wma"),
54 QStringLiteral("audio/x-musepack"),
55 QStringLiteral("audio/x-opus+ogg"),
56 QStringLiteral("audio/x-speex+ogg"),
57 QStringLiteral("audio/x-vorbis+ogg"),
58 QStringLiteral("audio/x-wav"),
59 QStringLiteral("audio/x-wavpack"),
60};
61
62int id3v2RatingTranslation[11] = {
63 0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255
64};
65
66using namespace KFileMetaData;
67
68template<typename ImageType>
69EmbeddedImageData::ImageType mapTaglibType(const ImageType type)
70{
71 switch (type) {
72 case ImageType::FrontCover:
73 return EmbeddedImageData::FrontCover;
74 case ImageType::Other:
75 return EmbeddedImageData::Other;
76 case ImageType::FileIcon:
77 return EmbeddedImageData::FileIcon;
78 case ImageType::OtherFileIcon:
79 return EmbeddedImageData::OtherFileIcon;
80 case ImageType::BackCover:
81 return EmbeddedImageData::BackCover;
82 case ImageType::LeafletPage:
83 return EmbeddedImageData::LeafletPage;
84 case ImageType::Media:
85 return EmbeddedImageData::Media;
86 case ImageType::LeadArtist:
87 return EmbeddedImageData::LeadArtist;
88 case ImageType::Artist:
89 return EmbeddedImageData::Artist;
90 case ImageType::Conductor:
91 return EmbeddedImageData::Conductor;
92 case ImageType::Band:
93 return EmbeddedImageData::Band;
94 case ImageType::Composer:
95 return EmbeddedImageData::Composer;
96 case ImageType::Lyricist:
97 return EmbeddedImageData::Lyricist;
98 case ImageType::RecordingLocation:
99 return EmbeddedImageData::RecordingLocation;
100 case ImageType::DuringRecording:
101 return EmbeddedImageData::DuringRecording;
102 case ImageType::DuringPerformance:
103 return EmbeddedImageData::DuringPerformance;
104 case ImageType::MovieScreenCapture:
105 return EmbeddedImageData::MovieScreenCapture;
106 case ImageType::ColouredFish:
107 return EmbeddedImageData::ColouredFish;
108 case ImageType::Illustration:
109 return EmbeddedImageData::Illustration;
110 case ImageType::BandLogo:
111 return EmbeddedImageData::BandLogo;
112 case ImageType::PublisherLogo:
113 return EmbeddedImageData::PublisherLogo;
114 default:
115 return EmbeddedImageData::Unknown;
116 }
117}
118
119template<typename ImageType>
120static const std::array<typename ImageType::Type, 21> allImageTypes = {
121 ImageType::FrontCover,
122 ImageType::Other,
123 ImageType::FileIcon,
124 ImageType::OtherFileIcon,
125 ImageType::BackCover,
126 ImageType::LeafletPage,
127 ImageType::Media,
128 ImageType::LeadArtist,
129 ImageType::Artist,
130 ImageType::Conductor,
131 ImageType::Band,
132 ImageType::Composer,
133 ImageType::Lyricist,
134 ImageType::RecordingLocation,
135 ImageType::DuringRecording,
136 ImageType::DuringPerformance,
137 ImageType::MovieScreenCapture,
138 ImageType::ColouredFish,
139 ImageType::Illustration,
140 ImageType::BandLogo,
141 ImageType::PublisherLogo,
142};
143
144TagLib::String determineMimeType(const QByteArray &pictureData)
145{
146 if (pictureData.startsWith(QByteArray::fromHex("89504E470D0A1A0A"))) {
147 return TagLib::String("image/png");
148 } else if (pictureData.startsWith(QByteArray::fromHex("FFD8FFDB")) ||
149 pictureData.startsWith(QByteArray::fromHex("FFD8FFE000104A4649460001")) ||
150 pictureData.startsWith(QByteArray::fromHex("FFD8FFEE")) ||
151 pictureData.startsWith(QByteArray::fromHex("FFD8FFE1"))) {
152 return TagLib::String("image/jpeg");
153 } else {
154 return TagLib::String();
155 }
156}
157
158void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags, const PropertyMultiMap &newProperties)
159{
160 if (newProperties.contains(Property::Rating)) {
161 int rating = newProperties.value(Property::Rating).toInt();
162 if (rating >= 0 && rating <= 10) {
163 id3Tags->removeFrames("POPM");
164 // ID3v2::Tag::addFrame takes ownership
165 auto ratingFrame = new TagLib::ID3v2::PopularimeterFrame;
166 ratingFrame->setEmail("org.kde.kfilemetadata");
167 ratingFrame->setRating(id3v2RatingTranslation[rating]);
168 id3Tags->addFrame(ratingFrame);
169 }
170 }
171}
172
173void writeID3v2Cover(TagLib::ID3v2::Tag *id3Tags,
175{
178 std::for_each(images.keyValueBegin(),images.keyValueEnd(),
179 [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
180 if (it.second.isEmpty()) {
181 removeTypes |= it.first;
182 } else {
183 wantedTypes |= it.first;
184 }
185 });
186
187 using PictureFrame = TagLib::ID3v2::AttachedPictureFrame;
188 auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
189 wantedTypes &= ~kfmType;
190 auto newCover = images[kfmType];
191 auto newMimeType = determineMimeType(newCover);
192 if (!newMimeType.isEmpty()) {
193 coverFrame->setPicture(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
194 coverFrame->setMimeType(newMimeType);
195 }
196 };
197
198 // Update existing covers
199 TagLib::ID3v2::FrameList lstID3v2 = id3Tags->frameListMap()["APIC"];
200 for (auto& frame : std::as_const(lstID3v2)) {
201 auto* coverFrame = static_cast<PictureFrame *>(frame);
202 const auto kfmType = mapTaglibType<PictureFrame::Type>(coverFrame->type());
203 if (kfmType & wantedTypes) {
204 updateFrame(coverFrame, kfmType);
205 } else if (kfmType & removeTypes) {
206 id3Tags->removeFrame(coverFrame);
207 }
208 }
209 // Add new covers
210 for (const auto type : allImageTypes<PictureFrame>) {
211 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
212 if (kfmType & wantedTypes) {
213 // ID3v2::Tag::addFrame takes ownership
214 auto* coverFrame = new PictureFrame;
215 coverFrame->setType(type);
216 updateFrame(coverFrame, kfmType);
217 id3Tags->addFrame(coverFrame);
218 }
219 }
220}
221
222// Instantiated for either FLAC::File or
223// Ogg::XiphComment (Ogg::*::File::tag())
224template<typename Container>
225void writeFlacCover(Container* tags,
227{
230 std::for_each(images.keyValueBegin(),images.keyValueEnd(),
231 [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
232 if (it.second.isEmpty()) {
233 removeTypes |= it.first;
234 } else {
235 wantedTypes |= it.first;
236 }
237 });
238
239 using PictureFrame = TagLib::FLAC::Picture;
240 auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
241 wantedTypes &= ~kfmType;
242 auto newCover = images[kfmType];
243 auto newMimeType = determineMimeType(newCover);
244 if (!newMimeType.isEmpty()) {
245 coverFrame->setData(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
246 coverFrame->setMimeType(newMimeType);
247 }
248 };
249
250 // Update existing covers
251 auto lstPic = tags->pictureList();
252 for (auto it = lstPic.begin(); it != lstPic.end(); ++it) {
253 const auto kfmType = mapTaglibType<PictureFrame::Type>((*it)->type());
254 if (kfmType & wantedTypes) {
255 updateFrame((*it), kfmType);
256 } else if (kfmType & removeTypes) {
257 tags->removePicture(*it);
258 }
259 }
260 // Add new covers
261 for (const auto type : allImageTypes<PictureFrame>) {
262 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
263 if (kfmType & wantedTypes) {
264 // FLAC::File::addPicture takes ownership (dito XiphComment)
265 auto* coverFrame = new PictureFrame;
266 coverFrame->setType(type);
267 updateFrame(coverFrame, kfmType);
268 tags->addPicture(coverFrame);
269 }
270 }
271}
272
273void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
274{
275 if (newProperties.contains(Property::Rating)) {
276 oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
277 }
278}
279
280void writeApeCover(TagLib::APE::Tag* apeTags,
282{
283 if (images.empty()) {
284 return;
285 }
286 auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
287 if ((images.size() > 1) || (imageIt == images.constEnd())) {
288 // TODO warn
289 }
290 if (imageIt == images.constEnd()) {
291 return;
292 }
293
294 const auto newCover = *imageIt;
295 if (newCover.isEmpty()) {
296 apeTags->removeItem("COVER ART (FRONT)");
297 return;
298 }
299
300 TagLib::ByteVector imageData;
301 if (determineMimeType(newCover) == TagLib::String("image/png")) {
302 imageData.setData("frontCover.png\0", 15);
303 } else {
304 imageData.setData("frontCover.jpeg\0", 16);
305 }
306 imageData.append(TagLib::ByteVector(newCover.constData(), newCover.size()));
307 apeTags->setData("COVER ART (FRONT)", imageData);
308}
309
310void writeVorbisTags(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
311{
312 if (newProperties.contains(Property::Rating)) {
313 oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
314 }
315}
316
317void writeAsfTags(TagLib::ASF::Tag *asfTags, const PropertyMultiMap &properties)
318{
319 if (properties.contains(Property::Rating)) {
320 //map the rating values of WMP to Baloo rating
321 //0->0, 1->2, 4->25, 6->50, 8->75, 10->99
322 int rating = properties.value(Property::Rating).toInt();
323 if (rating == 0) {
324 rating = 0;
325 } else if (rating <= 2) {
326 rating = 1;
327 } else if (rating == 10){
328 rating = 99;
329 } else {
330 rating = static_cast<int>(12.5 * rating - 25);
331 }
332 asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating));
333 }
334}
335
336void writeAsfCover(TagLib::ASF::Tag* asfTags,
338{
341 std::for_each(images.keyValueBegin(),images.keyValueEnd(),
342 [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
343 if (it.second.isEmpty()) {
344 removeTypes |= it.first;
345 } else {
346 wantedTypes |= it.first;
347 }
348 });
349
350 using PictureFrame = TagLib::ASF::Picture;
351 auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
352 wantedTypes &= ~kfmType;
353 auto newCover = images[kfmType];
354 auto newMimeType = determineMimeType(newCover);
355 if (!newMimeType.isEmpty()) {
356 coverFrame->setPicture(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
357 coverFrame->setMimeType(newMimeType);
358 }
359 };
360
361 // Update existing covers
362 TagLib::ASF::AttributeList lstPic = asfTags->attribute("WM/Picture");
363 for (auto it = lstPic.begin(); it != lstPic.end(); ) {
364 PictureFrame picture = (*it).toPicture();
365 const auto kfmType = mapTaglibType<PictureFrame::Type>(picture.type());
366 if (kfmType & wantedTypes) {
367 updateFrame(&picture, kfmType);
368 ++it;
369 } else if (kfmType & removeTypes) {
370 it = lstPic.erase(it);
371 } else {
372 ++it;
373 }
374 }
375 // Add new covers
376 for (const auto type : allImageTypes<PictureFrame>) {
377 const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
378 if (kfmType & wantedTypes) {
379 PictureFrame coverFrame;
380 updateFrame(&coverFrame, kfmType);
381 coverFrame.setType(type);
382 lstPic.append(coverFrame);
383 }
384 }
385 asfTags->setAttribute("WM/Picture", lstPic);
386}
387void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMultiMap &newProperties)
388{
389 if (newProperties.contains(Property::Rating)) {
390 mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10)));
391 }
392}
393
394void writeMp4Cover(TagLib::MP4::Tag *mp4Tags,
396{
397 if (images.empty()) {
398 return;
399 }
400 auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
401 if ((images.size() > 1) || (imageIt == images.constEnd())) {
402 // TODO warn
403 }
404 if (imageIt == images.constEnd()) {
405 return;
406 }
407
408 TagLib::MP4::CoverArtList coverArtList;
409 const auto newCover = *imageIt;
410 if (!newCover.isEmpty()) {
411 TagLib::ByteVector imageData(newCover.data(), newCover.size());
412 TagLib::MP4::CoverArt coverArt(TagLib::MP4::CoverArt::Format::Unknown, imageData);
413 coverArtList.append(coverArt);
414 }
415 mp4Tags->setItem("covr", coverArtList);
416}
417
418} // anonymous namespace
419
420void writeGenericProperties(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
421{
422 if (newProperties.empty()) {
423 return;
424 }
425
426 if (newProperties.contains(Property::Title)) {
427 oldProperties.replace("TITLE", QStringToTString(newProperties.value(Property::Title).toString()));
428 }
429
430 if (newProperties.contains(Property::Artist)) {
431 oldProperties.replace("ARTIST", QStringToTString(newProperties.value(Property::Artist).toString()));
432 }
433
434 if (newProperties.contains(Property::AlbumArtist)) {
435 oldProperties.replace("ALBUMARTIST", QStringToTString(newProperties.value(Property::AlbumArtist).toString()));
436 }
437
438 if (newProperties.contains(Property::Album)) {
439 oldProperties.replace("ALBUM", QStringToTString(newProperties.value(Property::Album).toString()));
440 }
441
442 if (newProperties.contains(Property::TrackNumber)) {
443 int trackNumber = newProperties.value(Property::TrackNumber).toInt();
444 //taglib requires uint
445 if (trackNumber >= 0) {
446 oldProperties.replace("TRACKNUMBER", QStringToTString(newProperties.value(Property::TrackNumber).toString()));
447 }
448 }
449
450 if (newProperties.contains(Property::DiscNumber)) {
451 int discNumber = newProperties.value(Property::DiscNumber).toInt();
452 //taglib requires uint
453 if (discNumber >= 0) {
454 oldProperties.replace("DISCNUMBER", QStringToTString(newProperties.value(Property::DiscNumber).toString()));
455 }
456 }
457
458 if (newProperties.contains(Property::ReleaseYear)) {
459 int year = newProperties.value(Property::ReleaseYear).toInt();
460 //taglib requires uint
461 if (year >= 0) {
462 oldProperties.replace("DATE", QStringToTString(newProperties.value(Property::ReleaseYear).toString()));
463 }
464 }
465
466 if (newProperties.contains(Property::Genre)) {
467 oldProperties.replace("GENRE", QStringToTString(newProperties.value(Property::Genre).toString()));
468 }
469
470 if (newProperties.contains(Property::Comment)) {
471 oldProperties.replace("COMMENT", QStringToTString(newProperties.value(Property::Comment).toString()));
472 }
473
474 if (newProperties.contains(Property::Composer)) {
475 oldProperties.replace("COMPOSER", QStringToTString(newProperties.value(Property::Composer).toString()));
476 }
477
478 if (newProperties.contains(Property::Lyricist)) {
479 oldProperties.replace("LYRICIST", QStringToTString(newProperties.value(Property::Lyricist).toString()));
480 }
481
482 if (newProperties.contains(Property::Conductor)) {
483 oldProperties.replace("CONDUCTOR", QStringToTString(newProperties.value(Property::Conductor).toString()));
484 }
485
486 if (newProperties.contains(Property::Copyright)) {
487 oldProperties.replace("COPYRIGHT", QStringToTString(newProperties.value(Property::Copyright).toString()));
488 }
489
490 if (newProperties.contains(Property::Lyrics)) {
491 oldProperties.replace("LYRICS", QStringToTString(newProperties.value(Property::Lyrics).toString()));
492 }
493
494 if (newProperties.contains(Property::Language)) {
495 oldProperties.replace("LANGUAGE", QStringToTString(newProperties.value(Property::Language).toString()));
496 }
497}
498
499TagLibWriter::TagLibWriter(QObject* parent)
500 : WriterPlugin(parent)
501{
502}
503
504QStringList TagLibWriter::writeMimetypes() const
505{
506 return supportedMimeTypes;
507}
508
509void TagLibWriter::write(const WriteData& data)
510{
511 const QString fileUrl = data.inputUrl();
512 const PropertyMultiMap properties = data.properties();
513 const QString mimeType = data.inputMimetype();
514
515#if defined Q_OS_WINDOWS
516 TagLib::FileStream stream(fileUrl.toLocal8Bit().constData(), false);
517#else
518 TagLib::FileStream stream(fileUrl.toUtf8().constData(), false);
519#endif
520 if (!stream.isOpen() || stream.readOnly()) {
521 qCWarning(KFILEMETADATA_LOG) << "Unable to open file in write mode: " << fileUrl;
522 return;
523 }
524
525 if (mimeType == QLatin1String("audio/mpeg") || mimeType == QLatin1String("audio/mpeg3")
526 || mimeType == QLatin1String("audio/x-mpeg")) {
527 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());
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 TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false);
617 if (file.isValid()) {
618 auto savedProperties = file.properties();
619 writeGenericProperties(savedProperties, properties);
620 writeVorbisTags(savedProperties, properties);
621 file.setProperties(savedProperties);
622 writeFlacCover(&file, data.imageData());
623 file.save();
624 }
625 } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) {
626 TagLib::Ogg::Vorbis::File file(&stream, false);
627 if (file.isValid()) {
628 auto savedProperties = file.properties();
629 writeGenericProperties(savedProperties, properties);
630 writeVorbisTags(savedProperties, properties);
631 file.setProperties(savedProperties);
632 writeFlacCover(file.tag(), data.imageData());
633 file.save();
634 }
635 } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) {
636 TagLib::Ogg::Opus::File file(&stream, false);
637 if (file.isValid()) {
638 auto savedProperties = file.properties();
639 writeGenericProperties(savedProperties, properties);
640 writeVorbisTags(savedProperties, properties);
641 file.setProperties(savedProperties);
642 writeFlacCover(file.tag(), data.imageData());
643 file.save();
644 }
645 } else if (mimeType == QLatin1String("audio/x-speex+ogg")) {
646 TagLib::Ogg::Speex::File file(&stream, false);
647 if (file.isValid()) {
648 auto savedProperties = file.properties();
649 writeGenericProperties(savedProperties, properties);
650 writeVorbisTags(savedProperties, properties);
651 file.setProperties(savedProperties);
652 writeFlacCover(file.tag(), data.imageData());
653 file.save();
654 }
655 } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
656 TagLib::ASF::File file(&stream, false);
657 if (file.isValid()) {
658 auto savedProperties = file.properties();
659 writeGenericProperties(savedProperties, properties);
660 file.setProperties(savedProperties);
661 auto asfTags = dynamic_cast<TagLib::ASF::Tag*>(file.tag());
662 if (asfTags){
663 writeAsfTags(asfTags, properties);
664 writeAsfCover(asfTags, data.imageData());
665 }
666 file.save();
667 }
668 }
669}
670
671#include "moc_taglibwriter.cpp"
KCALUTILS_EXPORT QString mimeType()
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 Tue Mar 26 2024 11:17:54 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.