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