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 PropertyMultiMap &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  // ID3v2::Tag::addFrame takes ownership
163  auto ratingFrame = new TagLib::ID3v2::PopularimeterFrame;
164  ratingFrame->setEmail("org.kde.kfilemetadata");
165  ratingFrame->setRating(id3v2RatingTranslation[rating]);
166  id3Tags->addFrame(ratingFrame);
167  }
168  }
169 }
170 
171 void writeID3v2Cover(TagLib::ID3v2::Tag *id3Tags,
173 {
174  EmbeddedImageData::ImageTypes wantedTypes;
175  EmbeddedImageData::ImageTypes removeTypes;
176  std::for_each(images.keyValueBegin(),images.keyValueEnd(),
177  [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
178  if (it.second.isEmpty()) {
179  removeTypes |= it.first;
180  } else {
181  wantedTypes |= it.first;
182  }
183  });
184 
185  using PictureFrame = TagLib::ID3v2::AttachedPictureFrame;
186  auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
187  wantedTypes &= ~kfmType;
188  auto newCover = images[kfmType];
189  auto newMimeType = determineMimeType(newCover);
190  if (!newMimeType.isEmpty()) {
191  coverFrame->setPicture(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
192  coverFrame->setMimeType(newMimeType);
193  }
194  };
195 
196  // Update existing covers
197  TagLib::ID3v2::FrameList lstID3v2 = id3Tags->frameListMap()["APIC"];
198  for (auto& frame : std::as_const(lstID3v2)) {
199  auto* coverFrame = static_cast<PictureFrame *>(frame);
200  const auto kfmType = mapTaglibType<PictureFrame::Type>(coverFrame->type());
201  if (kfmType & wantedTypes) {
202  updateFrame(coverFrame, kfmType);
203  } else if (kfmType & removeTypes) {
204  id3Tags->removeFrame(coverFrame);
205  }
206  }
207  // Add new covers
208  for (const auto type : allImageTypes<PictureFrame>) {
209  const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
210  if (kfmType & wantedTypes) {
211  // ID3v2::Tag::addFrame takes ownership
212  auto* coverFrame = new PictureFrame;
213  coverFrame->setType(type);
214  updateFrame(coverFrame, kfmType);
215  id3Tags->addFrame(coverFrame);
216  }
217  }
218 }
219 
220 // Instantiated for either FLAC::File or
221 // Ogg::XiphComment (Ogg::*::File::tag())
222 template<typename Container>
223 void writeFlacCover(Container* tags,
225 {
226  EmbeddedImageData::ImageTypes wantedTypes;
227  EmbeddedImageData::ImageTypes removeTypes;
228  std::for_each(images.keyValueBegin(),images.keyValueEnd(),
229  [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
230  if (it.second.isEmpty()) {
231  removeTypes |= it.first;
232  } else {
233  wantedTypes |= it.first;
234  }
235  });
236 
237  using PictureFrame = TagLib::FLAC::Picture;
238  auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
239  wantedTypes &= ~kfmType;
240  auto newCover = images[kfmType];
241  auto newMimeType = determineMimeType(newCover);
242  if (!newMimeType.isEmpty()) {
243  coverFrame->setData(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
244  coverFrame->setMimeType(newMimeType);
245  }
246  };
247 
248  // Update existing covers
249  auto lstPic = tags->pictureList();
250  for (auto it = lstPic.begin(); it != lstPic.end(); ++it) {
251  const auto kfmType = mapTaglibType<PictureFrame::Type>((*it)->type());
252  if (kfmType & wantedTypes) {
253  updateFrame((*it), kfmType);
254  } else if (kfmType & removeTypes) {
255  tags->removePicture(*it);
256  }
257  }
258  // Add new covers
259  for (const auto type : allImageTypes<PictureFrame>) {
260  const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
261  if (kfmType & wantedTypes) {
262  // FLAC::File::addPicture takes ownership (dito XiphComment)
263  auto* coverFrame = new PictureFrame;
264  coverFrame->setType(type);
265  updateFrame(coverFrame, kfmType);
266  tags->addPicture(coverFrame);
267  }
268  }
269 }
270 
271 void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
272 {
273  if (newProperties.contains(Property::Rating)) {
274  oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
275  }
276 }
277 
278 void writeApeCover(TagLib::APE::Tag* apeTags,
280 {
281  if (images.empty()) {
282  return;
283  }
284  auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
285  if ((images.size() > 1) || (imageIt == images.constEnd())) {
286  // TODO warn
287  }
288  if (imageIt == images.constEnd()) {
289  return;
290  }
291 
292  const auto newCover = *imageIt;
293  if (newCover.isEmpty()) {
294  apeTags->removeItem("COVER ART (FRONT)");
295  return;
296  }
297 
298  TagLib::ByteVector imageData;
299  if (determineMimeType(newCover) == TagLib::String("image/png")) {
300  imageData.setData("frontCover.png\0", 15);
301  } else {
302  imageData.setData("frontCover.jpeg\0", 16);
303  }
304  imageData.append(TagLib::ByteVector(newCover.constData(), newCover.size()));
305  apeTags->setData("COVER ART (FRONT)", imageData);
306 }
307 
308 void writeVorbisTags(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
309 {
310  if (newProperties.contains(Property::Rating)) {
311  oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
312  }
313 }
314 
315 void writeAsfTags(TagLib::ASF::Tag *asfTags, const PropertyMultiMap &properties)
316 {
317  if (properties.contains(Property::Rating)) {
318  //map the rating values of WMP to Baloo rating
319  //0->0, 1->2, 4->25, 6->50, 8->75, 10->99
320  int rating = properties.value(Property::Rating).toInt();
321  if (rating == 0) {
322  rating = 0;
323  } else if (rating <= 2) {
324  rating = 1;
325  } else if (rating == 10){
326  rating = 99;
327  } else {
328  rating = static_cast<int>(12.5 * rating - 25);
329  }
330  asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating));
331  }
332 }
333 
334 void writeAsfCover(TagLib::ASF::Tag* asfTags,
336 {
337  EmbeddedImageData::ImageTypes wantedTypes;
338  EmbeddedImageData::ImageTypes removeTypes;
339  std::for_each(images.keyValueBegin(),images.keyValueEnd(),
340  [&](const std::pair<EmbeddedImageData::ImageType, QByteArray> it) {
341  if (it.second.isEmpty()) {
342  removeTypes |= it.first;
343  } else {
344  wantedTypes |= it.first;
345  }
346  });
347 
348  using PictureFrame = TagLib::ASF::Picture;
349  auto updateFrame = [&wantedTypes, &images](PictureFrame* coverFrame, const EmbeddedImageData::ImageType kfmType) {
350  wantedTypes &= ~kfmType;
351  auto newCover = images[kfmType];
352  auto newMimeType = determineMimeType(newCover);
353  if (!newMimeType.isEmpty()) {
354  coverFrame->setPicture(TagLib::ByteVector(static_cast<const char *>(newCover.data()), newCover.size()));
355  coverFrame->setMimeType(newMimeType);
356  }
357  };
358 
359  // Update existing covers
360  TagLib::ASF::AttributeList lstPic = asfTags->attribute("WM/Picture");
361  for (auto it = lstPic.begin(); it != lstPic.end(); ) {
362  PictureFrame picture = (*it).toPicture();
363  const auto kfmType = mapTaglibType<PictureFrame::Type>(picture.type());
364  if (kfmType & wantedTypes) {
365  updateFrame(&picture, kfmType);
366  ++it;
367  } else if (kfmType & removeTypes) {
368  it = lstPic.erase(it);
369  } else {
370  ++it;
371  }
372  }
373  // Add new covers
374  for (const auto type : allImageTypes<PictureFrame>) {
375  const auto kfmType = mapTaglibType<PictureFrame::Type>(type);
376  if (kfmType & wantedTypes) {
377  PictureFrame coverFrame;
378  updateFrame(&coverFrame, kfmType);
379  coverFrame.setType(type);
380  lstPic.append(coverFrame);
381  }
382  }
383  asfTags->setAttribute("WM/Picture", lstPic);
384 }
385 void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMultiMap &newProperties)
386 {
387  if (newProperties.contains(Property::Rating)) {
388  mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10)));
389  }
390 }
391 
392 void writeMp4Cover(TagLib::MP4::Tag *mp4Tags,
394 {
395  if (images.empty()) {
396  return;
397  }
398  auto imageIt = images.constFind(EmbeddedImageData::FrontCover);
399  if ((images.size() > 1) || (imageIt == images.constEnd())) {
400  // TODO warn
401  }
402  if (imageIt == images.constEnd()) {
403  return;
404  }
405 
406  TagLib::MP4::CoverArtList coverArtList;
407  const auto newCover = *imageIt;
408  if (!newCover.isEmpty()) {
409  TagLib::ByteVector imageData(newCover.data(), newCover.size());
410  TagLib::MP4::CoverArt coverArt(TagLib::MP4::CoverArt::Format::Unknown, imageData);
411  coverArtList.append(coverArt);
412  }
413  mp4Tags->setItem("covr", coverArtList);
414 }
415 
416 } // anonymous namespace
417 
418 void writeGenericProperties(TagLib::PropertyMap &oldProperties, const PropertyMultiMap &newProperties)
419 {
420  if (newProperties.empty()) {
421  return;
422  }
423 
424  if (newProperties.contains(Property::Title)) {
425  oldProperties.replace("TITLE", QStringToTString(newProperties.value(Property::Title).toString()));
426  }
427 
428  if (newProperties.contains(Property::Artist)) {
429  oldProperties.replace("ARTIST", QStringToTString(newProperties.value(Property::Artist).toString()));
430  }
431 
432  if (newProperties.contains(Property::AlbumArtist)) {
433  oldProperties.replace("ALBUMARTIST", QStringToTString(newProperties.value(Property::AlbumArtist).toString()));
434  }
435 
436  if (newProperties.contains(Property::Album)) {
437  oldProperties.replace("ALBUM", QStringToTString(newProperties.value(Property::Album).toString()));
438  }
439 
440  if (newProperties.contains(Property::TrackNumber)) {
441  int trackNumber = newProperties.value(Property::TrackNumber).toInt();
442  //taglib requires uint
443  if (trackNumber >= 0) {
444  oldProperties.replace("TRACKNUMBER", QStringToTString(newProperties.value(Property::TrackNumber).toString()));
445  }
446  }
447 
448  if (newProperties.contains(Property::DiscNumber)) {
449  int discNumber = newProperties.value(Property::DiscNumber).toInt();
450  //taglib requires uint
451  if (discNumber >= 0) {
452  oldProperties.replace("DISCNUMBER", QStringToTString(newProperties.value(Property::DiscNumber).toString()));
453  }
454  }
455 
456  if (newProperties.contains(Property::ReleaseYear)) {
457  int year = newProperties.value(Property::ReleaseYear).toInt();
458  //taglib requires uint
459  if (year >= 0) {
460  oldProperties.replace("DATE", QStringToTString(newProperties.value(Property::ReleaseYear).toString()));
461  }
462  }
463 
464  if (newProperties.contains(Property::Genre)) {
465  oldProperties.replace("GENRE", QStringToTString(newProperties.value(Property::Genre).toString()));
466  }
467 
468  if (newProperties.contains(Property::Comment)) {
469  oldProperties.replace("COMMENT", QStringToTString(newProperties.value(Property::Comment).toString()));
470  }
471 
472  if (newProperties.contains(Property::Composer)) {
473  oldProperties.replace("COMPOSER", QStringToTString(newProperties.value(Property::Composer).toString()));
474  }
475 
476  if (newProperties.contains(Property::Lyricist)) {
477  oldProperties.replace("LYRICIST", QStringToTString(newProperties.value(Property::Lyricist).toString()));
478  }
479 
480  if (newProperties.contains(Property::Conductor)) {
481  oldProperties.replace("CONDUCTOR", QStringToTString(newProperties.value(Property::Conductor).toString()));
482  }
483 
484  if (newProperties.contains(Property::Copyright)) {
485  oldProperties.replace("COPYRIGHT", QStringToTString(newProperties.value(Property::Copyright).toString()));
486  }
487 
488  if (newProperties.contains(Property::Lyrics)) {
489  oldProperties.replace("LYRICS", QStringToTString(newProperties.value(Property::Lyrics).toString()));
490  }
491 
492  if (newProperties.contains(Property::Language)) {
493  oldProperties.replace("LANGUAGE", QStringToTString(newProperties.value(Property::Language).toString()));
494  }
495 }
496 
497 TagLibWriter::TagLibWriter(QObject* parent)
498  : WriterPlugin(parent)
499 {
500 }
501 
502 QStringList TagLibWriter::writeMimetypes() const
503 {
504  return supportedMimeTypes;
505 }
506 
507 void TagLibWriter::write(const WriteData& data)
508 {
509  const QString fileUrl = data.inputUrl();
510  const PropertyMultiMap properties = data.properties();
511  const QString mimeType = data.inputMimetype();
512 
513 #if defined Q_OS_WINDOWS
514  TagLib::FileStream stream(fileUrl.toLocal8Bit().constData(), false);
515 #else
516  TagLib::FileStream stream(fileUrl.toUtf8().constData(), false);
517 #endif
518  if (!stream.isOpen() || stream.readOnly()) {
519  qCWarning(KFILEMETADATA_LOG) << "Unable to open file in write mode: " << fileUrl;
520  return;
521  }
522 
523  if (mimeType == QLatin1String("audio/mpeg") || mimeType == QLatin1String("audio/mpeg3")
524  || mimeType == QLatin1String("audio/x-mpeg")) {
525  TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false);
526  if (file.isValid()) {
527  auto savedProperties = file.properties();
528  writeGenericProperties(savedProperties, properties);
529  file.setProperties(savedProperties);
530  if (file.hasID3v2Tag()) {
531  writeID3v2Tags(file.ID3v2Tag(), properties);
532  writeID3v2Cover(file.ID3v2Tag(), data.imageData());
533  }
534  file.save();
535  }
536  } else if (mimeType == QLatin1String("audio/x-aiff") || mimeType == QLatin1String("audio/x-aifc")) {
537  TagLib::RIFF::AIFF::File file(&stream, false);
538  if (file.isValid()) {
539  auto savedProperties = file.properties();
540  writeGenericProperties(savedProperties, properties);
541  file.setProperties(savedProperties);
542  auto id3Tags = dynamic_cast<TagLib::ID3v2::Tag*>(file.tag());
543  if (id3Tags) {
544  writeID3v2Tags(id3Tags, properties);
545  writeID3v2Cover(id3Tags, data.imageData());
546  }
547  file.save();
548  }
549  } else if (mimeType == QLatin1String("audio/wav") || 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 = dynamic_cast<TagLib::ID3v2::Tag*>(file.tag());
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/opus") || 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 }
int size() const const
QMap::key_value_iterator keyValueEnd()
bool empty() const const
const T value(const Key &key, const T &defaultValue) const const
KCALUTILS_EXPORT QString mimeType()
QMap::const_iterator constFind(const Key &key) const const
QMap::const_iterator constEnd() const const
QByteArray toUtf8() const const
bool startsWith(const QByteArray &ba) const const
KGuiItem properties()
const char * constData() const const
QMap::key_value_iterator keyValueBegin()
QByteArray toLocal8Bit() const const
bool contains(const Key &key, const T &value) const const
QByteArray fromHex(const QByteArray &hexEncoded)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu May 26 2022 03:46:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.