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 <QMimeType>
15 #include <QMimeDatabase>
16
17 #include <apetag.h>
18 #include <mp4tag.h>
19 #include <id3v2tag.h>
20 #include <fileref.h>
21 #include <mp4file.h>
22 #include <wavfile.h>
23 #include <apefile.h>
24 #include <mpcfile.h>
25 #include <mpegfile.h>
26 #include <aifffile.h>
27 #include <flacfile.h>
28 #include <wavpackfile.h>
29 #include <xiphcomment.h>
30 #include <flacpicture.h>
31 #include <attachedpictureframe.h>
32
33 extern "C"
34 {
new_creator()35 Q_DECL_EXPORT ThumbCreator* new_creator()
36 {
37 return new AudioCreator;
38 }
39 }
40
AudioCreator()41 AudioCreator::AudioCreator()
42 {
43 }
44
~AudioCreator()45 AudioCreator::~AudioCreator()
46 {
47 }
48
49 namespace TagLib
50 {
51 namespace RIFF
52 {
53 namespace AIFF
54 {
55 struct FileExt : public File
56 {
57 using File::File;
ID3v2TagTagLib::RIFF::AIFF::FileExt58 ID3v2::Tag* ID3v2Tag() const
59 {
60 return tag();
61 }
62 };
63 }
64 }
65 }
66
67 template<class T> static
parseID3v2Tag(T & file,QImage & img)68 bool parseID3v2Tag(T &file, QImage &img)
69 {
70 if (!file.hasID3v2Tag()) {
71 return false;
72 }
73 const auto &map = file.ID3v2Tag()->frameListMap();
74 if (map["APIC"].isEmpty()) {
75 return false;
76 }
77 auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(map["APIC"].front());
78 if (!apicFrame) {
79 return false;
80 }
81 const auto coverData = apicFrame->picture();
82 img.loadFromData((uchar *)coverData.data(), coverData.size());
83 return true;
84 }
85
86 template<class T> static
parseFlacTag(T & file,QImage & img)87 bool parseFlacTag(T &file, QImage &img)
88 {
89 const auto pictureList = file.pictureList();
90 for (const auto &picture : pictureList) {
91 if (picture->type() != TagLib::FLAC::Picture::FrontCover) {
92 continue;
93 }
94 const auto coverData = picture->data();
95 img.loadFromData((uchar *)coverData.data(), coverData.size());
96 return true;
97 }
98 return false;
99 }
100
101 template<class T> static
parseMP4Tag(T & file,QImage & img)102 bool parseMP4Tag(T &file, QImage &img)
103 {
104 if (!file.hasMP4Tag()) {
105 return false;
106 }
107 const auto &map = file.tag()->itemMap();
108 for (const auto &coverList : map) {
109 auto coverArtList = coverList.second.toCoverArtList();
110 if (coverArtList.isEmpty()) {
111 continue;
112 }
113 const auto coverData = coverArtList[0].data();
114 img.loadFromData((uchar *)coverData.data(), coverData.size());
115 return true;
116 }
117 return false;
118 }
119
120 template<class T> static
parseAPETag(T & file,QImage & img)121 bool parseAPETag(T &file, QImage &img)
122 {
123 if (!file.hasAPETag()) {
124 return false;
125 }
126 const auto &map = file.APETag()->itemListMap();
127 for (const auto &item : map) {
128 if (item.second.type() != TagLib::APE::Item::Binary) {
129 continue;
130 }
131 const auto coverData = item.second.binaryData();
132 const auto data = coverData.data();
133 const auto size = coverData.size();
134 for (size_t i=0; i<size; ++i) {
135 if (data[i] == '\0' && (i+1) < size) {
136 const auto start = data+i+1;
137 img.loadFromData((uchar *)start, size-(start-data));
138 return true;
139 }
140 }
141 }
142 return false;
143 }
144
create(const QString & path,int,int,QImage & img)145 bool AudioCreator::create(const QString &path, int, int, QImage &img)
146 {
147 QMimeDatabase db;
148 QMimeType type = db.mimeTypeForFile(path);
149 if (!type.isValid()) {
150 return false;
151 }
152
153 if (type.inherits("audio/mpeg")) {
154 TagLib::MPEG::File file(QFile::encodeName(path).data());
155 return parseID3v2Tag(file, img) || parseAPETag(file, img);
156 }
157 if (type.inherits("audio/x-flac") || type.inherits("audio/flac")) {
158 TagLib::FLAC::File file(QFile::encodeName(path).data());
159 return parseFlacTag(file, img) || parseID3v2Tag(file, img);
160 }
161 if (type.inherits("audio/mp4") || type.inherits("audio/x-m4a") ||
162 type.inherits("audio/vnd.audible.aax")) {
163 TagLib::MP4::File file(QFile::encodeName(path).data());
164 return parseMP4Tag(file, img);
165 }
166 if (type.inherits("audio/x-ape")) {
167 TagLib::APE::File file(QFile::encodeName(path).data());
168 return parseAPETag(file, img);
169 }
170 if (type.inherits("audio/x-wavpack") || type.inherits("audio/x-vw")) {
171 TagLib::WavPack::File file(QFile::encodeName(path).data());
172 return parseAPETag(file, img);
173 }
174 if (type.inherits("audio/x-musepack")) {
175 TagLib::MPC::File file(QFile::encodeName(path).data());
176 return parseAPETag(file, img);
177 }
178 if (type.inherits("audio/ogg") || type.inherits("audio/vorbis")) {
179 TagLib::FileRef fileRef(QFile::encodeName(path).data());
180 if (fileRef.isNull()) {
181 return false;
182 }
183 auto xiphComment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileRef.tag());
184 if (!xiphComment || xiphComment->isEmpty()) {
185 return false;
186 }
187 return parseFlacTag(*xiphComment, img);
188 }
189 if (type.inherits("audio/x-aiff") || type.inherits("audio/x-aifc")) {
190 TagLib::RIFF::AIFF::FileExt file(QFile::encodeName(path).data());
191 return parseID3v2Tag(file, img);
192 }
193 if (type.inherits("audio/x-wav")) {
194 TagLib::RIFF::WAV::File file(QFile::encodeName(path).data());
195 return parseID3v2Tag(file, img);
196 }
197 return false;
198 }
199