Coverage Report

Created: 2026-05-31 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
102
    {
56
102
        return tag();
57
102
    }
58
};
59
}
60
}
61
}
62
63
template<class T>
64
static KIO::ThumbnailResult parseID3v2Tag(T &file)
65
12.7k
{
66
12.7k
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
819
        return KIO::ThumbnailResult::fail();
68
819
    }
69
11.8k
    const auto &map = file.ID3v2Tag()->frameListMap();
70
11.8k
    if (map["APIC"].isEmpty()) {
71
2.89k
        return KIO::ThumbnailResult::fail();
72
2.89k
    }
73
8.99k
    auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front());
74
8.99k
    if (!apicFrame) {
75
7
        return KIO::ThumbnailResult::fail();
76
7
    }
77
8.98k
    const auto coverData = apicFrame->picture();
78
8.98k
    QImage img;
79
8.98k
    bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
80
8.98k
    return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
81
8.99k
}
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::MPEG::File>(TagLib::MPEG::File&)
Line
Count
Source
65
12.0k
{
66
12.0k
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
271
        return KIO::ThumbnailResult::fail();
68
271
    }
69
11.8k
    const auto &map = file.ID3v2Tag()->frameListMap();
70
11.8k
    if (map["APIC"].isEmpty()) {
71
2.85k
        return KIO::ThumbnailResult::fail();
72
2.85k
    }
73
8.94k
    auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front());
74
8.94k
    if (!apicFrame) {
75
5
        return KIO::ThumbnailResult::fail();
76
5
    }
77
8.94k
    const auto coverData = apicFrame->picture();
78
8.94k
    QImage img;
79
8.94k
    bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
80
8.94k
    return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
81
8.94k
}
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::FLAC::File>(TagLib::FLAC::File&)
Line
Count
Source
65
248
{
66
248
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
248
        return KIO::ThumbnailResult::fail();
68
248
    }
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
82
{
66
82
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
31
        return KIO::ThumbnailResult::fail();
68
31
    }
69
51
    const auto &map = file.ID3v2Tag()->frameListMap();
70
51
    if (map["APIC"].isEmpty()) {
71
10
        return KIO::ThumbnailResult::fail();
72
10
    }
73
41
    auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front());
74
41
    if (!apicFrame) {
75
1
        return KIO::ThumbnailResult::fail();
76
1
    }
77
40
    const auto coverData = apicFrame->picture();
78
40
    QImage img;
79
40
    bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
80
40
    return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
81
41
}
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::RIFF::WAV::File>(TagLib::RIFF::WAV::File&)
Line
Count
Source
65
298
{
66
298
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
269
        return KIO::ThumbnailResult::fail();
68
269
    }
69
29
    const auto &map = file.ID3v2Tag()->frameListMap();
70
29
    if (map["APIC"].isEmpty()) {
71
26
        return KIO::ThumbnailResult::fail();
72
26
    }
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
286
{
86
286
    const auto pictureList = file.pictureList();
87
286
    for (const auto &picture : pictureList) {
88
111
        if (picture->type() != TagLib::FLAC::Picture::FrontCover) {
89
111
            continue;
90
111
        }
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
111
    }
96
286
    return KIO::ThumbnailResult::fail();
97
286
}
audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::FLAC::File>(TagLib::FLAC::File&)
Line
Count
Source
85
248
{
86
248
    const auto pictureList = file.pictureList();
87
248
    for (const auto &picture : pictureList) {
88
111
        if (picture->type() != TagLib::FLAC::Picture::FrontCover) {
89
111
            continue;
90
111
        }
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
111
    }
96
248
    return KIO::ThumbnailResult::fail();
97
248
}
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
338
{
102
338
    if (!file.hasMP4Tag() || !file.tag()) {
103
338
        return KIO::ThumbnailResult::fail();
104
338
    }
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
12.3k
{
122
12.3k
    if (!file.hasAPETag() || !file.APETag()) {
123
12.0k
        return KIO::ThumbnailResult::fail();
124
12.0k
    }
125
286
    const auto &map = file.APETag()->itemListMap();
126
499
    for (const auto &item : map) {
127
499
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
343
            continue;
129
343
        }
130
156
        const auto coverData = item.second.binaryData();
131
156
        const auto data = coverData.data();
132
156
        const auto size = coverData.size();
133
502
        for (size_t i = 0; i < size; ++i) {
134
410
            if (data[i] == '\0' && (i + 1) < size) {
135
64
                const auto start = data + i + 1;
136
64
                QImage img;
137
64
                bool okay = img.loadFromData((uchar *)start, size - (start - data));
138
64
                return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
139
0
                ;
140
0
            }
141
410
        }
142
156
    }
143
222
    return KIO::ThumbnailResult::fail();
144
286
}
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPEG::File>(TagLib::MPEG::File&)
Line
Count
Source
121
11.2k
{
122
11.2k
    if (!file.hasAPETag() || !file.APETag()) {
123
11.1k
        return KIO::ThumbnailResult::fail();
124
11.1k
    }
125
143
    const auto &map = file.APETag()->itemListMap();
126
180
    for (const auto &item : map) {
127
180
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
102
            continue;
129
102
        }
130
78
        const auto coverData = item.second.binaryData();
131
78
        const auto data = coverData.data();
132
78
        const auto size = coverData.size();
133
187
        for (size_t i = 0; i < size; ++i) {
134
155
            if (data[i] == '\0' && (i + 1) < size) {
135
46
                const auto start = data + i + 1;
136
46
                QImage img;
137
46
                bool okay = img.loadFromData((uchar *)start, size - (start - data));
138
46
                return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
139
0
                ;
140
0
            }
141
155
        }
142
78
    }
143
97
    return KIO::ThumbnailResult::fail();
144
143
}
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::APE::File>(TagLib::APE::File&)
Line
Count
Source
121
259
{
122
259
    if (!file.hasAPETag() || !file.APETag()) {
123
220
        return KIO::ThumbnailResult::fail();
124
220
    }
125
39
    const auto &map = file.APETag()->itemListMap();
126
71
    for (const auto &item : map) {
127
71
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
49
            continue;
129
49
        }
130
22
        const auto coverData = item.second.binaryData();
131
22
        const auto data = coverData.data();
132
22
        const auto size = coverData.size();
133
79
        for (size_t i = 0; i < size; ++i) {
134
62
            if (data[i] == '\0' && (i + 1) < size) {
135
5
                const auto start = data + i + 1;
136
5
                QImage img;
137
5
                bool okay = img.loadFromData((uchar *)start, size - (start - data));
138
5
                return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
139
0
                ;
140
0
            }
141
62
        }
142
22
    }
143
34
    return KIO::ThumbnailResult::fail();
144
39
}
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::WavPack::File>(TagLib::WavPack::File&)
Line
Count
Source
121
469
{
122
469
    if (!file.hasAPETag() || !file.APETag()) {
123
421
        return KIO::ThumbnailResult::fail();
124
421
    }
125
48
    const auto &map = file.APETag()->itemListMap();
126
98
    for (const auto &item : map) {
127
98
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
72
            continue;
129
72
        }
130
26
        const auto coverData = item.second.binaryData();
131
26
        const auto data = coverData.data();
132
26
        const auto size = coverData.size();
133
94
        for (size_t i = 0; i < size; ++i) {
134
77
            if (data[i] == '\0' && (i + 1) < size) {
135
9
                const auto start = data + i + 1;
136
9
                QImage img;
137
9
                bool okay = img.loadFromData((uchar *)start, size - (start - data));
138
9
                return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
139
0
                ;
140
0
            }
141
77
        }
142
26
    }
143
39
    return KIO::ThumbnailResult::fail();
144
48
}
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPC::File>(TagLib::MPC::File&)
Line
Count
Source
121
303
{
122
303
    if (!file.hasAPETag() || !file.APETag()) {
123
247
        return KIO::ThumbnailResult::fail();
124
247
    }
125
56
    const auto &map = file.APETag()->itemListMap();
126
150
    for (const auto &item : map) {
127
150
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
120
            continue;
129
120
        }
130
30
        const auto coverData = item.second.binaryData();
131
30
        const auto data = coverData.data();
132
30
        const auto size = coverData.size();
133
142
        for (size_t i = 0; i < size; ++i) {
134
116
            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
116
        }
142
30
    }
143
52
    return KIO::ThumbnailResult::fail();
144
56
}
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
12.0k
        TagLib::MPEG::File file(fileName);
160
161
12.0k
        if (auto result = parseID3v2Tag(file); result.isValid()) {
162
792
            return result;
163
792
        }
164
165
11.2k
        return parseAPETag(file);
166
12.0k
    }
167
4.95k
    if (type.inherits("audio/x-flac") || type.inherits("audio/flac")) {
168
248
        TagLib::FLAC::File file(fileName);
169
170
248
        if (auto result = parseFlacTag(file); result.isValid()) {
171
0
            return result;
172
0
        }
173
174
248
        return parseID3v2Tag(file);
175
248
    }
176
4.70k
    if (type.inherits("audio/mp4") || type.inherits("audio/x-m4a") || type.inherits("audio/vnd.audible.aax")) {
177
338
        TagLib::MP4::File file(fileName);
178
338
        return parseMP4Tag(file);
179
338
    }
180
4.37k
    if (type.inherits("audio/x-ape")) {
181
259
        TagLib::APE::File file(fileName);
182
259
        return parseAPETag(file);
183
259
    }
184
4.11k
    if (type.inherits("audio/x-wavpack") || type.inherits("audio/x-vw")) {
185
469
        TagLib::WavPack::File file(fileName);
186
469
        return parseAPETag(file);
187
469
    }
188
3.64k
    if (type.inherits("audio/x-musepack")) {
189
303
        TagLib::MPC::File file(fileName);
190
303
        return parseAPETag(file);
191
303
    }
192
3.33k
    if (type.inherits("audio/ogg") || type.inherits("audio/vorbis")) {
193
1.69k
        TagLib::FileRef fileRef(fileName);
194
1.69k
        if (fileRef.isNull()) {
195
284
            return KIO::ThumbnailResult::fail();
196
284
        }
197
1.40k
        auto xiphComment = dynamic_cast<TagLib::Ogg::XiphComment *>(fileRef.tag());
198
1.40k
        if (!xiphComment || xiphComment->isEmpty()) {
199
1.36k
            return KIO::ThumbnailResult::fail();
200
1.36k
        }
201
38
        return parseFlacTag(*xiphComment);
202
1.40k
    }
203
1.64k
    if (type.inherits("audio/x-aiff") || type.inherits("audio/x-aifc")) {
204
82
        TagLib::RIFF::AIFF::FileExt file(fileName);
205
82
        return parseID3v2Tag(file);
206
82
    }
207
1.56k
    if (type.inherits("audio/x-wav")) {
208
298
        TagLib::RIFF::WAV::File file(fileName);
209
298
        return parseID3v2Tag(file);
210
298
    }
211
1.26k
    return KIO::ThumbnailResult::fail();
212
1.56k
}
213
214
#include "audiocreator.moc"
215
#include "moc_audiocreator.cpp"