Changeset View
Changeset View
Standalone View
Standalone View
src/writers/taglibwriter.cpp
Show All 15 Lines | |||||
16 | * License along with this library; if not, write to the Free Software | 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 | 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
18 | * | 18 | * | ||
19 | */ | 19 | */ | ||
20 | 20 | | |||
21 | #include "taglibwriter.h" | 21 | #include "taglibwriter.h" | ||
22 | #include "kfilemetadata_debug.h" | 22 | #include "kfilemetadata_debug.h" | ||
23 | 23 | | |||
24 | #include <QMimeDatabase> | ||||
25 | #include <QMimeType> | ||||
26 | | ||||
24 | #include <taglib.h> | 27 | #include <taglib.h> | ||
25 | #include <tfilestream.h> | 28 | #include <tfilestream.h> | ||
26 | #include <tpropertymap.h> | 29 | #include <tpropertymap.h> | ||
27 | #include <tstring.h> | 30 | #include <tstring.h> | ||
28 | #include <aifffile.h> | 31 | #include <aifffile.h> | ||
29 | #include <apefile.h> | 32 | #include <apefile.h> | ||
33 | #include <apetag.h> | ||||
30 | #include <asffile.h> | 34 | #include <asffile.h> | ||
31 | #include <asftag.h> | 35 | #include <asftag.h> | ||
32 | #include <flacfile.h> | 36 | #include <flacfile.h> | ||
33 | #include <mp4file.h> | 37 | #include <mp4file.h> | ||
34 | #include <mp4tag.h> | 38 | #include <mp4tag.h> | ||
35 | #include <mpcfile.h> | 39 | #include <mpcfile.h> | ||
36 | #include <mpegfile.h> | 40 | #include <mpegfile.h> | ||
37 | #include <id3v2tag.h> | 41 | #include <id3v2tag.h> | ||
Show All 28 Lines | 52 | const QStringList supportedMimeTypes = { | |||
66 | QStringLiteral("audio/x-wav"), | 70 | QStringLiteral("audio/x-wav"), | ||
67 | QStringLiteral("audio/x-wavpack"), | 71 | QStringLiteral("audio/x-wavpack"), | ||
68 | }; | 72 | }; | ||
69 | 73 | | |||
70 | int id3v2RatingTranslation[11] = { | 74 | int id3v2RatingTranslation[11] = { | ||
71 | 0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255 | 75 | 0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255 | ||
72 | }; | 76 | }; | ||
73 | 77 | | |||
74 | | ||||
75 | using namespace KFileMetaData; | 78 | using namespace KFileMetaData; | ||
76 | 79 | | |||
80 | TagLib::String determineMimeTypeOfPicture(const QByteArray &pictureData) | ||||
bruns: https://doc.qt.io/qt-5/qmimedatabase.html#mimeTypeForData | |||||
81 | { | ||||
82 | const QMimeDatabase mimeDb; | ||||
83 | return QStringToTString(mimeDb.mimeTypeForData(pictureData).name()); | ||||
84 | } | ||||
85 | | ||||
77 | void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags, const PropertyMap &newProperties) | 86 | void writeID3v2Tags(TagLib::ID3v2::Tag *id3Tags, const PropertyMap &newProperties) | ||
78 | { | 87 | { | ||
This list is incomplete - there are also plain jpegs which start with a huffmann table, and Adobe apparent writes ones which start with an FFEE application marker. bruns: This list is incomplete - there are also plain jpegs which start with a huffmann table, and… | |||||
79 | if (newProperties.contains(Property::Rating)) { | 88 | if (newProperties.contains(Property::Rating)) { | ||
80 | int rating = newProperties.value(Property::Rating).toInt(); | 89 | int rating = newProperties.value(Property::Rating).toInt(); | ||
81 | if (rating >= 0 && rating <= 10) { | 90 | if (rating >= 0 && rating <= 10) { | ||
82 | id3Tags->removeFrames("POPM"); | 91 | id3Tags->removeFrames("POPM"); | ||
83 | auto ratingFrame = new TagLib::ID3v2::PopularimeterFrame; | 92 | auto ratingFrame = new TagLib::ID3v2::PopularimeterFrame; | ||
84 | ratingFrame->setEmail("org.kde.kfilemetadata"); | 93 | ratingFrame->setEmail("org.kde.kfilemetadata"); | ||
85 | ratingFrame->setRating(id3v2RatingTranslation[rating]); | 94 | ratingFrame->setRating(id3v2RatingTranslation[rating]); | ||
86 | id3Tags->addFrame(ratingFrame); | 95 | id3Tags->addFrame(ratingFrame); | ||
87 | } | 96 | } | ||
88 | } | 97 | } | ||
98 | if (newProperties.contains(Property::FrontCover)) { | ||||
99 | // Try to update existing front cover frame first | ||||
100 | TagLib::ID3v2::FrameList lstID3v2; | ||||
101 | lstID3v2 = id3Tags->frameListMap()["APIC"]; | ||||
102 | const auto pictureData = newProperties.value(Property::FrontCover).toByteArray(); | ||||
103 | for (auto& frame : qAsConst(lstID3v2)) | ||||
104 | { | ||||
105 | auto *frontCoverFrame = static_cast<TagLib::ID3v2::AttachedPictureFrame *>(frame); | ||||
106 | if (frontCoverFrame->type() == frontCoverFrame->FrontCover) { | ||||
107 | frontCoverFrame->setPicture(TagLib::ByteVector(static_cast<const char *>(pictureData.data()), pictureData.size())); | ||||
108 | frontCoverFrame->setMimeType(determineMimeTypeOfPicture(pictureData)); | ||||
109 | return; | ||||
110 | } | ||||
111 | } | ||||
112 | auto pictureFrame = new TagLib::ID3v2::AttachedPictureFrame; | ||||
113 | pictureFrame->setPicture(TagLib::ByteVector(pictureData.data(), pictureData.size())); | ||||
114 | pictureFrame->setType(pictureFrame->FrontCover); | ||||
115 | pictureFrame->setMimeType(determineMimeTypeOfPicture(pictureData)); | ||||
116 | id3Tags->addFrame(pictureFrame); | ||||
117 | } | ||||
89 | } | 118 | } | ||
90 | 119 | | |||
91 | void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties) | 120 | void writeApeTags(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties) | ||
92 | { | 121 | { | ||
93 | if (newProperties.contains(Property::Rating)) { | 122 | if (newProperties.contains(Property::Rating)) { | ||
94 | oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10)); | 123 | oldProperties.replace("RATING", TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10)); | ||
95 | } | 124 | } | ||
96 | } | 125 | } | ||
Show All 17 Lines | 142 | } else if (rating <= 2) { | |||
114 | rating = 1; | 143 | rating = 1; | ||
115 | } else if (rating == 10){ | 144 | } else if (rating == 10){ | ||
116 | rating = 99; | 145 | rating = 99; | ||
117 | } else { | 146 | } else { | ||
118 | rating = static_cast<int>(12.5 * rating - 25); | 147 | rating = static_cast<int>(12.5 * rating - 25); | ||
119 | } | 148 | } | ||
120 | asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating)); | 149 | asfTags->setAttribute("WM/SharedUserRating", TagLib::String::number(rating)); | ||
121 | } | 150 | } | ||
151 | if (properties.contains(Property::FrontCover)) { | ||||
152 | TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/Picture"); | ||||
153 | const auto pictureData = properties.value(Property::FrontCover).toByteArray(); | ||||
154 | for (const auto& attribute : qAsConst(lstASF)) { | ||||
155 | TagLib::ASF::Picture picture = attribute.toPicture(); | ||||
156 | if (picture.type() == TagLib::ASF::Picture::FrontCover) { | ||||
157 | picture.setPicture(TagLib::ByteVector(pictureData.constData(), pictureData.size())); | ||||
158 | picture.setMimeType(determineMimeTypeOfPicture(pictureData)); | ||||
159 | TagLib::ByteVector pictureData = picture.picture(); | ||||
160 | return; | ||||
161 | } | ||||
162 | } | ||||
163 | TagLib::ASF::Picture picture; | ||||
164 | picture.setPicture(TagLib::ByteVector(pictureData.constData(), pictureData.size())); | ||||
165 | picture.setType(TagLib::ASF::Picture::FrontCover); | ||||
166 | lstASF.append(picture); | ||||
167 | } | ||||
122 | } | 168 | } | ||
123 | 169 | | |||
124 | void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMap &newProperties) | 170 | void writeMp4Tags(TagLib::MP4::Tag *mp4Tags, const PropertyMap &newProperties) | ||
125 | { | 171 | { | ||
126 | if (newProperties.contains(Property::Rating)) { | 172 | if (newProperties.contains(Property::Rating)) { | ||
127 | mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10))); | 173 | mp4Tags->setItem("rate", TagLib::StringList(TagLib::String::number(newProperties.value(Property::Rating).toInt() * 10))); | ||
128 | } | 174 | } | ||
175 | if (newProperties.contains(Property::FrontCover)) { | ||||
176 | TagLib::MP4::Item coverArtItem = mp4Tags->item("covr"); | ||||
177 | TagLib::MP4::CoverArtList coverArtList; | ||||
178 | const auto pictureData = newProperties.value(Property::FrontCover).toByteArray(); | ||||
179 | TagLib::MP4::CoverArt coverArt(TagLib::MP4::CoverArt::Format::Unknown, TagLib::ByteVector(pictureData.data(), pictureData.size())); | ||||
180 | if (coverArtItem.isValid()) | ||||
181 | { | ||||
182 | coverArtList = coverArtItem.toCoverArtList(); | ||||
183 | coverArtList.clear(); | ||||
184 | } | ||||
185 | coverArtList.append(coverArt); | ||||
186 | mp4Tags->setItem("covr", coverArtList); | ||||
187 | } | ||||
188 | } | ||||
189 | | ||||
190 | void writeFlacPicture(TagLib::List<TagLib::FLAC::Picture *> lstPic, const PropertyMap &newProperties) { | ||||
191 | if (newProperties.contains(Property::FrontCover)) { | ||||
192 | const auto pictureData = newProperties.value(Property::FrontCover).toByteArray(); | ||||
193 | for (const auto &picture : qAsConst(lstPic)) { | ||||
194 | if (picture->type() == picture->FrontCover) { | ||||
195 | picture->setData(TagLib::ByteVector(pictureData.data(), pictureData.size())); | ||||
196 | picture->setMimeType(determineMimeTypeOfPicture(pictureData)); | ||||
197 | return ; | ||||
198 | } | ||||
199 | } | ||||
200 | auto flacPicture = new TagLib::FLAC::Picture; | ||||
201 | flacPicture->setMimeType(determineMimeTypeOfPicture(pictureData)); | ||||
202 | flacPicture->setData(TagLib::ByteVector(pictureData.data(), pictureData.size())); | ||||
203 | lstPic.append(flacPicture); | ||||
204 | } | ||||
205 | } | ||||
206 | | ||||
207 | void writeApePicture(TagLib::APE::Tag* apeTags, const PropertyMap &newProperties) { | ||||
208 | if (newProperties.contains(Property::FrontCover)) { | ||||
209 | /* The cover art tag for APEv2 tags starts with the filename as string | ||||
210 | * with zero termination followed by the actual picture data */ | ||||
211 | TagLib::ByteVector imageData; | ||||
212 | TagLib::String name; | ||||
213 | const auto pictureData = newProperties.value(Property::FrontCover).toByteArray(); | ||||
214 | if (determineMimeTypeOfPicture(pictureData) == TagLib::String("image/png")) { | ||||
215 | name = "frontCover.png"; | ||||
216 | } else { | ||||
217 | name = "frontCover.jpeg"; | ||||
218 | } | ||||
219 | imageData.append(name.data(TagLib::String::UTF8)); | ||||
220 | imageData.append('\0'); | ||||
221 | imageData.append(TagLib::ByteVector(pictureData.constData(), pictureData.size())); | ||||
222 | apeTags->setData("COVER ART (FRONT)", imageData); | ||||
223 | } | ||||
129 | } | 224 | } | ||
130 | 225 | | |||
131 | } // anonymous namespace | 226 | } // anonymous namespace | ||
132 | 227 | | |||
133 | void writeGenericProperties(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties) | 228 | void writeGenericProperties(TagLib::PropertyMap &oldProperties, const PropertyMap &newProperties) | ||
134 | { | 229 | { | ||
135 | if (newProperties.contains(Property::Title)) { | 230 | if (newProperties.contains(Property::Title)) { | ||
136 | oldProperties.replace("TITLE", QStringToTString(newProperties.value(Property::Title).toString())); | 231 | oldProperties.replace("TITLE", QStringToTString(newProperties.value(Property::Title).toString())); | ||
▲ Show 20 Lines • Show All 131 Lines • ▼ Show 20 Line(s) | 355 | if (file.isValid()) { | |||
268 | file.save(); | 363 | file.save(); | ||
269 | } | 364 | } | ||
270 | } else if (mimeType == QLatin1String("audio/x-musepack")) { | 365 | } else if (mimeType == QLatin1String("audio/x-musepack")) { | ||
271 | TagLib::MPC::File file(&stream, false); | 366 | TagLib::MPC::File file(&stream, false); | ||
272 | if (file.isValid()) { | 367 | if (file.isValid()) { | ||
273 | auto savedProperties = file.properties(); | 368 | auto savedProperties = file.properties(); | ||
274 | writeGenericProperties(savedProperties, properties); | 369 | writeGenericProperties(savedProperties, properties); | ||
275 | writeApeTags(savedProperties, properties); | 370 | writeApeTags(savedProperties, properties); | ||
371 | if (file.hasAPETag()) { | ||||
372 | writeApePicture(file.APETag(), properties); | ||||
373 | } | ||||
276 | file.setProperties(savedProperties); | 374 | file.setProperties(savedProperties); | ||
277 | file.save(); | 375 | file.save(); | ||
278 | } | 376 | } | ||
279 | } else if (mimeType == QLatin1String("audio/x-ape")) { | 377 | } else if (mimeType == QLatin1String("audio/x-ape")) { | ||
280 | TagLib::APE::File file(&stream, false); | 378 | TagLib::APE::File file(&stream, false); | ||
281 | if (file.isValid()) { | 379 | if (file.isValid()) { | ||
282 | auto savedProperties = file.properties(); | 380 | auto savedProperties = file.properties(); | ||
283 | writeGenericProperties(savedProperties, properties); | 381 | writeGenericProperties(savedProperties, properties); | ||
284 | writeApeTags(savedProperties, properties); | 382 | writeApeTags(savedProperties, properties); | ||
383 | if (file.hasAPETag()) { | ||||
384 | writeApePicture(file.APETag(), properties); | ||||
385 | } | ||||
285 | file.setProperties(savedProperties); | 386 | file.setProperties(savedProperties); | ||
286 | file.save(); | 387 | file.save(); | ||
287 | } | 388 | } | ||
288 | } else if (mimeType == QLatin1String("audio/x-wavpack")) { | 389 | } else if (mimeType == QLatin1String("audio/x-wavpack")) { | ||
289 | TagLib::WavPack::File file(&stream, false); | 390 | TagLib::WavPack::File file(&stream, false); | ||
290 | if (file.isValid()) { | 391 | if (file.isValid()) { | ||
291 | auto savedProperties = file.properties(); | 392 | auto savedProperties = file.properties(); | ||
292 | writeGenericProperties(savedProperties, properties); | 393 | writeGenericProperties(savedProperties, properties); | ||
293 | writeApeTags(savedProperties, properties); | 394 | writeApeTags(savedProperties, properties); | ||
395 | if (file.hasAPETag()) { | ||||
396 | writeApePicture(file.APETag(), properties); | ||||
397 | } | ||||
294 | file.setProperties(savedProperties); | 398 | file.setProperties(savedProperties); | ||
295 | file.save(); | 399 | file.save(); | ||
296 | } | 400 | } | ||
297 | } else if (mimeType == QLatin1String("audio/mp4")) { | 401 | } else if (mimeType == QLatin1String("audio/mp4")) { | ||
298 | TagLib::MP4::File file(&stream, false); | 402 | TagLib::MP4::File file(&stream, false); | ||
299 | if (file.isValid()) { | 403 | if (file.isValid()) { | ||
300 | auto savedProperties = file.properties(); | 404 | auto savedProperties = file.properties(); | ||
301 | writeGenericProperties(savedProperties, properties); | 405 | writeGenericProperties(savedProperties, properties); | ||
302 | auto mp4Tags = dynamic_cast<TagLib::MP4::Tag*>(file.tag()); | 406 | auto mp4Tags = dynamic_cast<TagLib::MP4::Tag*>(file.tag()); | ||
303 | if (mp4Tags) { | 407 | if (mp4Tags) { | ||
304 | writeMp4Tags(mp4Tags, properties); | 408 | writeMp4Tags(mp4Tags, properties); | ||
305 | } | 409 | } | ||
306 | file.setProperties(savedProperties); | 410 | file.setProperties(savedProperties); | ||
307 | file.save(); | 411 | file.save(); | ||
308 | } | 412 | } | ||
309 | } else if (mimeType == QLatin1String("audio/flac")) { | 413 | } else if (mimeType == QLatin1String("audio/flac")) { | ||
310 | TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false); | 414 | TagLib::FLAC::File file(&stream, TagLib::ID3v2::FrameFactory::instance(), false); | ||
311 | if (file.isValid()) { | 415 | if (file.isValid()) { | ||
312 | auto savedProperties = file.properties(); | 416 | auto savedProperties = file.properties(); | ||
313 | writeGenericProperties(savedProperties, properties); | 417 | writeGenericProperties(savedProperties, properties); | ||
314 | writeVorbisTags(savedProperties, properties); | 418 | writeVorbisTags(savedProperties, properties); | ||
419 | writeFlacPicture(file.pictureList(), properties); | ||||
315 | file.setProperties(savedProperties); | 420 | file.setProperties(savedProperties); | ||
316 | file.save(); | 421 | file.save(); | ||
317 | } | 422 | } | ||
318 | } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) { | 423 | } else if (mimeType == QLatin1String("audio/ogg") || mimeType == QLatin1String("audio/x-vorbis+ogg")) { | ||
319 | TagLib::Ogg::Vorbis::File file(&stream, false); | 424 | TagLib::Ogg::Vorbis::File file(&stream, false); | ||
320 | if (file.isValid()) { | 425 | if (file.isValid()) { | ||
321 | auto savedProperties = file.properties(); | 426 | auto savedProperties = file.properties(); | ||
322 | writeGenericProperties(savedProperties, properties); | 427 | writeGenericProperties(savedProperties, properties); | ||
323 | writeVorbisTags(savedProperties, properties); | 428 | writeVorbisTags(savedProperties, properties); | ||
429 | if (file.tag()) { | ||||
430 | writeFlacPicture(file.tag()->pictureList(), properties); | ||||
431 | } | ||||
324 | file.setProperties(savedProperties); | 432 | file.setProperties(savedProperties); | ||
325 | file.save(); | 433 | file.save(); | ||
326 | } | 434 | } | ||
327 | } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) { | 435 | } else if (mimeType == QLatin1String("audio/opus") || mimeType == QLatin1String("audio/x-opus+ogg")) { | ||
328 | TagLib::Ogg::Opus::File file(&stream, false); | 436 | TagLib::Ogg::Opus::File file(&stream, false); | ||
329 | if (file.isValid()) { | 437 | if (file.isValid()) { | ||
330 | auto savedProperties = file.properties(); | 438 | auto savedProperties = file.properties(); | ||
331 | writeGenericProperties(savedProperties, properties); | 439 | writeGenericProperties(savedProperties, properties); | ||
332 | writeVorbisTags(savedProperties, properties); | 440 | writeVorbisTags(savedProperties, properties); | ||
441 | if (file.tag()) { | ||||
442 | writeFlacPicture(file.tag()->pictureList(), properties); | ||||
443 | } | ||||
333 | file.setProperties(savedProperties); | 444 | file.setProperties(savedProperties); | ||
334 | file.save(); | 445 | file.save(); | ||
335 | } | 446 | } | ||
336 | } else if (mimeType == QLatin1String("audio/speex") || mimeType == QLatin1String("audio/x-speex+ogg")) { | 447 | } else if (mimeType == QLatin1String("audio/speex") || mimeType == QLatin1String("audio/x-speex+ogg")) { | ||
337 | TagLib::Ogg::Speex::File file(&stream, false); | 448 | TagLib::Ogg::Speex::File file(&stream, false); | ||
338 | if (file.isValid()) { | 449 | if (file.isValid()) { | ||
339 | auto savedProperties = file.properties(); | 450 | auto savedProperties = file.properties(); | ||
340 | writeGenericProperties(savedProperties, properties); | 451 | writeGenericProperties(savedProperties, properties); | ||
341 | writeVorbisTags(savedProperties, properties); | 452 | writeVorbisTags(savedProperties, properties); | ||
453 | if (file.tag()) { | ||||
454 | writeFlacPicture(file.tag()->pictureList(), properties); | ||||
455 | } | ||||
342 | file.setProperties(savedProperties); | 456 | file.setProperties(savedProperties); | ||
343 | file.save(); | 457 | file.save(); | ||
344 | } | 458 | } | ||
345 | } else if (mimeType == QLatin1String("audio/x-ms-wma")) { | 459 | } else if (mimeType == QLatin1String("audio/x-ms-wma")) { | ||
346 | TagLib::ASF::File file(&stream, false); | 460 | TagLib::ASF::File file(&stream, false); | ||
347 | if (file.isValid()) { | 461 | if (file.isValid()) { | ||
348 | auto savedProperties = file.properties(); | 462 | auto savedProperties = file.properties(); | ||
349 | writeGenericProperties(savedProperties, properties); | 463 | writeGenericProperties(savedProperties, properties); | ||
Show All 9 Lines |
https://doc.qt.io/qt-5/qmimedatabase.html#mimeTypeForData