Coverage Report

Created: 2026-10-28 07:09

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
21.6k
    : KIO::ThumbnailCreator(parent, args)
39
21.6k
{
40
21.6k
}
41
42
AudioCreator::~AudioCreator()
43
21.6k
{
44
21.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
76
    {
56
76
        return tag();
57
76
    }
58
};
59
}
60
}
61
}
62
63
template<class T>
64
static KIO::ThumbnailResult parseID3v2Tag(T &file)
65
16.9k
{
66
16.9k
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
1.24k
        return KIO::ThumbnailResult::fail();
68
1.24k
    }
69
15.7k
    const auto &map = file.ID3v2Tag()->frameListMap();
70
15.7k
    if (map["APIC"].isEmpty()) {
71
3.59k
        return KIO::ThumbnailResult::fail();
72
3.59k
    }
73
12.1k
    auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front());
74
12.1k
    if (!apicFrame) {
75
25
        return KIO::ThumbnailResult::fail();
76
25
    }
77
12.1k
    const auto coverData = apicFrame->picture();
78
12.1k
    QImage img;
79
12.1k
    bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
80
12.1k
    return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
81
12.1k
}
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::MPEG::File>(TagLib::MPEG::File&)
Line
Count
Source
65
15.9k
{
66
15.9k
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
286
        return KIO::ThumbnailResult::fail();
68
286
    }
69
15.6k
    const auto &map = file.ID3v2Tag()->frameListMap();
70
15.6k
    if (map["APIC"].isEmpty()) {
71
3.56k
        return KIO::ThumbnailResult::fail();
72
3.56k
    }
73
12.1k
    auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front());
74
12.1k
    if (!apicFrame) {
75
23
        return KIO::ThumbnailResult::fail();
76
23
    }
77
12.0k
    const auto coverData = apicFrame->picture();
78
12.0k
    QImage img;
79
12.0k
    bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
80
12.0k
    return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
81
12.1k
}
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::FLAC::File>(TagLib::FLAC::File&)
Line
Count
Source
65
375
{
66
375
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
375
        return KIO::ThumbnailResult::fail();
68
375
    }
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
202
{
66
202
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
164
        return KIO::ThumbnailResult::fail();
68
164
    }
69
38
    const auto &map = file.ID3v2Tag()->frameListMap();
70
38
    if (map["APIC"].isEmpty()) {
71
11
        return KIO::ThumbnailResult::fail();
72
11
    }
73
27
    auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front());
74
27
    if (!apicFrame) {
75
1
        return KIO::ThumbnailResult::fail();
76
1
    }
77
26
    const auto coverData = apicFrame->picture();
78
26
    QImage img;
79
26
    bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
80
26
    return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
81
27
}
audiocreator.cpp:KIO::ThumbnailResult parseID3v2Tag<TagLib::RIFF::WAV::File>(TagLib::RIFF::WAV::File&)
Line
Count
Source
65
447
{
66
447
    if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
67
424
        return KIO::ThumbnailResult::fail();
68
424
    }
69
23
    const auto &map = file.ID3v2Tag()->frameListMap();
70
23
    if (map["APIC"].isEmpty()) {
71
20
        return KIO::ThumbnailResult::fail();
72
20
    }
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
405
{
86
405
    const auto pictureList = file.pictureList();
87
405
    for (const auto &picture : pictureList) {
88
92
        if (picture->type() != TagLib::FLAC::Picture::FrontCover) {
89
91
            continue;
90
91
        }
91
1
        const auto coverData = picture->data();
92
1
        QImage img;
93
1
        bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
94
1
        return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
95
92
    }
96
404
    return KIO::ThumbnailResult::fail();
97
405
}
audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::FLAC::File>(TagLib::FLAC::File&)
Line
Count
Source
85
375
{
86
375
    const auto pictureList = file.pictureList();
87
375
    for (const auto &picture : pictureList) {
88
92
        if (picture->type() != TagLib::FLAC::Picture::FrontCover) {
89
91
            continue;
90
91
        }
91
1
        const auto coverData = picture->data();
92
1
        QImage img;
93
1
        bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
94
1
        return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
95
92
    }
96
374
    return KIO::ThumbnailResult::fail();
97
375
}
audiocreator.cpp:KIO::ThumbnailResult parseFlacTag<TagLib::Ogg::XiphComment>(TagLib::Ogg::XiphComment&)
Line
Count
Source
85
30
{
86
30
    const auto pictureList = file.pictureList();
87
30
    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
30
    return KIO::ThumbnailResult::fail();
97
30
}
98
99
template<class T>
100
static KIO::ThumbnailResult parseMP4Tag(T &file)
101
423
{
102
423
    if (!file.hasMP4Tag() || !file.tag()) {
103
423
        return KIO::ThumbnailResult::fail();
104
423
    }
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
16.1k
{
122
16.1k
    if (!file.hasAPETag() || !file.APETag()) {
123
15.7k
        return KIO::ThumbnailResult::fail();
124
15.7k
    }
125
382
    const auto &map = file.APETag()->itemListMap();
126
615
    for (const auto &item : map) {
127
615
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
375
            continue;
129
375
        }
130
240
        const auto coverData = item.second.binaryData();
131
240
        const auto data = coverData.data();
132
240
        const auto size = coverData.size();
133
909
        for (size_t i = 0; i < size; ++i) {
134
768
            if (data[i] == '\0' && (i + 1) < size) {
135
99
                const auto start = data + i + 1;
136
99
                QImage img;
137
99
                bool okay = img.loadFromData((uchar *)start, size - (start - data));
138
99
                return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
139
0
                ;
140
0
            }
141
768
        }
142
240
    }
143
283
    return KIO::ThumbnailResult::fail();
144
382
}
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPEG::File>(TagLib::MPEG::File&)
Line
Count
Source
121
14.9k
{
122
14.9k
    if (!file.hasAPETag() || !file.APETag()) {
123
14.8k
        return KIO::ThumbnailResult::fail();
124
14.8k
    }
125
181
    const auto &map = file.APETag()->itemListMap();
126
272
    for (const auto &item : map) {
127
272
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
135
            continue;
129
135
        }
130
137
        const auto coverData = item.second.binaryData();
131
137
        const auto data = coverData.data();
132
137
        const auto size = coverData.size();
133
479
        for (size_t i = 0; i < size; ++i) {
134
414
            if (data[i] == '\0' && (i + 1) < size) {
135
72
                const auto start = data + i + 1;
136
72
                QImage img;
137
72
                bool okay = img.loadFromData((uchar *)start, size - (start - data));
138
72
                return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
139
0
                ;
140
0
            }
141
414
        }
142
137
    }
143
109
    return KIO::ThumbnailResult::fail();
144
181
}
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::APE::File>(TagLib::APE::File&)
Line
Count
Source
121
328
{
122
328
    if (!file.hasAPETag() || !file.APETag()) {
123
242
        return KIO::ThumbnailResult::fail();
124
242
    }
125
86
    const auto &map = file.APETag()->itemListMap();
126
86
    for (const auto &item : map) {
127
82
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
56
            continue;
129
56
        }
130
26
        const auto coverData = item.second.binaryData();
131
26
        const auto data = coverData.data();
132
26
        const auto size = coverData.size();
133
112
        for (size_t i = 0; i < size; ++i) {
134
94
            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
94
        }
142
26
    }
143
78
    return KIO::ThumbnailResult::fail();
144
86
}
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::WavPack::File>(TagLib::WavPack::File&)
Line
Count
Source
121
504
{
122
504
    if (!file.hasAPETag() || !file.APETag()) {
123
445
        return KIO::ThumbnailResult::fail();
124
445
    }
125
59
    const auto &map = file.APETag()->itemListMap();
126
126
    for (const auto &item : map) {
127
126
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
93
            continue;
129
93
        }
130
33
        const auto coverData = item.second.binaryData();
131
33
        const auto data = coverData.data();
132
33
        const auto size = coverData.size();
133
143
        for (size_t i = 0; i < size; ++i) {
134
123
            if (data[i] == '\0' && (i + 1) < size) {
135
13
                const auto start = data + i + 1;
136
13
                QImage img;
137
13
                bool okay = img.loadFromData((uchar *)start, size - (start - data));
138
13
                return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
139
0
                ;
140
0
            }
141
123
        }
142
33
    }
143
46
    return KIO::ThumbnailResult::fail();
144
59
}
audiocreator.cpp:KIO::ThumbnailResult parseAPETag<TagLib::MPC::File>(TagLib::MPC::File&)
Line
Count
Source
121
301
{
122
301
    if (!file.hasAPETag() || !file.APETag()) {
123
245
        return KIO::ThumbnailResult::fail();
124
245
    }
125
56
    const auto &map = file.APETag()->itemListMap();
126
135
    for (const auto &item : map) {
127
135
        if (item.second.type() != TagLib::APE::Item::Binary) {
128
91
            continue;
129
91
        }
130
44
        const auto coverData = item.second.binaryData();
131
44
        const auto data = coverData.data();
132
44
        const auto size = coverData.size();
133
175
        for (size_t i = 0; i < size; ++i) {
134
137
            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
137
        }
142
44
    }
143
50
    return KIO::ThumbnailResult::fail();
144
56
}
145
146
KIO::ThumbnailResult AudioCreator::create(const KIO::ThumbnailRequest &request)
147
21.6k
{
148
21.6k
    QMimeDatabase db;
149
21.6k
    QMimeType type = db.mimeTypeForName(request.mimeType());
150
151
21.6k
    const QByteArray fileNameBytes = QFile::encodeName(request.url().toLocalFile());
152
21.6k
    const char *fileName = fileNameBytes.constData();
153
154
21.6k
    if (!type.isValid()) {
155
0
        return KIO::ThumbnailResult::fail();
156
0
    }
157
158
21.6k
    if (type.inherits("audio/mpeg")) {
159
15.9k
        TagLib::MPEG::File file(fileName);
160
161
15.9k
        if (auto result = parseID3v2Tag(file); result.isValid()) {
162
968
            return result;
163
968
        }
164
165
14.9k
        return parseAPETag(file);
166
15.9k
    }
167
5.74k
    if (type.inherits("audio/x-flac") || type.inherits("audio/flac")) {
168
375
        TagLib::FLAC::File file(fileName);
169
170
375
        if (auto result = parseFlacTag(file); result.isValid()) {
171
0
            return result;
172
0
        }
173
174
375
        return parseID3v2Tag(file);
175
375
    }
176
5.36k
    if (type.inherits("audio/mp4") || type.inherits("audio/x-m4a") || type.inherits("audio/vnd.audible.aax")) {
177
423
        TagLib::MP4::File file(fileName);
178
423
        return parseMP4Tag(file);
179
423
    }
180
4.94k
    if (type.inherits("audio/x-ape")) {
181
328
        TagLib::APE::File file(fileName);
182
328
        return parseAPETag(file);
183
328
    }
184
4.61k
    if (type.inherits("audio/x-wavpack") || type.inherits("audio/x-vw")) {
185
504
        TagLib::WavPack::File file(fileName);
186
504
        return parseAPETag(file);
187
504
    }
188
4.11k
    if (type.inherits("audio/x-musepack")) {
189
301
        TagLib::MPC::File file(fileName);
190
301
        return parseAPETag(file);
191
301
    }
192
3.81k
    if (type.inherits("audio/ogg") || type.inherits("audio/vorbis")) {
193
1.70k
        TagLib::FileRef fileRef(fileName);
194
1.70k
        if (fileRef.isNull()) {
195
336
            return KIO::ThumbnailResult::fail();
196
336
        }
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
30
        return parseFlacTag(*xiphComment);
202
1.37k
    }
203
2.10k
    if (type.inherits("audio/x-aiff") || type.inherits("audio/x-aifc")) {
204
202
        TagLib::RIFF::AIFF::FileExt file(fileName);
205
202
        return parseID3v2Tag(file);
206
202
    }
207
1.90k
    if (type.inherits("audio/x-wav")) {
208
447
        TagLib::RIFF::WAV::File file(fileName);
209
447
        return parseID3v2Tag(file);
210
447
    }
211
1.45k
    return KIO::ThumbnailResult::fail();
212
1.90k
}
213
214
#include "audiocreator.moc"
215
#include "moc_audiocreator.cpp"