/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 | 11.4k | : KIO::ThumbnailCreator(parent, args) |
39 | 11.4k | { |
40 | 11.4k | } |
41 | | |
42 | | AudioCreator::~AudioCreator() |
43 | 11.4k | { |
44 | 11.4k | } |
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 | 16 | { |
56 | 16 | return tag(); |
57 | 16 | } |
58 | | }; |
59 | | } |
60 | | } |
61 | | } |
62 | | |
63 | | template<class T> |
64 | | static KIO::ThumbnailResult parseID3v2Tag(T &file) |
65 | 6.79k | { |
66 | 6.79k | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { |
67 | 563 | return KIO::ThumbnailResult::fail(); |
68 | 563 | } |
69 | 6.22k | const auto &map = file.ID3v2Tag()->frameListMap(); |
70 | 6.22k | if (map["APIC"].isEmpty()) { |
71 | 2.13k | return KIO::ThumbnailResult::fail(); |
72 | 2.13k | } |
73 | 4.09k | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); |
74 | 4.09k | if (!apicFrame) { |
75 | 13 | return KIO::ThumbnailResult::fail(); |
76 | 13 | } |
77 | 4.08k | const auto coverData = apicFrame->picture(); |
78 | 4.08k | QImage img; |
79 | 4.08k | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); |
80 | 4.08k | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); |
81 | 4.09k | } audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::MPEG::File>(TagLib::MPEG::File&) Line | Count | Source | 65 | 6.37k | { | 66 | 6.37k | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 182 | return KIO::ThumbnailResult::fail(); | 68 | 182 | } | 69 | 6.19k | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 6.19k | if (map["APIC"].isEmpty()) { | 71 | 2.10k | return KIO::ThumbnailResult::fail(); | 72 | 2.10k | } | 73 | 4.08k | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 4.08k | if (!apicFrame) { | 75 | 11 | return KIO::ThumbnailResult::fail(); | 76 | 11 | } | 77 | 4.07k | const auto coverData = apicFrame->picture(); | 78 | 4.07k | QImage img; | 79 | 4.07k | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 4.07k | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 4.08k | } |
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::FLAC::File>(TagLib::FLAC::File&) Line | Count | Source | 65 | 273 | { | 66 | 273 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 273 | return KIO::ThumbnailResult::fail(); | 68 | 273 | } | 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 | 27 | { | 66 | 27 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 19 | return KIO::ThumbnailResult::fail(); | 68 | 19 | } | 69 | 8 | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 8 | if (map["APIC"].isEmpty()) { | 71 | 3 | return KIO::ThumbnailResult::fail(); | 72 | 3 | } | 73 | 5 | auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front()); | 74 | 5 | if (!apicFrame) { | 75 | 1 | return KIO::ThumbnailResult::fail(); | 76 | 1 | } | 77 | 4 | const auto coverData = apicFrame->picture(); | 78 | 4 | QImage img; | 79 | 4 | bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size()); | 80 | 4 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 81 | 5 | } |
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::RIFF::WAV::File>(TagLib::RIFF::WAV::File&) Line | Count | Source | 65 | 117 | { | 66 | 117 | if (!file.hasID3v2Tag() || !file.ID3v2Tag()) { | 67 | 89 | return KIO::ThumbnailResult::fail(); | 68 | 89 | } | 69 | 28 | const auto &map = file.ID3v2Tag()->frameListMap(); | 70 | 28 | if (map["APIC"].isEmpty()) { | 71 | 25 | return KIO::ThumbnailResult::fail(); | 72 | 25 | } | 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 | 295 | { |
86 | 295 | const auto pictureList = file.pictureList(); |
87 | 295 | for (const auto &picture : pictureList) { |
88 | 42 | if (picture->type() != TagLib::FLAC::Picture::FrontCover) { |
89 | 42 | continue; |
90 | 42 | } |
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 | 42 | } |
96 | 295 | return KIO::ThumbnailResult::fail(); |
97 | 295 | } audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::FLAC::File>(TagLib::FLAC::File&) Line | Count | Source | 85 | 273 | { | 86 | 273 | const auto pictureList = file.pictureList(); | 87 | 273 | for (const auto &picture : pictureList) { | 88 | 42 | if (picture->type() != TagLib::FLAC::Picture::FrontCover) { | 89 | 42 | continue; | 90 | 42 | } | 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 | 42 | } | 96 | 273 | return KIO::ThumbnailResult::fail(); | 97 | 273 | } |
audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::Ogg::XiphComment>(TagLib::Ogg::XiphComment&) Line | Count | Source | 85 | 22 | { | 86 | 22 | const auto pictureList = file.pictureList(); | 87 | 22 | 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 | 22 | return KIO::ThumbnailResult::fail(); | 97 | 22 | } |
|
98 | | |
99 | | template<class T> |
100 | | static KIO::ThumbnailResult parseMP4Tag(T &file) |
101 | 304 | { |
102 | 304 | if (!file.hasMP4Tag() || !file.tag()) { |
103 | 304 | return KIO::ThumbnailResult::fail(); |
104 | 304 | } |
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 | 6.93k | { |
122 | 6.93k | if (!file.hasAPETag() || !file.APETag()) { |
123 | 6.65k | return KIO::ThumbnailResult::fail(); |
124 | 6.65k | } |
125 | 281 | const auto &map = file.APETag()->itemListMap(); |
126 | 392 | for (const auto &item : map) { |
127 | 392 | if (item.second.type() != TagLib::APE::Item::Binary) { |
128 | 289 | continue; |
129 | 289 | } |
130 | 103 | const auto coverData = item.second.binaryData(); |
131 | 103 | const auto data = coverData.data(); |
132 | 103 | const auto size = coverData.size(); |
133 | 525 | for (size_t i = 0; i < size; ++i) { |
134 | 469 | if (data[i] == '\0' && (i + 1) < size) { |
135 | 47 | const auto start = data + i + 1; |
136 | 47 | QImage img; |
137 | 47 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); |
138 | 47 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); |
139 | 0 | ; |
140 | 0 | } |
141 | 469 | } |
142 | 103 | } |
143 | 234 | return KIO::ThumbnailResult::fail(); |
144 | 281 | } audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPEG::File>(TagLib::MPEG::File&) Line | Count | Source | 121 | 5.90k | { | 122 | 5.90k | if (!file.hasAPETag() || !file.APETag()) { | 123 | 5.77k | return KIO::ThumbnailResult::fail(); | 124 | 5.77k | } | 125 | 130 | const auto &map = file.APETag()->itemListMap(); | 126 | 130 | for (const auto &item : map) { | 127 | 112 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 58 | continue; | 129 | 58 | } | 130 | 54 | const auto coverData = item.second.binaryData(); | 131 | 54 | const auto data = coverData.data(); | 132 | 54 | const auto size = coverData.size(); | 133 | 271 | for (size_t i = 0; i < size; ++i) { | 134 | 246 | 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 | 246 | } | 142 | 54 | } | 143 | 101 | return KIO::ThumbnailResult::fail(); | 144 | 130 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::APE::File>(TagLib::APE::File&) Line | Count | Source | 121 | 275 | { | 122 | 275 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 224 | return KIO::ThumbnailResult::fail(); | 124 | 224 | } | 125 | 51 | const auto &map = file.APETag()->itemListMap(); | 126 | 77 | for (const auto &item : map) { | 127 | 77 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 60 | continue; | 129 | 60 | } | 130 | 17 | const auto coverData = item.second.binaryData(); | 131 | 17 | const auto data = coverData.data(); | 132 | 17 | const auto size = coverData.size(); | 133 | 94 | for (size_t i = 0; i < size; ++i) { | 134 | 83 | 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 | 83 | } | 142 | 17 | } | 143 | 45 | return KIO::ThumbnailResult::fail(); | 144 | 51 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::WavPack::File>(TagLib::WavPack::File&) Line | Count | Source | 121 | 461 | { | 122 | 461 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 408 | return KIO::ThumbnailResult::fail(); | 124 | 408 | } | 125 | 53 | const auto &map = file.APETag()->itemListMap(); | 126 | 81 | for (const auto &item : map) { | 127 | 81 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 70 | continue; | 129 | 70 | } | 130 | 11 | const auto coverData = item.second.binaryData(); | 131 | 11 | const auto data = coverData.data(); | 132 | 11 | const auto size = coverData.size(); | 133 | 96 | for (size_t i = 0; i < size; ++i) { | 134 | 93 | if (data[i] == '\0' && (i + 1) < size) { | 135 | 8 | const auto start = data + i + 1; | 136 | 8 | QImage img; | 137 | 8 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); | 138 | 8 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 139 | 0 | ; | 140 | 0 | } | 141 | 93 | } | 142 | 11 | } | 143 | 45 | return KIO::ThumbnailResult::fail(); | 144 | 53 | } |
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPC::File>(TagLib::MPC::File&) Line | Count | Source | 121 | 295 | { | 122 | 295 | if (!file.hasAPETag() || !file.APETag()) { | 123 | 248 | return KIO::ThumbnailResult::fail(); | 124 | 248 | } | 125 | 47 | const auto &map = file.APETag()->itemListMap(); | 126 | 122 | for (const auto &item : map) { | 127 | 122 | if (item.second.type() != TagLib::APE::Item::Binary) { | 128 | 101 | continue; | 129 | 101 | } | 130 | 21 | const auto coverData = item.second.binaryData(); | 131 | 21 | const auto data = coverData.data(); | 132 | 21 | const auto size = coverData.size(); | 133 | 64 | for (size_t i = 0; i < size; ++i) { | 134 | 47 | if (data[i] == '\0' && (i + 1) < size) { | 135 | 4 | const auto start = data + i + 1; | 136 | 4 | QImage img; | 137 | 4 | bool okay = img.loadFromData((uchar *)start, size - (start - data)); | 138 | 4 | return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); | 139 | 0 | ; | 140 | 0 | } | 141 | 47 | } | 142 | 21 | } | 143 | 43 | return KIO::ThumbnailResult::fail(); | 144 | 47 | } |
|
145 | | |
146 | | KIO::ThumbnailResult AudioCreator::create(const KIO::ThumbnailRequest &request) |
147 | 11.4k | { |
148 | 11.4k | QMimeDatabase db; |
149 | 11.4k | QMimeType type = db.mimeTypeForName(request.mimeType()); |
150 | | |
151 | 11.4k | const QByteArray fileNameBytes = QFile::encodeName(request.url().toLocalFile()); |
152 | 11.4k | const char *fileName = fileNameBytes.constData(); |
153 | | |
154 | 11.4k | if (!type.isValid()) { |
155 | 0 | return KIO::ThumbnailResult::fail(); |
156 | 0 | } |
157 | | |
158 | 11.4k | if (type.inherits("audio/mpeg")) { |
159 | 6.37k | TagLib::MPEG::File file(fileName); |
160 | | |
161 | 6.37k | if (auto result = parseID3v2Tag(file); result.isValid()) { |
162 | 475 | return result; |
163 | 475 | } |
164 | | |
165 | 5.90k | return parseAPETag(file); |
166 | 6.37k | } |
167 | 5.06k | if (type.inherits("audio/x-flac") || type.inherits("audio/flac")) { |
168 | 273 | TagLib::FLAC::File file(fileName); |
169 | | |
170 | 273 | if (auto result = parseFlacTag(file); result.isValid()) { |
171 | 0 | return result; |
172 | 0 | } |
173 | | |
174 | 273 | return parseID3v2Tag(file); |
175 | 273 | } |
176 | 4.79k | if (type.inherits("audio/mp4") || type.inherits("audio/x-m4a") || type.inherits("audio/vnd.audible.aax")) { |
177 | 304 | TagLib::MP4::File file(fileName); |
178 | 304 | return parseMP4Tag(file); |
179 | 304 | } |
180 | 4.48k | if (type.inherits("audio/x-ape")) { |
181 | 275 | TagLib::APE::File file(fileName); |
182 | 275 | return parseAPETag(file); |
183 | 275 | } |
184 | 4.21k | if (type.inherits("audio/x-wavpack") || type.inherits("audio/x-vw")) { |
185 | 461 | TagLib::WavPack::File file(fileName); |
186 | 461 | return parseAPETag(file); |
187 | 461 | } |
188 | 3.75k | if (type.inherits("audio/x-musepack")) { |
189 | 295 | TagLib::MPC::File file(fileName); |
190 | 295 | return parseAPETag(file); |
191 | 295 | } |
192 | 3.45k | if (type.inherits("audio/ogg") || type.inherits("audio/vorbis")) { |
193 | 1.95k | TagLib::FileRef fileRef(fileName); |
194 | 1.95k | if (fileRef.isNull()) { |
195 | 252 | return KIO::ThumbnailResult::fail(); |
196 | 252 | } |
197 | 1.69k | auto xiphComment = dynamic_cast<TagLib::Ogg::XiphComment *>(fileRef.tag()); |
198 | 1.69k | if (!xiphComment || xiphComment->isEmpty()) { |
199 | 1.67k | return KIO::ThumbnailResult::fail(); |
200 | 1.67k | } |
201 | 22 | return parseFlacTag(*xiphComment); |
202 | 1.69k | } |
203 | 1.50k | if (type.inherits("audio/x-aiff") || type.inherits("audio/x-aifc")) { |
204 | 27 | TagLib::RIFF::AIFF::FileExt file(fileName); |
205 | 27 | return parseID3v2Tag(file); |
206 | 27 | } |
207 | 1.47k | if (type.inherits("audio/x-wav")) { |
208 | 117 | TagLib::RIFF::WAV::File file(fileName); |
209 | 117 | return parseID3v2Tag(file); |
210 | 117 | } |
211 | 1.36k | return KIO::ThumbnailResult::fail(); |
212 | 1.47k | } |
213 | | |
214 | | #include "audiocreator.moc" |
215 | | #include "moc_audiocreator.cpp" |