KFileMetaData

taglibwriter.cpp
1 /*
2  * Copyright (C) 2016 Varun Joshi <[email protected]>
3  * Copyright (C) 2018 Alexander Stippich <[email protected]>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  *
19  */
20 
21 #include "taglibwriter.h"
22 #include "kfilemetadata_debug.h"
23 
24 #include <taglib.h>
25 #include <tfilestream.h>
26 #include <tpropertymap.h>
27 #include <tstring.h>
28 #include <aifffile.h>
29 #include <apefile.h>
30 #include <asffile.h>
31 #include <asftag.h>
32 #include <flacfile.h>
33 #include <mp4file.h>
34 #include <mp4tag.h>
35 #include <mpcfile.h>
36 #include <mpegfile.h>
37 #include <id3v2tag.h>
38 #include <oggfile.h>
39 #include <opusfile.h>
40 #include <vorbisfile.h>
41 #include <speexfile.h>
42 #include <wavpackfile.h>
43 #include <wavfile.h>
44 #include <popularimeterframe.h>
45 
46 namespace {
47 
48 const QStringList supportedMimeTypes = {
49  QStringLiteral("audio/flac"),
50  QStringLiteral("audio/mp4"),
51  QStringLiteral("audio/mpeg"),
52  QStringLiteral("audio/mpeg3"),
53  QStringLiteral("audio/ogg"),
54  QStringLiteral("audio/opus"),
55  QStringLiteral("audio/speex"),
56  QStringLiteral("audio/wav"),
57  QStringLiteral("audio/x-aiff"),
58  QStringLiteral("audio/x-aifc"),
59  QStringLiteral("audio/x-ape"),
60  QStringLiteral("audio/x-mpeg"),
61  QStringLiteral("audio/x-ms-wma"),
62  QStringLiteral("audio/x-musepack"),
63  QStringLiteral("audio/x-opus+ogg"),
64  QStringLiteral("audio/x-speex+ogg"),
65  QStringLiteral("audio/x-vorbis+ogg"),
66  QStringLiteral("audio/x-wav"),
67  QStringLiteral("audio/x-wavpack"),
68 };
69 
70 int id3v2RatingTranslation[11] = {
71  0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255
72 };
73 
74 
75 using namespace KFileMetaData;
76 
77 void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags, const PropertyMap &newProperties)
78 {
79  if (newProperties.contains(Property::Rating)) {
80  int rating = newProperties.value(Property::Rating).toInt();
81  if (rating >= 0 && rating <= 10) {
82  id3Tags->removeFrames("POPM");
83  auto ratingFrame = new TagLib::ID3v2::PopularimeterFrame;
84  ratingFrame->setEmail("org.kde.kfilemetadata");
85  ratingFrame->setRating(id3v2RatingTranslation[rating]);
86  id3Tags->addFrame(ratingFrame);
87  }
88  }
89 }
90 
91 void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties)
92 {
93  if (newProperties.contains(Property::Rating)) {
94  oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
95  }
96 }
97 
98 void writeVorbisTags(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties)
99 {
100  if (newProperties.contains(Property::Rating)) {
101  oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10));
102  }
103 }
104 
105 void writeAsfTags(TagLib::ASF::Tag *asfTags, const PropertyMap &properties)
106 {
107  if (properties.contains(Property::Rating)) {
108  //map the rating values of WMP to Baloo rating
109  //0->0, 1->2, 4->25, 6->50, 8->75, 10->99
110  int rating = properties.value(Property::Rating).toInt();
111  if (rating == 0) {
112  rating = 0;
113  } else if (rating <= 2) {
114  rating = 1;
115  } else if (rating == 10){
116  rating = 99;
117  } else {
118  rating = static_cast<int>(12.5 * rating - 25);
119  }
120  asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating));
121  }
122 }
123 
124 void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMap &newProperties)
125 {
126  if (newProperties.contains(Property::Rating)) {
127  mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10)));
128  }
129 }
130 
131 } // anonymous namespace
132 
133 void writeGenericProperties(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties)
134 {
135  if (newProperties.contains(Property::Title)) {
136  oldProperties.replace("TITLE", QStringToTString(newProperties.value(Property::Title).toString()));
137  }
138 
139  if (newProperties.contains(Property::Artist)) {
140  oldProperties.replace("ARTIST", QStringToTString(newProperties.value(Property::Artist).toString()));
141  }
142 
143  if (newProperties.contains(Property::AlbumArtist)) {
144  oldProperties.replace("ALBUMARTIST", QStringToTString(newProperties.value(Property::AlbumArtist).toString()));
145  }
146 
147  if (newProperties.contains(Property::Album)) {
148  oldProperties.replace("ALBUM", QStringToTString(newProperties.value(Property::Album).toString()));
149  }
150 
151  if (newProperties.contains(Property::TrackNumber)) {
152  int trackNumber = newProperties.value(Property::TrackNumber).toInt();
153  //taglib requires uint
154  if (trackNumber >= 0) {
155  oldProperties.replace("TRACKNUMBER", QStringToTString(newProperties.value(Property::TrackNumber).toString()));
156  }
157  }
158 
159  if (newProperties.contains(Property::DiscNumber)) {
160  int discNumber = newProperties.value(Property::DiscNumber).toInt();
161  //taglib requires uint
162  if (discNumber >= 0) {
163  oldProperties.replace("DISCNUMBER", QStringToTString(newProperties.value(Property::DiscNumber).toString()));
164  }
165  }
166 
167  if (newProperties.contains(Property::ReleaseYear)) {
168  int year = newProperties.value(Property::ReleaseYear).toInt();
169  //taglib requires uint
170  if (year >= 0) {
171  oldProperties.replace("DATE", QStringToTString(newProperties.value(Property::ReleaseYear).toString()));
172  }
173  }
174 
175  if (newProperties.contains(Property::Genre)) {
176  oldProperties.replace("GENRE", QStringToTString(newProperties.value(Property::Genre).toString()));
177  }
178 
179  if (newProperties.contains(Property::Comment)) {
180  oldProperties.replace("COMMENT", QStringToTString(newProperties.value(Property::Comment).toString()));
181  }
182 
183  if (newProperties.contains(Property::Composer)) {
184  oldProperties.replace("COMPOSER", QStringToTString(newProperties.value(Property::Composer).toString()));
185  }
186 
187  if (newProperties.contains(Property::Lyricist)) {
188  oldProperties.replace("LYRICIST", QStringToTString(newProperties.value(Property::Lyricist).toString()));
189  }
190 
191  if (newProperties.contains(Property::Conductor)) {
192  oldProperties.replace("CONDUCTOR", QStringToTString(newProperties.value(Property::Conductor).toString()));
193  }
194 
195  if (newProperties.contains(Property::Copyright)) {
196  oldProperties.replace("COPYRIGHT", QStringToTString(newProperties.value(Property::Copyright).toString()));
197  }
198 
199  if (newProperties.contains(Property::Lyrics)) {
200  oldProperties.replace("LYRICS", QStringToTString(newProperties.value(Property::Lyrics).toString()));
201  }
202 
203  if (newProperties.contains(Property::Language)) {
204  oldProperties.replace("LANGUAGE", QStringToTString(newProperties.value(Property::Language).toString()));
205  }
206 }
207 
208 TagLibWriter::TagLibWriter(QObject* parent)
209  : WriterPlugin(parent)
210 {
211 }
212 
213 QStringList TagLibWriter::writeMimetypes() const
214 {
215  return supportedMimeTypes;
216 }
217 
218 void TagLibWriter::write(const WriteData& data)
219 {
220  const QString fileUrl = data.inputUrl();
221  const PropertyMap properties = data.getAllProperties();
222  const QString mimeType = data.inputMimetype();
223 
224 #if defined Q_OS_WINDOWS
225  TagLib::FileStream stream(fileUrl.toLocal8Bit().constData(), true);
226 #else
227  TagLib::FileStream stream(fileUrl.toUtf8().constData(), false);
228 #endif
229  if (!stream.isOpen()) {
230  qCWarning(KFILEMETADATA_LOG) << "Unable to open file in write mode: " << fileUrl;
231  return;
232  }
233 
234  if (mimeType == QLatin1String("audio/mpeg") || mimeType == QLatin1String("audio/mpeg3")
235  || mimeType == QLatin1String("audio/x-mpeg")) {
236  TagLib::MPEG::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false);
237  if (file.isValid()) {
238  auto savedProperties = file.properties();
239  writeGenericProperties(savedProperties, properties);
240  file.setProperties(savedProperties);
241  if (file.hasID3v2Tag()) {
242  writeID3v2Tags(file.ID3v2Tag(), properties);
243  }
244  file.save();
245  }
246  } else if (mimeType == QLatin1String("audio/x-aiff") || mimeType == QLatin1String("audio/x-aifc")) {
247  TagLib::RIFF::AIFF::File file(&stream, false);
248  if (file.isValid()) {
249  auto savedProperties = file.properties();
250  writeGenericProperties(savedProperties, properties);
251  file.setProperties(savedProperties);
252  auto id3Tags = dynamic_cast<TagLib::ID3v2::Tag*>(file.tag());
253  if (id3Tags) {
254  writeID3v2Tags(id3Tags, properties);
255  }
256  file.save();
257  }
258  } else if (mimeType == QLatin1String("audio/wav") || mimeType == QLatin1String("audio/x-wav")) {
259  TagLib::RIFF::WAV::File file(&stream, false);
260  if (file.isValid()) {
261  auto savedProperties = file.properties();
262  writeGenericProperties(savedProperties, properties);
263  file.setProperties(savedProperties);
264  auto id3Tags = dynamic_cast<TagLib::ID3v2::Tag*>(file.tag());
265  if (id3Tags) {
266  writeID3v2Tags(id3Tags, properties);
267  }
268  file.save();
269  }
270  } else if (mimeType == QLatin1String("audio/x-musepack")) {
271  TagLib::MPC::File file(&stream, false);
272  if (file.isValid()) {
273  auto savedProperties = file.properties();
274  writeGenericProperties(savedProperties, properties);
275  writeApeTags(savedProperties, properties);
276  file.setProperties(savedProperties);
277  file.save();
278  }
279  } else if (mimeType == QLatin1String("audio/x-ape")) {
280  TagLib::APE::File file(&stream, false);
281  if (file.isValid()) {
282  auto savedProperties = file.properties();
283  writeGenericProperties(savedProperties, properties);
284  writeApeTags(savedProperties, properties);
285  file.setProperties(savedProperties);
286  file.save();
287  }
288  } else if (mimeType == QLatin1String("audio/x-wavpack")) {
289  TagLib::WavPack::File file(&stream, false);
290  if (file.isValid()) {
291  auto savedProperties = file.properties();
292  writeGenericProperties(savedProperties, properties);
293  writeApeTags(savedProperties, properties);
294  file.setProperties(savedProperties);
295  file.save();
296  }
297  } else if (mimeType == QLatin1String("audio/mp4")) {
298  TagLib::MP4::File file(&stream, false);
299  if (file.isValid()) {
300  auto savedProperties = file.properties();
301  writeGenericProperties(savedProperties, properties);
302  auto mp4Tags = dynamic_cast<TagLib::MP4::Tag*>(file.tag());
303  if (mp4Tags) {
304  writeMp4Tags(mp4Tags, properties);
305  }
306  file.setProperties(savedProperties);
307  file.save();
308  }
309  } else if (mimeType == QLatin1String("audio/flac")) {
310  TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false);
311  if (file.isValid()) {
312  auto savedProperties = file.properties();
313  writeGenericProperties(savedProperties, properties);
314  writeVorbisTags(savedProperties, properties);
315  file.setProperties(savedProperties);
316  file.save();
317  }
318  } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) {
319  TagLib::Ogg::Vorbis::File file(&stream, false);
320  if (file.isValid()) {
321  auto savedProperties = file.properties();
322  writeGenericProperties(savedProperties, properties);
323  writeVorbisTags(savedProperties, properties);
324  file.setProperties(savedProperties);
325  file.save();
326  }
327  } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) {
328  TagLib::Ogg::Opus::File file(&stream, false);
329  if (file.isValid()) {
330  auto savedProperties = file.properties();
331  writeGenericProperties(savedProperties, properties);
332  writeVorbisTags(savedProperties, properties);
333  file.setProperties(savedProperties);
334  file.save();
335  }
336  } else if (mimeType == QLatin1String("audio/speex") || mimeType == QLatin1String("audio/x-speex+ogg")) {
337  TagLib::Ogg::Speex::File file(&stream, false);
338  if (file.isValid()) {
339  auto savedProperties = file.properties();
340  writeGenericProperties(savedProperties, properties);
341  writeVorbisTags(savedProperties, properties);
342  file.setProperties(savedProperties);
343  file.save();
344  }
345  } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
346  TagLib::ASF::File file(&stream, false);
347  if (file.isValid()) {
348  auto savedProperties = file.properties();
349  writeGenericProperties(savedProperties, properties);
350  file.setProperties(savedProperties);
351  auto asfTags = dynamic_cast<TagLib::ASF::Tag*>(file.tag());
352  if (asfTags){
353  writeAsfTags(asfTags, properties);
354  }
355  file.save();
356  }
357  }
358 }
bool contains(const Key &key) const const
const char * constData() const const
QByteArray toLocal8Bit() const const
QString mimeType(Type)
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-2020 The KDE developers.
Generated on Wed Jun 3 2020 22:57:37 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.