KFileMetaData

taglibwriter.cpp
1 /*
2  SPDX-FileCopyrightText: 2016 Varun Joshi <[email protected]>
3  SPDX-FileCopyrightText: 2018 Alexander Stippich <[email protected]>
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 
37 namespace {
38 
39 const QStringList supportedMimeTypes = {
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"),
58 };
59 
60 int id3v2RatingTranslation[11] = {
61  0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255
62 };
63 
64 using namespace KFileMetaData;
65 
66 template<typename ImageType>
67 EmbeddedImageData::ImageType mapTaglibType(const ImageType type)
68 {
69  switch (type) {
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;
90  case ImageType::Band:
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;
112  default:
113  return EmbeddedImageData::Unknown;
114  }
115 }
116 
117 template<typename ImageType>
118 static const std::array<typename ImageType::Type, 21> allImageTypes = {
119  ImageType::FrontCover,
120  ImageType::Other,
121  ImageType::FileIcon,
122  ImageType::OtherFileIcon,
123  ImageType::BackCover,
124  ImageType::LeafletPage,
125  ImageType::Media,
126  ImageType::LeadArtist,
127  ImageType::Artist,
128  ImageType::Conductor,
129  ImageType::Band,
130  ImageType::Composer,
131  ImageType::Lyricist,
132  ImageType::RecordingLocation,
133  ImageType::DuringRecording,
134  ImageType::DuringPerformance,
135  ImageType::MovieScreenCapture,
136  ImageType::ColouredFish,
137  ImageType::Illustration,
138  ImageType::BandLogo,
139  ImageType::PublisherLogo,
140 };
141 
142 TagLib::String determineMimeType(const QByteArray &pictureData)
143 {
144  if (pictureData.startsWith(QByteArray::fromHex("89504E470D0A1A0A"))) {
145  return TagLib::String("image/png");
146  } else if (pictureData.startsWith(QByteArray::fromHex("FFD8FFDB")) ||
147  pictureData.startsWith(QByteArray::fromHex("FFD8FFE000104A4649460001")) ||
148  pictureData.startsWith(QByteArray::fromHex("FFD8FFEE")) ||
149  pictureData.startsWith(QByteArray::fromHex("FFD8FFE1"))) {
150  return TagLib::String("image/jpeg");
151  } else {
152  return TagLib::String();
153  }
154 }
155 
156 void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags, const PropertyMap &newProperties)
157 {
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);
166  }
167  }
168 }
169 
170 void writeID3v2Cover(TagLib::ID3v2::Tag *id3Tags,
172 {
173  EmbeddedImageData::ImageTypes wantedTypes;
174  EmbeddedImageData::ImageTypes removeTypes;
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 : 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);
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  auto* coverFrame = new PictureFrame;
211  coverFrame->setType(type);
212  updateFrame(coverFrame, kfmType);
213  id3Tags->addFrame(coverFrame);
214  }
215  }
216 }
217 
218 // Instantiated for either FLAC::File or
219 // Ogg::XiphComment (Ogg::*::File::tag())
220 template<typename Container>
221 void writeFlacCover(Container* tags,
223 {
224  EmbeddedImageData::ImageTypes wantedTypes;
225  EmbeddedImageData::ImageTypes removeTypes;
226  std::for_each(images.keyValueBegin(),images.keyValueEnd(),
227  [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
228  if (it.second.isEmpty()) {
229  removeTypes |= it.first;
230  } else {
231  wantedTypes |= it.first;
232  }
233  });
234 
235  using PictureFrame = TagLib::FLAC::Picture;
236  auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
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);
243  }
244  };
245 
246  // Update existing covers
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);
254  }
255  }
256  // Add new covers
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);
264  }
265  }
266 }
267 
268 void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties)
269 {
270  if (newProperties.contains(Property::Rating)) {
271  oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
272  }
273 }
274 
275 void writeApeCover(TagLib::APE::Tag* apeTags,
277 {
278  if (images.empty()) {
279  return;
280  }
281  auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
282  if ((images.size() > 1) || (imageIt == images.constEnd())) {
283  // TODO warn
284  }
285  if (imageIt == images.constEnd()) {
286  return;
287  }
288 
289  const auto newCover = *imageIt;
290  if (newCover.isEmpty()) {
291  apeTags->removeItem("COVER ART (FRONT)");
292  return;
293  }
294 
295  TagLib::ByteVector imageData;
296  if (determineMimeType(newCover) == TagLib::String("image/png")) {
297  imageData.setData("frontCover.png\0", 15);
298  } else {
299  imageData.setData("frontCover.jpeg\0", 16);
300  }
301  imageData.append(TagLib::ByteVector(newCover.constData(), newCover.size()));
302  apeTags->setData("COVER ART (FRONT)", imageData);
303 }
304 
305 void writeVorbisTags(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties)
306 {
307  if (newProperties.contains(Property::Rating)) {
308  oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
309  }
310 }
311 
312 void writeAsfTags(TagLib::ASF::Tag *asfTags, const PropertyMap &properties)
313 {
314  if (properties.contains(Property::Rating)) {
315  //map the rating values of WMP to Baloo rating
316  //0->0, 1->2, 4->25, 6->50, 8->75, 10->99
317  int rating = properties.value(Property::Rating).toInt();
318  if (rating == 0) {
319  rating = 0;
320  } else if (rating <= 2) {
321  rating = 1;
322  } else if (rating == 10){
323  rating = 99;
324  } else {
325  rating = static_cast<int>(12.5 * rating - 25);
326  }
327  asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating));
328  }
329 }
330 
331 void writeAsfCover(TagLib::ASF::Tag* asfTags,
333 {
334  EmbeddedImageData::ImageTypes wantedTypes;
335  EmbeddedImageData::ImageTypes removeTypes;
336  std::for_each(images.keyValueBegin(),images.keyValueEnd(),
337  [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
338  if (it.second.isEmpty()) {
339  removeTypes |= it.first;
340  } else {
341  wantedTypes |= it.first;
342  }
343  });
344 
345  using PictureFrame = TagLib::ASF::Picture;
346  auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
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);
353  }
354  };
355 
356  // Update existing covers
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);
363  ++it;
364  } else if (kfmType & removeTypes) {
365  it = lstPic.erase(it);
366  } else {
367  ++it;
368  }
369  }
370  // Add new covers
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);
378  }
379  }
380  asfTags->setAttribute("WM/Picture", lstPic);
381 }
382 void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMap &newProperties)
383 {
384  if (newProperties.contains(Property::Rating)) {
385  mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10)));
386  }
387 }
388 
389 void writeMp4Cover(TagLib::MP4::Tag *mp4Tags,
391 {
392  if (images.empty()) {
393  return;
394  }
395  auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
396  if ((images.size() > 1) || (imageIt == images.constEnd())) {
397  // TODO warn
398  }
399  if (imageIt == images.constEnd()) {
400  return;
401  }
402 
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);
409  }
410  mp4Tags->setItem("covr", coverArtList);
411 }
412 
413 } // anonymous namespace
414 
415 void writeGenericProperties(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties)
416 {
417  if (newProperties.empty()) {
418  return;
419  }
420 
421  if (newProperties.contains(Property::Title)) {
422  oldProperties.replace("TITLE", QStringToTString(newProperties.value(Property::Title).toString()));
423  }
424 
425  if (newProperties.contains(Property::Artist)) {
426  oldProperties.replace("ARTIST", QStringToTString(newProperties.value(Property::Artist).toString()));
427  }
428 
429  if (newProperties.contains(Property::AlbumArtist)) {
430  oldProperties.replace("ALBUMARTIST", QStringToTString(newProperties.value(Property::AlbumArtist).toString()));
431  }
432 
433  if (newProperties.contains(Property::Album)) {
434  oldProperties.replace("ALBUM", QStringToTString(newProperties.value(Property::Album).toString()));
435  }
436 
437  if (newProperties.contains(Property::TrackNumber)) {
438  int trackNumber = newProperties.value(Property::TrackNumber).toInt();
439  //taglib requires uint
440  if (trackNumber >= 0) {
441  oldProperties.replace("TRACKNUMBER", QStringToTString(newProperties.value(Property::TrackNumber).toString()));
442  }
443  }
444 
445  if (newProperties.contains(Property::DiscNumber)) {
446  int discNumber = newProperties.value(Property::DiscNumber).toInt();
447  //taglib requires uint
448  if (discNumber >= 0) {
449  oldProperties.replace("DISCNUMBER", QStringToTString(newProperties.value(Property::DiscNumber).toString()));
450  }
451  }
452 
453  if (newProperties.contains(Property::ReleaseYear)) {
454  int year = newProperties.value(Property::ReleaseYear).toInt();
455  //taglib requires uint
456  if (year >= 0) {
457  oldProperties.replace("DATE", QStringToTString(newProperties.value(Property::ReleaseYear).toString()));
458  }
459  }
460 
461  if (newProperties.contains(Property::Genre)) {
462  oldProperties.replace("GENRE", QStringToTString(newProperties.value(Property::Genre).toString()));
463  }
464 
465  if (newProperties.contains(Property::Comment)) {
466  oldProperties.replace("COMMENT", QStringToTString(newProperties.value(Property::Comment).toString()));
467  }
468 
469  if (newProperties.contains(Property::Composer)) {
470  oldProperties.replace("COMPOSER", QStringToTString(newProperties.value(Property::Composer).toString()));
471  }
472 
473  if (newProperties.contains(Property::Lyricist)) {
474  oldProperties.replace("LYRICIST", QStringToTString(newProperties.value(Property::Lyricist).toString()));
475  }
476 
477  if (newProperties.contains(Property::Conductor)) {
478  oldProperties.replace("CONDUCTOR", QStringToTString(newProperties.value(Property::Conductor).toString()));
479  }
480 
481  if (newProperties.contains(Property::Copyright)) {
482  oldProperties.replace("COPYRIGHT", QStringToTString(newProperties.value(Property::Copyright).toString()));
483  }
484 
485  if (newProperties.contains(Property::Lyrics)) {
486  oldProperties.replace("LYRICS", QStringToTString(newProperties.value(Property::Lyrics).toString()));
487  }
488 
489  if (newProperties.contains(Property::Language)) {
490  oldProperties.replace("LANGUAGE", QStringToTString(newProperties.value(Property::Language).toString()));
491  }
492 }
493 
494 TagLibWriter::TagLibWriter(QObject* parent)
495  : WriterPlugin(parent)
496 {
497 }
498 
499 QStringList TagLibWriter::writeMimetypes() const
500 {
501  return supportedMimeTypes;
502 }
503 
504 void TagLibWriter::write(const WriteData& data)
505 {
506  const QString fileUrl = data.inputUrl();
507  const PropertyMap properties = data.getAllProperties();
508  const QString mimeType = data.inputMimetype();
509 
510 #if defined Q_OS_WINDOWS
511  TagLib::FileStream stream(fileUrl.toLocal8Bit().constData(), false);
512 #else
513  TagLib::FileStream stream(fileUrl.toUtf8().constData(), false);
514 #endif
515  if (!stream.isOpen() || stream.readOnly()) {
516  qCWarning(KFILEMETADATA_LOG) << "Unable to open file in write mode: " << fileUrl;
517  return;
518  }
519 
520  if (mimeType == QLatin1String("audio/mpeg") || mimeType == QLatin1String("audio/mpeg3")
521  || mimeType == QLatin1String("audio/x-mpeg")) {
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());
530  }
531  file.save();
532  }
533  } else if (mimeType == QLatin1String("audio/x-aiff") || mimeType == QLatin1String("audio/x-aifc")) {
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());
540  if (id3Tags) {
541  writeID3v2Tags(id3Tags, properties);
542  writeID3v2Cover(id3Tags, data.imageData());
543  }
544  file.save();
545  }
546  } else if (mimeType == QLatin1String("audio/wav") || mimeType == QLatin1String("audio/x-wav")) {
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());
553  if (id3Tags) {
554  writeID3v2Tags(id3Tags, properties);
555  writeID3v2Cover(id3Tags, data.imageData());
556  }
557  file.save();
558  }
559  } else if (mimeType == QLatin1String("audio/x-musepack")) {
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());
568  }
569  file.save();
570  }
571  } else if (mimeType == QLatin1String("audio/x-ape")) {
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());
580  }
581  file.save();
582  }
583  } else if (mimeType == QLatin1String("audio/x-wavpack")) {
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());
592  }
593  file.save();
594  }
595  } else if (mimeType == QLatin1String("audio/mp4")) {
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());
601  if (mp4Tags) {
602  writeMp4Tags(mp4Tags, properties);
603  writeMp4Cover(mp4Tags, data.imageData());
604  }
605  file.setProperties(savedProperties);
606  file.save();
607  }
608  } else if (mimeType == QLatin1String("audio/flac")) {
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());
616  file.save();
617  }
618  } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) {
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());
626  file.save();
627  }
628  } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) {
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());
636  file.save();
637  }
638  } else if (mimeType == QLatin1String("audio/x-speex+ogg")) {
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());
646  file.save();
647  }
648  } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
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());
655  if (asfTags){
656  writeAsfTags(asfTags, properties);
657  writeAsfCover(asfTags, data.imageData());
658  }
659  file.save();
660  }
661  }
662 }
bool contains(const Key &key) const const
bool empty() 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()
QString mimeType(Type)
QMap::key_value_iterator keyValueEnd()
int size() const const
const T value(const Key &key, const T &defaultValue) const const
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Jan 23 2021 03:15:06 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.