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