1/*
2 * Copyright (C) 2018 Alexander Stippich <a.stippich@gmx.net>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20#include "embeddedimagedata.h"
21#include "kfilemetadata_debug.h"
22// Taglib includes
23#include <taglib.h>
24#include <tag.h>
25#include <tfilestream.h>
26#include <mpegfile.h>
27#include <oggfile.h>
28#include <mp4file.h>
29#include <flacfile.h>
30#include <vorbisfile.h>
31#include <opusfile.h>
32#include <mpcfile.h>
33#include <id3v2tag.h>
34#include <attachedpictureframe.h>
35#include <mp4tag.h>
36#include <xiphcomment.h>
37#include <apefile.h>
38#include <apetag.h>
39#include <wavpackfile.h>
40#include <speexfile.h>
41#include <wavfile.h>
42#include <aifffile.h>
43#include <asffile.h>
44#include <asfattribute.h>
45#include <asfpicture.h>
46
47#include <QMimeDatabase>
48
49using namespace KFileMetaData;
50
51class Q_DECL_HIDDEN EmbeddedImageData::Private
52{
53public:
54 QMimeDatabase mMimeDatabase;
55 QByteArray getFrontCover(const QString &fileUrl, const QString &mimeType) const;
56 QByteArray getFrontCoverFromID3(TagLib::ID3v2::Tag* Id3Tags) const;
57 QByteArray getFrontCoverFromFlacPicture(TagLib::List<TagLib::FLAC::Picture *> lstPic) const;
58 QByteArray getFrontCoverFromMp4(TagLib::MP4::Tag* mp4Tags) const;
59 QByteArray getFrontCoverFromApe(TagLib::APE::Tag* apeTags) const;
60 QByteArray getFrontCoverFromAsf(TagLib::ASF::Tag* asfTags) const;
61 static const QStringList mMimetypes;
62};
63
64const QStringList EmbeddedImageData::Private::mMimetypes =
65{
66 QStringLiteral("audio/flac"),
67 QStringLiteral("audio/mp4"),
68 QStringLiteral("audio/mpeg"),
69 QStringLiteral("audio/mpeg3"),
70 QStringLiteral("audio/ogg"),
71 QStringLiteral("audio/opus"),
72 QStringLiteral("audio/speex"),
73 QStringLiteral("audio/wav"),
74 QStringLiteral("audio/x-aiff"),
75 QStringLiteral("audio/x-ape"),
76 QStringLiteral("audio/x-mpeg"),
77 QStringLiteral("audio/x-ms-wma"),
78 QStringLiteral("audio/x-musepack"),
79 QStringLiteral("audio/x-opus+ogg"),
80 QStringLiteral("audio/x-speex"),
81 QStringLiteral("audio/x-vorbis+ogg"),
82 QStringLiteral("audio/x-wav"),
83 QStringLiteral("audio/x-wavpack"),
84};
85
86EmbeddedImageData::EmbeddedImageData()
87 : d(std::unique_ptr<Private>(new Private()))
88{
89}
90
91EmbeddedImageData::~EmbeddedImageData()
92= default;
93
94QStringList EmbeddedImageData::mimeTypes() const
95{
96 return d->mMimetypes;
97}
98
99QMap<EmbeddedImageData::ImageType, QByteArray>
100EmbeddedImageData::imageData(const QString &fileUrl,
101 const EmbeddedImageData::ImageTypes types) const
102{
103 QMap<EmbeddedImageData::ImageType, QByteArray> imageData;
104
105 const auto &fileMimeType = d->mMimeDatabase.mimeTypeForFile(fileUrl);
106 if (fileMimeType.name().startsWith(QLatin1String("audio/"))) {
107 if (types & EmbeddedImageData::FrontCover) {
108 imageData.insert(EmbeddedImageData::FrontCover, d->getFrontCover(fileUrl, fileMimeType.name()));
109 }
110 }
111
112 return imageData;
113}
114
115QByteArray
116EmbeddedImageData::Private::getFrontCover(const QString &fileUrl,
117 const QString &mimeType) const
118{
119 TagLib::FileStream stream(fileUrl.toUtf8().constData(), true);
120 if (!stream.isOpen()) {
121 qCWarning(KFILEMETADATA_LOG) << "Unable to open file readonly: " << fileUrl;
122 return QByteArray();
123 }
124 if ((mimeType == QLatin1String("audio/mpeg"))
125 || (mimeType == QLatin1String("audio/mpeg3"))
126 || (mimeType == QLatin1String("audio/x-mpeg"))) {
127
128 // Handling multiple tags in mpeg files.
129 TagLib::MPEG::File mpegFile(&stream, TagLib::ID3v2::FrameFactory::instance(), true);
130 if (mpegFile.ID3v2Tag()) {
131 return getFrontCoverFromID3(mpegFile.ID3v2Tag());
132 }
133
134 } else if (mimeType == QLatin1String("audio/x-aiff")) {
135
136 TagLib::RIFF::AIFF::File aiffFile(&stream, true);
137 if (aiffFile.hasID3v2Tag()) {
138 return getFrontCoverFromID3(aiffFile.tag());
139 }
140
141 } else if ((mimeType == QLatin1String("audio/wav"))
142 || (mimeType == QLatin1String("audio/x-wav"))) {
143
144 TagLib::RIFF::WAV::File wavFile(&stream, true);
145 if (wavFile.hasID3v2Tag()) {
146 return getFrontCoverFromID3(wavFile.ID3v2Tag());
147 }
148
149 } else if (mimeType == QLatin1String("audio/mp4")) {
150
151 TagLib::MP4::File mp4File(&stream, true);
152 if (mp4File.tag()) {
153 return getFrontCoverFromMp4(mp4File.tag());
154 }
155
156 } else if (mimeType == QLatin1String("audio/x-musepack")) {
157
158 TagLib::MPC::File mpcFile(&stream, true);
159 if (mpcFile.APETag()) {
160 return getFrontCoverFromApe(mpcFile.APETag());
161 }
162
163 } else if (mimeType == QLatin1String("audio/x-ape")) {
164
165 TagLib::APE::File apeFile(&stream, true);
166 if (apeFile.hasAPETag()) {
167 return getFrontCoverFromApe(apeFile.APETag());
168 }
169
170 } else if (mimeType == QLatin1String("audio/x-wavpack")) {
171
172 TagLib::WavPack::File wavpackFile(&stream, true);
173 if (wavpackFile.hasAPETag()) {
174 return getFrontCoverFromApe(wavpackFile.APETag());
175 }
176
177 } else if (mimeType == QLatin1String("audio/x-ms-wma")) {
178
179 TagLib::ASF::File asfFile(&stream, true);
180 TagLib::ASF::Tag* asfTags = dynamic_cast<TagLib::ASF::Tag*>(asfFile.tag());
181 if (asfTags) {
182 return getFrontCoverFromAsf(asfTags);
183 }
184
185 } else if (mimeType == QLatin1String("audio/flac")) {
186
187 TagLib::FLAC::File flacFile(&stream, TagLib::ID3v2::FrameFactory::instance(), true);
188 return getFrontCoverFromFlacPicture(flacFile.pictureList());
189
190 } else if ((mimeType == QLatin1String("audio/ogg"))
191 || (mimeType == QLatin1String("audio/x-vorbis+ogg"))) {
192
193 TagLib::Ogg::Vorbis::File oggFile(&stream, true);
194 if (oggFile.tag()) {
195 return getFrontCoverFromFlacPicture(oggFile.tag()->pictureList());
196 }
197
198 }
199 else if ((mimeType == QLatin1String("audio/opus"))
200 || (mimeType == QLatin1String("audio/x-opus+ogg"))) {
201
202 TagLib::Ogg::Opus::File opusFile(&stream, true);
203 if (opusFile.tag()) {
204 return getFrontCoverFromFlacPicture(opusFile.tag()->pictureList());
205 }
206
207 } else if (mimeType == QLatin1String("audio/speex") || mimeType == QLatin1String("audio/x-speex")) {
208
209 TagLib::Ogg::Speex::File speexFile(&stream, true);
210 if (speexFile.tag()) {
211 return getFrontCoverFromFlacPicture(speexFile.tag()->pictureList());
212 }
213
214 }
215 return QByteArray();
216}
217
218QByteArray
219EmbeddedImageData::Private::getFrontCoverFromID3(TagLib::ID3v2::Tag* Id3Tags) const
220{
221 TagLib::ID3v2::FrameList lstID3v2;
222 // Attached Front Picture.
223 lstID3v2 = Id3Tags->frameListMap()["APIC"];
224 for (const auto& frame : qAsConst(lstID3v2))
225 {
226 const auto *frontCoverFrame = static_cast<TagLib::ID3v2::AttachedPictureFrame *>(frame);
227 if (frontCoverFrame->type() == frontCoverFrame->FrontCover) {
228 return QByteArray(frontCoverFrame->picture().data(), frontCoverFrame->picture().size());
229 }
230 }
231 return QByteArray();
232}
233
234QByteArray
235EmbeddedImageData::Private::getFrontCoverFromFlacPicture(TagLib::List<TagLib::FLAC::Picture *> lstPic) const
236{
237 for (const auto &picture : qAsConst(lstPic)) {
238 if (picture->type() == picture->FrontCover) {
239 return QByteArray(picture->data().data(), picture->data().size());
240 }
241 }
242 return QByteArray();
243}
244
245QByteArray
246EmbeddedImageData::Private::getFrontCoverFromMp4(TagLib::MP4::Tag* mp4Tags) const
247{
248 TagLib::MP4::Item coverArtItem = mp4Tags->item("covr");
249 if (coverArtItem.isValid())
250 {
251 TagLib::MP4::CoverArtList coverArtList = coverArtItem.toCoverArtList();
252 TagLib::MP4::CoverArt& frontCover = coverArtList.front();
253 return QByteArray(frontCover.data().data(), frontCover.data().size());
254 }
255 return QByteArray();
256}
257
258QByteArray
259EmbeddedImageData::Private::getFrontCoverFromApe(TagLib::APE::Tag* apeTags) const
260{
261 TagLib::APE::ItemListMap lstApe = apeTags->itemListMap();
262 TagLib::APE::ItemListMap::ConstIterator itApe;
263
264 /* The cover art tag for APEv2 tags starts with the filename as string
265 * with zero termination followed by the actual picture data */
266 itApe = lstApe.find("COVER ART (FRONT)");
267 if (itApe != lstApe.end()) {
268 TagLib::ByteVector pictureData = (*itApe).second.binaryData();
269 int position = pictureData.find('\0');
270 if (position >= 0) {
271 position += 1;
272 return QByteArray(pictureData.data() + position, pictureData.size() - position);
273 }
274 }
275 return QByteArray();
276}
277QByteArray
278EmbeddedImageData::Private::getFrontCoverFromAsf(TagLib::ASF::Tag* asfTags) const
279{
280 TagLib::ASF::AttributeList lstASF = asfTags->attribute("WM/Picture");
281 for (const auto& attribute : qAsConst(lstASF)) {
282 TagLib::ASF::Picture picture = attribute.toPicture();
283 if (picture.type() == TagLib::ASF::Picture::FrontCover) {
284 TagLib::ByteVector pictureData = picture.picture();
285 return QByteArray(pictureData.data(), pictureData.size());
286 }
287 }
288 return QByteArray();
289}
290