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