/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 | 17.0k | : KIO::ThumbnailCreator(parent, args) |
39 | 17.0k | { |
40 | 17.0k | } |
41 | | |
42 | | AudioCreator::~AudioCreator() |
43 | 17.0k | { |
44 | 17.0k | } |
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 | 160 | { |
56 | 160 | return tag(); |
57 | 160 | } |
58 | | }; |
59 | | } |
60 | | } |
61 | | } |
62 | | |
63 | | template<class T> |
64 | | static KIO::ThumbnailResult parseID3v2Tag(T &file) |
65 | 11.1k | { |
66 | 11.1k | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { |
67 | 864 | return KIO::ThumbnailResult::fail(); |
68 | 864 | } |
69 | 10.2k | const auto &map = file.ID3v2Tag()->frameListMap(); |
70 | 10.2k | if (map["APIC"].isEmpty()) { |
71 | 2.72k | return KIO::ThumbnailResult::fail(); |
72 | 2.72k | } |
73 | 7.54k | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); |
74 | 7.54k | if (!apicFrame) { |
75 | 19 | return KIO::ThumbnailResult::fail(); |
76 | 19 | } |
77 | 7.52k | const auto coverData = apicFrame->picture(); |
78 | 7.52k | QImage img; |
79 | 7.52k | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); |
80 | 7.52k | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); |
81 | 7.54k | } audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::MPEG::File>(TagLib::MPEG::File&) Line | Count | Source | 65 | 10.4k | { | 66 | 10.4k | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 289 | return KIO::ThumbnailResult::fail(); | 68 | 289 | } | 69 | 10.1k | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 10.1k | if (map["APIC"].isEmpty()) { | 71 | 2.69k | return KIO::ThumbnailResult::fail(); | 72 | 2.69k | } | 73 | 7.46k | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 7.46k | if (!apicFrame) { | 75 | 17 | return KIO::ThumbnailResult::fail(); | 76 | 17 | } | 77 | 7.45k | const auto coverData = apicFrame->picture(); | 78 | 7.45k | QImage img; | 79 | 7.45k | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 7.45k | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 7.46k | } |
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::FLAC::File>(TagLib::FLAC::File&) Line | Count | Source | 65 | 294 | { | 66 | 294 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 294 | return KIO::ThumbnailResult::fail(); | 68 | 294 | } | 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 | 129 | { | 66 | 129 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 49 | return KIO::ThumbnailResult::fail(); | 68 | 49 | } | 69 | 80 | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 80 | if (map["APIC"].isEmpty()) { | 71 | 9 | return KIO::ThumbnailResult::fail(); | 72 | 9 | } | 73 | 71 | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 71 | if (!apicFrame) { | 75 | 1 | return KIO::ThumbnailResult::fail(); | 76 | 1 | } | 77 | 70 | const auto coverData = apicFrame->picture(); | 78 | 70 | QImage img; | 79 | 70 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 70 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 71 | } |
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::RIFF::WAV::File>(TagLib::RIFF::WAV::File&) Line | Count | Source | 65 | 257 | { | 66 | 257 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 232 | return KIO::ThumbnailResult::fail(); | 68 | 232 | } | 69 | 25 | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 25 | if (map["APIC"].isEmpty()) { | 71 | 22 | return KIO::ThumbnailResult::fail(); | 72 | 22 | } | 73 | 3 | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 3 | if (!apicFrame) { | 75 | 1 | return KIO::ThumbnailResult::fail(); | 76 | 1 | } | 77 | 2 | const auto coverData = apicFrame->picture(); | 78 | 2 | QImage img; | 79 | 2 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 2 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 3 | } |
|
82 | | |
83 | | template<class T> |
84 | | static KIO::ThumbnailResult parseFlacTag(T &file) |
85 | 332 | { |
86 | 332 | const auto pictureList = file.pictureList(); |
87 | 332 | for (const auto &picture : pictureList) { |
88 | 98 | if (picture->type() != TagLib::FLAC::Picture::FrontCover) { |
89 | 98 | continue; |
90 | 98 | } |
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 | 98 | } |
96 | 332 | return KIO::ThumbnailResult::fail(); |
97 | 332 | } audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::FLAC::File>(TagLib::FLAC::File&) Line | Count | Source | 85 | 294 | { | 86 | 294 | const auto pictureList = file.pictureList(); | 87 | 294 | for (const auto &picture : pictureList) { | 88 | 98 | if (picture->type() != TagLib::FLAC::Picture::FrontCover) { | 89 | 98 | continue; | 90 | 98 | } | 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 | 98 | } | 96 | 294 | return KIO::ThumbnailResult::fail(); | 97 | 294 | } |
audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::Ogg::XiphComment>(TagLib::Ogg::XiphComment&) Line | Count | Source | 85 | 38 | { | 86 | 38 | const auto pictureList = file.pictureList(); | 87 | 38 | 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 | 38 | return KIO::ThumbnailResult::fail(); | 97 | 38 | } |
|
98 | | |
99 | | template<class T> |
100 | | static KIO::ThumbnailResult parseMP4Tag(T &file) |
101 | 375 | { |
102 | 375 | if (!file.hasMP4Tag() || !file.tag()) { |
103 | 375 | return KIO::ThumbnailResult::fail(); |
104 | 375 | } |
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 | 11.0k | { |
122 | 11.0k | if (!file.hasAPETag() || !file.APETag()) { |
123 | 10.7k | return KIO::ThumbnailResult::fail(); |
124 | 10.7k | } |
125 | 352 | const auto &map = file.APETag()->itemListMap(); |
126 | 580 | for (const auto &item : map) { |
127 | 580 | if (item.second.type() != TagLib::APE::Item::Binary) { |
128 | 424 | continue; |
129 | 424 | } |
130 | 156 | const auto coverData = item.second.binaryData(); |
131 | 156 | const auto data = coverData.data(); |
132 | 156 | const auto size = coverData.size(); |
133 | 596 | for (size_t i = 0; i < size; ++i) { |
134 | 494 | if (data[i] == '\0' && (i + 1) < size) { |
135 | 54 | const auto start = data + i + 1; |
136 | 54 | QImage img; |
137 | 54 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); |
138 | 54 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); |
139 | 0 | ; |
140 | 0 | } |
141 | 494 | } |
142 | 156 | } |
143 | 298 | return KIO::ThumbnailResult::fail(); |
144 | 352 | } audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPEG::File>(TagLib::MPEG::File&) Line | Count | Source | 121 | 9.86k | { | 122 | 9.86k | if (!file.hasAPETag() || !file.APETag()) { | 123 | 9.70k | return KIO::ThumbnailResult::fail(); | 124 | 9.70k | } | 125 | 166 | const auto &map = file.APETag()->itemListMap(); | 126 | 166 | for (const auto &item : map) { | 127 | 156 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 99 | continue; | 129 | 99 | } | 130 | 57 | const auto coverData = item.second.binaryData(); | 131 | 57 | const auto data = coverData.data(); | 132 | 57 | const auto size = coverData.size(); | 133 | 174 | for (size_t i = 0; i < size; ++i) { | 134 | 146 | if (data[i] == '\0' && (i + 1) < size) { | 135 | 29 | const auto start = data + i + 1; | 136 | 29 | QImage img; | 137 | 29 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); | 138 | 29 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 139 | 0 | ; | 140 | 0 | } | 141 | 146 | } | 142 | 57 | } | 143 | 137 | return KIO::ThumbnailResult::fail(); | 144 | 166 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::APE::File>(TagLib::APE::File&) Line | Count | Source | 121 | 265 | { | 122 | 265 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 206 | return KIO::ThumbnailResult::fail(); | 124 | 206 | } | 125 | 59 | const auto &map = file.APETag()->itemListMap(); | 126 | 95 | for (const auto &item : map) { | 127 | 95 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 67 | continue; | 129 | 67 | } | 130 | 28 | const auto coverData = item.second.binaryData(); | 131 | 28 | const auto data = coverData.data(); | 132 | 28 | const auto size = coverData.size(); | 133 | 117 | for (size_t i = 0; i < size; ++i) { | 134 | 96 | 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 | 96 | } | 142 | 28 | } | 143 | 52 | return KIO::ThumbnailResult::fail(); | 144 | 59 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::WavPack::File>(TagLib::WavPack::File&) Line | Count | Source | 121 | 573 | { | 122 | 573 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 513 | return KIO::ThumbnailResult::fail(); | 124 | 513 | } | 125 | 60 | const auto &map = file.APETag()->itemListMap(); | 126 | 133 | for (const auto &item : map) { | 127 | 133 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 102 | continue; | 129 | 102 | } | 130 | 31 | const auto coverData = item.second.binaryData(); | 131 | 31 | const auto data = coverData.data(); | 132 | 31 | const auto size = coverData.size(); | 133 | 134 | for (size_t i = 0; i < size; ++i) { | 134 | 114 | if (data[i] == '\0' && (i + 1) < size) { | 135 | 11 | const auto start = data + i + 1; | 136 | 11 | QImage img; | 137 | 11 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); | 138 | 11 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 139 | 0 | ; | 140 | 0 | } | 141 | 114 | } | 142 | 31 | } | 143 | 49 | return KIO::ThumbnailResult::fail(); | 144 | 60 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPC::File>(TagLib::MPC::File&) Line | Count | Source | 121 | 355 | { | 122 | 355 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 288 | return KIO::ThumbnailResult::fail(); | 124 | 288 | } | 125 | 67 | const auto &map = file.APETag()->itemListMap(); | 126 | 196 | for (const auto &item : map) { | 127 | 196 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 156 | continue; | 129 | 156 | } | 130 | 40 | const auto coverData = item.second.binaryData(); | 131 | 40 | const auto data = coverData.data(); | 132 | 40 | const auto size = coverData.size(); | 133 | 171 | for (size_t i = 0; i < size; ++i) { | 134 | 138 | 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 | 138 | } | 142 | 40 | } | 143 | 60 | return KIO::ThumbnailResult::fail(); | 144 | 67 | } |
|
145 | | |
146 | | KIO::ThumbnailResult AudioCreator::create(const KIO::ThumbnailRequest &request) |
147 | 17.0k | { |
148 | 17.0k | QMimeDatabase db; |
149 | 17.0k | QMimeType type = db.mimeTypeForName(request.mimeType()); |
150 | | |
151 | 17.0k | const QByteArray fileNameBytes = QFile::encodeName(request.url().toLocalFile()); |
152 | 17.0k | const char *fileName = fileNameBytes.constData(); |
153 | | |
154 | 17.0k | if (!type.isValid()) { |
155 | 0 | return KIO::ThumbnailResult::fail(); |
156 | 0 | } |
157 | | |
158 | 17.0k | if (type.inherits("audio/mpeg")) { |
159 | 10.4k | TagLib::MPEG::File file(fileName); |
160 | | |
161 | 10.4k | if (auto result = parseID3v2Tag(file); result.isValid()) { |
162 | 585 | return result; |
163 | 585 | } |
164 | | |
165 | 9.86k | return parseAPETag(file); |
166 | 10.4k | } |
167 | 6.56k | if (type.inherits("audio/x-flac") || type.inherits("audio/flac")) { |
168 | 294 | TagLib::FLAC::File file(fileName); |
169 | | |
170 | 294 | if (auto result = parseFlacTag(file); result.isValid()) { |
171 | 0 | return result; |
172 | 0 | } |
173 | | |
174 | 294 | return parseID3v2Tag(file); |
175 | 294 | } |
176 | 6.27k | if (type.inherits("audio/mp4") || type.inherits("audio/x-m4a") || type.inherits("audio/vnd.audible.aax")) { |
177 | 375 | TagLib::MP4::File file(fileName); |
178 | 375 | return parseMP4Tag(file); |
179 | 375 | } |
180 | 5.89k | if (type.inherits("audio/x-ape")) { |
181 | 265 | TagLib::APE::File file(fileName); |
182 | 265 | return parseAPETag(file); |
183 | 265 | } |
184 | 5.63k | if (type.inherits("audio/x-wavpack") || type.inherits("audio/x-vw")) { |
185 | 573 | TagLib::WavPack::File file(fileName); |
186 | 573 | return parseAPETag(file); |
187 | 573 | } |
188 | 5.06k | if (type.inherits("audio/x-musepack")) { |
189 | 355 | TagLib::MPC::File file(fileName); |
190 | 355 | return parseAPETag(file); |
191 | 355 | } |
192 | 4.70k | if (type.inherits("audio/ogg") || type.inherits("audio/vorbis")) { |
193 | 2.53k | TagLib::FileRef fileRef(fileName); |
194 | 2.53k | if (fileRef.isNull()) { |
195 | 318 | return KIO::ThumbnailResult::fail(); |
196 | 318 | } |
197 | 2.21k | auto xiphComment = dynamic_cast<TagLib::Ogg::XiphComment *>(fileRef.tag()); |
198 | 2.21k | if (!xiphComment || xiphComment->isEmpty()) { |
199 | 2.17k | return KIO::ThumbnailResult::fail(); |
200 | 2.17k | } |
201 | 38 | return parseFlacTag(*xiphComment); |
202 | 2.21k | } |
203 | 2.17k | if (type.inherits("audio/x-aiff") || type.inherits("audio/x-aifc")) { |
204 | 129 | TagLib::RIFF::AIFF::FileExt file(fileName); |
205 | 129 | return parseID3v2Tag(file); |
206 | 129 | } |
207 | 2.04k | if (type.inherits("audio/x-wav")) { |
208 | 257 | TagLib::RIFF::WAV::File file(fileName); |
209 | 257 | return parseID3v2Tag(file); |
210 | 257 | } |
211 | 1.78k | return KIO::ThumbnailResult::fail(); |
212 | 2.04k | } |
213 | | |
214 | | #include "audiocreator.moc" |
215 | | #include "moc_audiocreator.cpp" |