1 #include "coverart/embedded.h"
2 
3 #include "tag.h"
4 #include "mp4file.h"
5 #include "id3v2.h"
6 #include "id3v2frame.h"
7 #include "attachedpictureframe.h"
8 #include "id3v2tag.h"
9 #include "mpegfile.h"
10 #include "flacfile.h"
11 #include "flacpicture.h"
12 #include "fileref.h"
13 #include "tag.h"
14 #include "tpropertymap.h"
15 
16 #include <QDebug>
17 #include <QBuffer>
18 #include <QDir>
19 #include <QApplication>
20 #include <QCryptographicHash>
21 
22 namespace CoverArt {
Embedded()23   Embedded::Embedded() {
24     temp_dir = QDir::tempPath() + QDir::separator() + qAppName() + "_embedded_covers" + QDir::separator();
25     if (!QDir(temp_dir).exists()) {
26       QDir().mkdir(temp_dir);
27     }
28   }
29 
~Embedded()30   Embedded::~Embedded() {
31     QDir(temp_dir).removeRecursively();
32   }
33 
get(const QString & filepath)34   QString Embedded::get(const QString &filepath) {
35     QString result;
36     if (filepath.isEmpty()) {
37       return result;
38     }
39 
40     if (cache.contains(filepath)) {
41       return cache.value(filepath)->fileName();
42     }
43 
44     QImage img;
45     if (filepath.endsWith("m4a", Qt::CaseInsensitive)) {
46       img = m4a(filepath);
47     } else if (filepath.endsWith("mp3", Qt::CaseInsensitive)) {
48       img = mp3(filepath);
49     } else if (filepath.endsWith("flac", Qt::CaseInsensitive)) {
50       img = flac(filepath);
51     }
52 
53     auto f = save(img);
54     if (f != nullptr) {
55       result = f->fileName();
56       cache.insert(filepath, f);
57       qDebug() << "embedded cover in" << f->fileName();
58     }
59 
60     return result;
61   }
62 
m4a(const QString & filepath) const63   QImage Embedded::m4a(const QString &filepath) const {
64     QImage image;
65 
66     TagLib::MP4::File f(filepath.toUtf8().constData());
67     TagLib::MP4::Tag* tag = f.tag();
68     if (tag != nullptr) {
69       TagLib::MP4::ItemMap itemsListMap = tag->itemMap();
70       TagLib::MP4::Item coverItem = itemsListMap["covr"];
71       TagLib::MP4::CoverArtList coverArtList = coverItem.toCoverArtList();
72       if (!coverArtList.isEmpty()) {
73         TagLib::MP4::CoverArt coverArt = coverArtList.front();
74         image.loadFromData((const unsigned char *)coverArt.data().data(), coverArt.data().size());
75       }
76     }
77     return image;
78   }
79 
mp3(const QString & filepath) const80   QImage Embedded::mp3(const QString &filepath) const {
81     QImage image;
82 
83     TagLib::MPEG::File f(filepath.toUtf8().constData());
84     TagLib::ID3v2::Tag *tag = f.ID3v2Tag();
85     if (tag != nullptr) {
86       TagLib::ID3v2::FrameList l = tag->frameList("APIC");
87       if (!l.isEmpty()) {
88         for(TagLib::ID3v2::FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) {
89           auto picframe = (TagLib::ID3v2::AttachedPictureFrame *)(*it);
90           if (picframe->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover) {
91             image.loadFromData((const unsigned char *)picframe->picture().data(), picframe->picture().size());
92           }
93         }
94       }
95     }
96 
97     return image;
98   }
99 
flac(const QString & filepath) const100   QImage Embedded::flac(const QString &filepath) const {
101     QImage image;
102 
103     TagLib::FLAC::File f(filepath.toUtf8().constData());
104     auto pics = f.pictureList();
105     if (!pics.isEmpty()) {
106       auto pic = pics[0];
107       image.loadFromData((const unsigned char *)pic->data().data(), pic->data().size());
108     }
109 
110     return image;
111   }
112 
save(const QImage & img)113   std::shared_ptr<QTemporaryFile> Embedded::save(const QImage &img) {
114     if (img.isNull()) {
115       return nullptr;
116     }
117 
118     auto ba = image_to_bytearray(img);
119     auto hashed = image_hash(ba);
120     if (files_cache.contains(hashed)) {
121       return files_cache.value(hashed);
122     }
123 
124     auto tmpfile = create_tempfile();
125     if (!tmpfile->open()) {
126       qDebug() << "cannot open tempfile" << tmpfile->fileName() << tmpfile->errorString();
127       return nullptr;
128     }
129     tmpfile->setTextModeEnabled(false);
130     if (tmpfile->write(ba) < 0) {
131       qDebug() << "cannot write embedded cover to a tempfile" << tmpfile->fileName() << tmpfile->errorString();
132       return nullptr;
133     }
134     tmpfile->close();
135 
136     files_cache.insert(hashed, tmpfile);
137 
138     return tmpfile;
139   }
140 
image_to_bytearray(const QImage & image) const141   QByteArray Embedded::image_to_bytearray(const QImage &image) const {
142     QByteArray ba;
143     QBuffer buffer(&ba);
144     buffer.open(QIODevice::WriteOnly);
145     image.save(&buffer, "PNG");
146     return ba;
147   }
148 
image_hash(const QByteArray & ba) const149   QByteArray Embedded::image_hash(const QByteArray &ba) const {
150     QCryptographicHash hash(QCryptographicHash::Sha1);
151     hash.addData(ba);
152     return hash.result();
153   }
154 
create_tempfile()155   std::shared_ptr<QTemporaryFile> Embedded::create_tempfile() {
156     auto name_template = temp_dir + "XXXXXX.png";
157     return std::shared_ptr<QTemporaryFile>(new QTemporaryFile(name_template, qApp));
158   }
159 }
160