1 /* $BEGIN_LICENSE
2
3 This file is part of Musique.
4 Copyright 2013, Flavio Tordini <flavio.tordini@gmail.com>
5
6 Musique is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Musique is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Musique. If not, see <http://www.gnu.org/licenses/>.
18
19 $END_LICENSE */
20
21 #include "coverutils.h"
22 #include "finderitemdelegate.h"
23 #include "model/album.h"
24
isAcceptableImage(const QImage & image)25 bool CoverUtils::isAcceptableImage(const QImage &image) {
26 const int minimumSize = FinderItemDelegate::ITEM_WIDTH;
27 const int width = image.size().width();
28 const int height = image.size().height();
29
30 if (width < minimumSize || height < minimumSize) {
31 qDebug() << "Local cover too small" << image.size();
32 return false;
33 }
34
35 float aspectRatio = (float)width / (float)height;
36 if (aspectRatio > 1.2 || aspectRatio < 0.8) {
37 qDebug() << "Local cover not square enough" << image.size();
38 return false;
39 }
40
41 return true;
42 }
43
maybeScaleImage(const QImage & image)44 QImage CoverUtils::maybeScaleImage(const QImage &image) {
45 static const int maximumSize = 300;
46 const int width = image.size().width();
47 const int height = image.size().height();
48 if (width > maximumSize || height > maximumSize) {
49 qDebug() << "Scaling local cover" << image.size();
50 return image.scaled(maximumSize, maximumSize, Qt::KeepAspectRatio,
51 Qt::SmoothTransformation);
52 }
53 return image;
54 }
55
saveImage(const QImage & image,Album * album)56 bool CoverUtils::saveImage(const QImage &image, Album *album) {
57 QImage scaledImage = maybeScaleImage(image);
58 QBuffer buffer;
59 scaledImage.save(&buffer, "JPG");
60 album->setPhoto(buffer.data());
61 return true;
62 }
63
coverFromFile(const QString & dir,Album * album)64 bool CoverUtils::coverFromFile(const QString &dir, Album *album) {
65 static const QVector<QRegExp> coverREs = [] {
66 QLatin1String ext(".(jpe?g|gif|png|bmp)");
67 QVector<QRegExp> res;
68 res << QRegExp(".*cover.*" + ext, Qt::CaseInsensitive)
69 << QRegExp(".*front.*" + ext, Qt::CaseInsensitive)
70 << QRegExp(".*folder.*" + ext, Qt::CaseInsensitive);
71 return res;
72 }();
73
74 const QFileInfoList flist =
75 QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Readable);
76
77 for (const QFileInfo &fileInfo : flist) {
78 const QString filename = fileInfo.fileName();
79 for (const QRegExp &re : coverREs) {
80 if (filename.contains(re)) {
81 qDebug() << "Found local cover" << filename;
82 QImage image(fileInfo.absoluteFilePath());
83 if (isAcceptableImage(image)) return saveImage(image, album);
84 break;
85 }
86 }
87 }
88
89 return false;
90 }
91
coverFromTags(const QString & filename,Album * album)92 bool CoverUtils::coverFromTags(const QString &filename, Album *album) {
93 const QString suffix = QFileInfo(filename).suffix().toLower();
94 if (suffix == "mp3") {
95 TagLib::MPEG::File f((TagLib::FileName)filename.toUtf8());
96 if (!f.isValid()) return false;
97 return coverFromMPEGTags(f.ID3v2Tag(), album);
98 } else if (suffix == "ogg" || suffix == "oga") {
99 TagLib::Ogg::Vorbis::File f((TagLib::FileName)filename.toUtf8());
100 if (!f.isValid()) return false;
101 return coverFromXiphComment(f.tag(), album);
102 } else if (suffix == "flac") {
103 TagLib::FLAC::File f((TagLib::FileName)filename.toUtf8());
104 bool res = false;
105 if (f.isValid()) res = coverFromMPEGTags(f.ID3v2Tag(), album);
106 if (!res) res = coverFromXiphComment(f.xiphComment(), album);
107 return res;
108 } else if (suffix == "aac" || suffix == "m4a" || suffix == "m4b" || suffix == "m4p" ||
109 suffix == "mp4") {
110 return coverFromMP4(filename, album);
111 }
112 return false;
113 }
114
coverFromMPEGTags(TagLib::ID3v2::Tag * tag,Album * album)115 bool CoverUtils::coverFromMPEGTags(TagLib::ID3v2::Tag *tag, Album *album) {
116 if (!tag) return false;
117
118 TagLib::ID3v2::FrameList list = tag->frameList("APIC");
119 if (list.isEmpty()) return false;
120
121 TagLib::ID3v2::AttachedPictureFrame *frame =
122 static_cast<TagLib::ID3v2::AttachedPictureFrame *>(list.front());
123 if (!frame) return false;
124 const int frameSize = frame->picture().size();
125 if (frameSize <= 0) return false;
126
127 QImage image;
128 image.loadFromData((const uchar *)frame->picture().data(), frame->picture().size());
129 if (!isAcceptableImage(image)) return false;
130
131 return saveImage(image, album);
132 }
133
coverFromXiphComment(TagLib::Ogg::XiphComment * xiphComment,Album * album)134 bool CoverUtils::coverFromXiphComment(TagLib::Ogg::XiphComment *xiphComment, Album *album) {
135 if (!xiphComment) return false;
136
137 const TagLib::StringList &stringList = xiphComment->fieldListMap()["COVERART"];
138 if (stringList.isEmpty()) return false;
139 TagLib::ByteVector byteVector = stringList.front().data(TagLib::String::Latin1);
140
141 QByteArray encodedData;
142 encodedData.setRawData(byteVector.data(), byteVector.size());
143
144 QByteArray data = QByteArray::fromBase64(encodedData);
145
146 QImage image;
147 image.loadFromData(data);
148 if (!isAcceptableImage(image)) return false;
149
150 qDebug() << "Cover from Xiph!";
151
152 return saveImage(image, album);
153 }
154
coverFromMP4(const QString & filename,Album * album)155 bool CoverUtils::coverFromMP4(const QString &filename, Album *album) {
156 TagLib::MP4::File f((TagLib::FileName)filename.toUtf8());
157 if (!f.isValid()) return false;
158
159 TagLib::MP4::Tag *tag = static_cast<TagLib::MP4::Tag *>(f.tag());
160 if (!tag) return false;
161
162 TagLib::MP4::ItemListMap itemsListMap = tag->itemListMap();
163 TagLib::MP4::Item coverItem = itemsListMap["covr"];
164 TagLib::MP4::CoverArtList coverArtList = coverItem.toCoverArtList();
165 TagLib::MP4::CoverArt coverArt = coverArtList.front();
166
167 QImage image;
168 image.loadFromData((const uchar *)coverArt.data().data(), coverArt.data().size());
169 if (!isAcceptableImage(image)) return false;
170
171 qDebug() << "Cover from MP4!";
172
173 return saveImage(image, album);
174 }
175