/src/kio-extras/thumbnail/audiocreator.cpp
Line | Count | Source |
1 | | |
2 | | /* |
3 | | * This file is part of the KDE KIO-Extras project |
4 | | * SPDX-FileCopyrightText: 2009 Vytautas Mickus <vmickus@gmail.com> |
5 | | * SPDX-FileCopyrightText: 2016 Anthony Fieroni <bvbfan@abv.com> |
6 | | * |
7 | | * SPDX-License-Identifier: LGPL-2.1-only |
8 | | */ |
9 | | |
10 | | #include "audiocreator.h" |
11 | | |
12 | | #include <QFile> |
13 | | #include <QImage> |
14 | | #include <QMimeDatabase> |
15 | | #include <QMimeType> |
16 | | |
17 | | #include <KPluginFactory> |
18 | | |
19 | | #include <aifffile.h> |
20 | | #include <apefile.h> |
21 | | #include <apetag.h> |
22 | | #include <attachedpictureframe.h> |
23 | | #include <fileref.h> |
24 | | #include <flacfile.h> |
25 | | #include <flacpicture.h> |
26 | | #include <id3v2tag.h> |
27 | | #include <mp4file.h> |
28 | | #include <mp4tag.h> |
29 | | #include <mpcfile.h> |
30 | | #include <mpegfile.h> |
31 | | #include <wavfile.h> |
32 | | #include <wavpackfile.h> |
33 | | #include <xiphcomment.h> |
34 | | |
35 | 0 | K_PLUGIN_CLASS_WITH_JSON(AudioCreator, "audiothumbnail.json") Unexecuted instantiation: audiothumbnail_factory::tr(char const*, char const*, int) Unexecuted instantiation: audiothumbnail_factory::~audiothumbnail_factory() |
36 | 0 |
|
37 | 0 | AudioCreator::AudioCreator(QObject *parent, const QVariantList &args) |
38 | 19.6k | : KIO::ThumbnailCreator(parent, args) |
39 | 19.6k | { |
40 | 19.6k | } |
41 | | |
42 | | AudioCreator::~AudioCreator() |
43 | 19.6k | { |
44 | 19.6k | } |
45 | | |
46 | | namespace TagLib |
47 | | { |
48 | | namespace RIFF |
49 | | { |
50 | | namespace AIFF |
51 | | { |
52 | | struct FileExt : public File { |
53 | | using File::File; |
54 | | ID3v2::Tag *ID3v2Tag() const |
55 | 114 | { |
56 | 114 | return tag(); |
57 | 114 | } |
58 | | }; |
59 | | } |
60 | | } |
61 | | } |
62 | | |
63 | | template<class T> |
64 | | static KIO::ThumbnailResult parseID3v2Tag(T &file) |
65 | 15.2k | { |
66 | 15.2k | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { |
67 | 1.01k | return KIO::ThumbnailResult::fail(); |
68 | 1.01k | } |
69 | 14.2k | const auto &map = file.ID3v2Tag()->frameListMap(); |
70 | 14.2k | if (map["APIC"].isEmpty()) { |
71 | 2.94k | return KIO::ThumbnailResult::fail(); |
72 | 2.94k | } |
73 | 11.3k | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); |
74 | 11.3k | if (!apicFrame) { |
75 | 12 | return KIO::ThumbnailResult::fail(); |
76 | 12 | } |
77 | 11.3k | const auto coverData = apicFrame->picture(); |
78 | 11.3k | QImage img; |
79 | 11.3k | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); |
80 | 11.3k | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); |
81 | 11.3k | } audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::MPEG::File>(TagLib::MPEG::File&) Line | Count | Source | 65 | 14.4k | { | 66 | 14.4k | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 300 | return KIO::ThumbnailResult::fail(); | 68 | 300 | } | 69 | 14.1k | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 14.1k | if (map["APIC"].isEmpty()) { | 71 | 2.91k | return KIO::ThumbnailResult::fail(); | 72 | 2.91k | } | 73 | 11.2k | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 11.2k | if (!apicFrame) { | 75 | 10 | return KIO::ThumbnailResult::fail(); | 76 | 10 | } | 77 | 11.2k | const auto coverData = apicFrame->picture(); | 78 | 11.2k | QImage img; | 79 | 11.2k | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 11.2k | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 11.2k | } |
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::FLAC::File>(TagLib::FLAC::File&) Line | Count | Source | 65 | 297 | { | 66 | 297 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 297 | return KIO::ThumbnailResult::fail(); | 68 | 297 | } | 69 | 0 | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 0 | if (map["APIC"].isEmpty()) { | 71 | 0 | return KIO::ThumbnailResult::fail(); | 72 | 0 | } | 73 | 0 | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 0 | if (!apicFrame) { | 75 | 0 | return KIO::ThumbnailResult::fail(); | 76 | 0 | } | 77 | 0 | const auto coverData = apicFrame->picture(); | 78 | 0 | QImage img; | 79 | 0 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 0 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 0 | } |
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::RIFF::AIFF::FileExt>(TagLib::RIFF::AIFF::FileExt&) Line | Count | Source | 65 | 153 | { | 66 | 153 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 96 | return KIO::ThumbnailResult::fail(); | 68 | 96 | } | 69 | 57 | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 57 | if (map["APIC"].isEmpty()) { | 71 | 12 | return KIO::ThumbnailResult::fail(); | 72 | 12 | } | 73 | 45 | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 45 | if (!apicFrame) { | 75 | 1 | return KIO::ThumbnailResult::fail(); | 76 | 1 | } | 77 | 44 | const auto coverData = apicFrame->picture(); | 78 | 44 | QImage img; | 79 | 44 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 44 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 45 | } |
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::RIFF::WAV::File>(TagLib::RIFF::WAV::File&) Line | Count | Source | 65 | 337 | { | 66 | 337 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 318 | return KIO::ThumbnailResult::fail(); | 68 | 318 | } | 69 | 19 | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 19 | if (map["APIC"].isEmpty()) { | 71 | 17 | return KIO::ThumbnailResult::fail(); | 72 | 17 | } | 73 | 2 | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 2 | if (!apicFrame) { | 75 | 1 | return KIO::ThumbnailResult::fail(); | 76 | 1 | } | 77 | 1 | const auto coverData = apicFrame->picture(); | 78 | 1 | QImage img; | 79 | 1 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 1 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 2 | } |
|
82 | | |
83 | | template<class T> |
84 | | static KIO::ThumbnailResult parseFlacTag(T &file) |
85 | 329 | { |
86 | 329 | const auto pictureList = file.pictureList(); |
87 | 329 | for (const auto &picture : pictureList) { |
88 | 107 | if (picture->type() != TagLib::FLAC::Picture::FrontCover) { |
89 | 107 | continue; |
90 | 107 | } |
91 | 0 | const auto coverData = picture->data(); |
92 | 0 | QImage img; |
93 | 0 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); |
94 | 0 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); |
95 | 107 | } |
96 | 329 | return KIO::ThumbnailResult::fail(); |
97 | 329 | } audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::FLAC::File>(TagLib::FLAC::File&) Line | Count | Source | 85 | 297 | { | 86 | 297 | const auto pictureList = file.pictureList(); | 87 | 297 | for (const auto &picture : pictureList) { | 88 | 107 | if (picture->type() != TagLib::FLAC::Picture::FrontCover) { | 89 | 107 | continue; | 90 | 107 | } | 91 | 0 | const auto coverData = picture->data(); | 92 | 0 | QImage img; | 93 | 0 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 94 | 0 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 95 | 107 | } | 96 | 297 | return KIO::ThumbnailResult::fail(); | 97 | 297 | } |
audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::Ogg::XiphComment>(TagLib::Ogg::XiphComment&) Line | Count | Source | 85 | 32 | { | 86 | 32 | const auto pictureList = file.pictureList(); | 87 | 32 | for (const auto &picture : pictureList) { | 88 | 0 | if (picture->type() != TagLib::FLAC::Picture::FrontCover) { | 89 | 0 | continue; | 90 | 0 | } | 91 | 0 | const auto coverData = picture->data(); | 92 | 0 | QImage img; | 93 | 0 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 94 | 0 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 95 | 0 | } | 96 | 32 | return KIO::ThumbnailResult::fail(); | 97 | 32 | } |
|
98 | | |
99 | | template<class T> |
100 | | static KIO::ThumbnailResult parseMP4Tag(T &file) |
101 | 373 | { |
102 | 373 | if (!file.hasMP4Tag() || !file.tag()) { |
103 | 373 | return KIO::ThumbnailResult::fail(); |
104 | 373 | } |
105 | 0 | const auto &map = file.tag()->itemMap(); |
106 | 0 | for (const auto &coverList : map) { |
107 | 0 | auto coverArtList = coverList.second.toCoverArtList(); |
108 | 0 | if (coverArtList.isEmpty()) { |
109 | 0 | continue; |
110 | 0 | } |
111 | 0 | const auto coverData = coverArtList[0].data(); |
112 | 0 | QImage img; |
113 | 0 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); |
114 | 0 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); |
115 | 0 | } |
116 | 0 | return KIO::ThumbnailResult::fail(); |
117 | 0 | } |
118 | | |
119 | | template<class T> |
120 | | static KIO::ThumbnailResult parseAPETag(T &file) |
121 | 14.6k | { |
122 | 14.6k | if (!file.hasAPETag() || !file.APETag()) { |
123 | 14.2k | return KIO::ThumbnailResult::fail(); |
124 | 14.2k | } |
125 | 320 | const auto &map = file.APETag()->itemListMap(); |
126 | 548 | for (const auto &item : map) { |
127 | 548 | if (item.second.type() != TagLib::APE::Item::Binary) { |
128 | 370 | continue; |
129 | 370 | } |
130 | 178 | const auto coverData = item.second.binaryData(); |
131 | 178 | const auto data = coverData.data(); |
132 | 178 | const auto size = coverData.size(); |
133 | 532 | for (size_t i = 0; i < size; ++i) { |
134 | 422 | if (data[i] == '\0' && (i + 1) < size) { |
135 | 68 | const auto start = data + i + 1; |
136 | 68 | QImage img; |
137 | 68 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); |
138 | 68 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); |
139 | 0 | ; |
140 | 0 | } |
141 | 422 | } |
142 | 178 | } |
143 | 252 | return KIO::ThumbnailResult::fail(); |
144 | 320 | } audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPEG::File>(TagLib::MPEG::File&) Line | Count | Source | 121 | 13.6k | { | 122 | 13.6k | if (!file.hasAPETag() || !file.APETag()) { | 123 | 13.4k | return KIO::ThumbnailResult::fail(); | 124 | 13.4k | } | 125 | 158 | const auto &map = file.APETag()->itemListMap(); | 126 | 190 | for (const auto &item : map) { | 127 | 190 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 94 | continue; | 129 | 94 | } | 130 | 96 | const auto coverData = item.second.binaryData(); | 131 | 96 | const auto data = coverData.data(); | 132 | 96 | const auto size = coverData.size(); | 133 | 203 | for (size_t i = 0; i < size; ++i) { | 134 | 152 | if (data[i] == '\0' && (i + 1) < size) { | 135 | 45 | const auto start = data + i + 1; | 136 | 45 | QImage img; | 137 | 45 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); | 138 | 45 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 139 | 0 | ; | 140 | 0 | } | 141 | 152 | } | 142 | 96 | } | 143 | 113 | return KIO::ThumbnailResult::fail(); | 144 | 158 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::APE::File>(TagLib::APE::File&) Line | Count | Source | 121 | 256 | { | 122 | 256 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 200 | return KIO::ThumbnailResult::fail(); | 124 | 200 | } | 125 | 56 | const auto &map = file.APETag()->itemListMap(); | 126 | 86 | for (const auto &item : map) { | 127 | 86 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 60 | continue; | 129 | 60 | } | 130 | 26 | const auto coverData = item.second.binaryData(); | 131 | 26 | const auto data = coverData.data(); | 132 | 26 | const auto size = coverData.size(); | 133 | 103 | for (size_t i = 0; i < size; ++i) { | 134 | 84 | if (data[i] == '\0' && (i + 1) < size) { | 135 | 7 | const auto start = data + i + 1; | 136 | 7 | QImage img; | 137 | 7 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); | 138 | 7 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 139 | 0 | ; | 140 | 0 | } | 141 | 84 | } | 142 | 26 | } | 143 | 49 | return KIO::ThumbnailResult::fail(); | 144 | 56 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::WavPack::File>(TagLib::WavPack::File&) Line | Count | Source | 121 | 468 | { | 122 | 468 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 415 | return KIO::ThumbnailResult::fail(); | 124 | 415 | } | 125 | 53 | const auto &map = file.APETag()->itemListMap(); | 126 | 111 | for (const auto &item : map) { | 127 | 111 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 89 | continue; | 129 | 89 | } | 130 | 22 | const auto coverData = item.second.binaryData(); | 131 | 22 | const auto data = coverData.data(); | 132 | 22 | const auto size = coverData.size(); | 133 | 104 | for (size_t i = 0; i < size; ++i) { | 134 | 92 | if (data[i] == '\0' && (i + 1) < size) { | 135 | 10 | const auto start = data + i + 1; | 136 | 10 | QImage img; | 137 | 10 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); | 138 | 10 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 139 | 0 | ; | 140 | 0 | } | 141 | 92 | } | 142 | 22 | } | 143 | 43 | return KIO::ThumbnailResult::fail(); | 144 | 53 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPC::File>(TagLib::MPC::File&) Line | Count | Source | 121 | 264 | { | 122 | 264 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 211 | return KIO::ThumbnailResult::fail(); | 124 | 211 | } | 125 | 53 | const auto &map = file.APETag()->itemListMap(); | 126 | 161 | for (const auto &item : map) { | 127 | 161 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 127 | continue; | 129 | 127 | } | 130 | 34 | const auto coverData = item.second.binaryData(); | 131 | 34 | const auto data = coverData.data(); | 132 | 34 | const auto size = coverData.size(); | 133 | 122 | for (size_t i = 0; i < size; ++i) { | 134 | 94 | if (data[i] == '\0' && (i + 1) < size) { | 135 | 6 | const auto start = data + i + 1; | 136 | 6 | QImage img; | 137 | 6 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); | 138 | 6 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 139 | 0 | ; | 140 | 0 | } | 141 | 94 | } | 142 | 34 | } | 143 | 47 | return KIO::ThumbnailResult::fail(); | 144 | 53 | } |
|
145 | | |
146 | | KIO::ThumbnailResult AudioCreator::create(const KIO::ThumbnailRequest &request) |
147 | 19.6k | { |
148 | 19.6k | QMimeDatabase db; |
149 | 19.6k | QMimeType type = db.mimeTypeForName(request.mimeType()); |
150 | | |
151 | 19.6k | const QByteArray fileNameBytes = QFile::encodeName(request.url().toLocalFile()); |
152 | 19.6k | const char *fileName = fileNameBytes.constData(); |
153 | | |
154 | 19.6k | if (!type.isValid()) { |
155 | 0 | return KIO::ThumbnailResult::fail(); |
156 | 0 | } |
157 | | |
158 | 19.6k | if (type.inherits("audio/mpeg")) { |
159 | 14.4k | TagLib::MPEG::File file(fileName); |
160 | | |
161 | 14.4k | if (auto result = parseID3v2Tag(file); result.isValid()) { |
162 | 864 | return result; |
163 | 864 | } |
164 | | |
165 | 13.6k | return parseAPETag(file); |
166 | 14.4k | } |
167 | 5.12k | if (type.inherits("audio/x-flac") || type.inherits("audio/flac")) { |
168 | 297 | TagLib::FLAC::File file(fileName); |
169 | | |
170 | 297 | if (auto result = parseFlacTag(file); result.isValid()) { |
171 | 0 | return result; |
172 | 0 | } |
173 | | |
174 | 297 | return parseID3v2Tag(file); |
175 | 297 | } |
176 | 4.82k | if (type.inherits("audio/mp4") || type.inherits("audio/x-m4a") || type.inherits("audio/vnd.audible.aax")) { |
177 | 373 | TagLib::MP4::File file(fileName); |
178 | 373 | return parseMP4Tag(file); |
179 | 373 | } |
180 | 4.45k | if (type.inherits("audio/x-ape")) { |
181 | 256 | TagLib::APE::File file(fileName); |
182 | 256 | return parseAPETag(file); |
183 | 256 | } |
184 | 4.19k | if (type.inherits("audio/x-wavpack") || type.inherits("audio/x-vw")) { |
185 | 468 | TagLib::WavPack::File file(fileName); |
186 | 468 | return parseAPETag(file); |
187 | 468 | } |
188 | 3.73k | if (type.inherits("audio/x-musepack")) { |
189 | 264 | TagLib::MPC::File file(fileName); |
190 | 264 | return parseAPETag(file); |
191 | 264 | } |
192 | 3.46k | if (type.inherits("audio/ogg") || type.inherits("audio/vorbis")) { |
193 | 1.66k | TagLib::FileRef fileRef(fileName); |
194 | 1.66k | if (fileRef.isNull()) { |
195 | 290 | return KIO::ThumbnailResult::fail(); |
196 | 290 | } |
197 | 1.37k | auto xiphComment = dynamic_cast<TagLib::Ogg::XiphComment *>(fileRef.tag()); |
198 | 1.37k | if (!xiphComment || xiphComment->isEmpty()) { |
199 | 1.34k | return KIO::ThumbnailResult::fail(); |
200 | 1.34k | } |
201 | 32 | return parseFlacTag(*xiphComment); |
202 | 1.37k | } |
203 | 1.80k | if (type.inherits("audio/x-aiff") || type.inherits("audio/x-aifc")) { |
204 | 153 | TagLib::RIFF::AIFF::FileExt file(fileName); |
205 | 153 | return parseID3v2Tag(file); |
206 | 153 | } |
207 | 1.65k | if (type.inherits("audio/x-wav")) { |
208 | 337 | TagLib::RIFF::WAV::File file(fileName); |
209 | 337 | return parseID3v2Tag(file); |
210 | 337 | } |
211 | 1.31k | return KIO::ThumbnailResult::fail(); |
212 | 1.65k | } |
213 | | |
214 | | #include "audiocreator.moc" |
215 | | #include "moc_audiocreator.cpp" |