File indexing completed on 2024-04-28 04:58:02

0001 
0002 /*
0003  *   This file is part of the KDE KIO-Extras project
0004  *   SPDX-FileCopyrightText: 2009 Vytautas Mickus <vmickus@gmail.com>
0005  *   SPDX-FileCopyrightText: 2016 Anthony Fieroni <bvbfan@abv.com>
0006  *
0007  *   SPDX-License-Identifier: LGPL-2.1-only
0008  */
0009 
0010 #include "audiocreator.h"
0011 
0012 #include <QFile>
0013 #include <QImage>
0014 #include <QMimeDatabase>
0015 #include <QMimeType>
0016 
0017 #include <KPluginFactory>
0018 
0019 #include <aifffile.h>
0020 #include <apefile.h>
0021 #include <apetag.h>
0022 #include <attachedpictureframe.h>
0023 #include <fileref.h>
0024 #include <flacfile.h>
0025 #include <flacpicture.h>
0026 #include <id3v2tag.h>
0027 #include <mp4file.h>
0028 #include <mp4tag.h>
0029 #include <mpcfile.h>
0030 #include <mpegfile.h>
0031 #include <wavfile.h>
0032 #include <wavpackfile.h>
0033 #include <xiphcomment.h>
0034 
0035 K_PLUGIN_CLASS_WITH_JSON(AudioCreator, "audiothumbnail.json")
0036 
0037 AudioCreator::AudioCreator(QObject *parent, const QVariantList &args)
0038     : KIO::ThumbnailCreator(parent, args)
0039 {
0040 }
0041 
0042 AudioCreator::~AudioCreator()
0043 {
0044 }
0045 
0046 namespace TagLib
0047 {
0048 namespace RIFF
0049 {
0050 namespace AIFF
0051 {
0052 struct FileExt : public File {
0053     using File::File;
0054     ID3v2::Tag *ID3v2Tag() const
0055     {
0056         return tag();
0057     }
0058 };
0059 }
0060 }
0061 }
0062 
0063 template<class T>
0064 static KIO::ThumbnailResult parseID3v2Tag(T &file)
0065 {
0066     if (!file.hasID3v2Tag() || !file.ID3v2Tag()) {
0067         return KIO::ThumbnailResult::fail();
0068     }
0069     const auto &map = file.ID3v2Tag()->frameListMap();
0070     if (map["APIC"].isEmpty()) {
0071         return KIO::ThumbnailResult::fail();
0072     }
0073     auto apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(map["APIC"].front());
0074     if (!apicFrame) {
0075         return KIO::ThumbnailResult::fail();
0076     }
0077     const auto coverData = apicFrame->picture();
0078     QImage img;
0079     bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
0080     return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
0081 }
0082 
0083 template<class T>
0084 static KIO::ThumbnailResult parseFlacTag(T &file)
0085 {
0086     const auto pictureList = file.pictureList();
0087     for (const auto &picture : pictureList) {
0088         if (picture->type() != TagLib::FLAC::Picture::FrontCover) {
0089             continue;
0090         }
0091         const auto coverData = picture->data();
0092         QImage img;
0093         bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
0094         return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
0095     }
0096     return KIO::ThumbnailResult::fail();
0097 }
0098 
0099 template<class T>
0100 static KIO::ThumbnailResult parseMP4Tag(T &file)
0101 {
0102     if (!file.hasMP4Tag() || !file.tag()) {
0103         return KIO::ThumbnailResult::fail();
0104     }
0105     const auto &map = file.tag()->itemMap();
0106     for (const auto &coverList : map) {
0107         auto coverArtList = coverList.second.toCoverArtList();
0108         if (coverArtList.isEmpty()) {
0109             continue;
0110         }
0111         const auto coverData = coverArtList[0].data();
0112         QImage img;
0113         bool okay = img.loadFromData((uchar *)coverData.data(), coverData.size());
0114         return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
0115     }
0116     return KIO::ThumbnailResult::fail();
0117 }
0118 
0119 template<class T>
0120 static KIO::ThumbnailResult parseAPETag(T &file)
0121 {
0122     if (!file.hasAPETag() || !file.APETag()) {
0123         return KIO::ThumbnailResult::fail();
0124     }
0125     const auto &map = file.APETag()->itemListMap();
0126     for (const auto &item : map) {
0127         if (item.second.type() != TagLib::APE::Item::Binary) {
0128             continue;
0129         }
0130         const auto coverData = item.second.binaryData();
0131         const auto data = coverData.data();
0132         const auto size = coverData.size();
0133         for (size_t i = 0; i < size; ++i) {
0134             if (data[i] == '\0' && (i + 1) < size) {
0135                 const auto start = data + i + 1;
0136                 QImage img;
0137                 bool okay = img.loadFromData((uchar *)start, size - (start - data));
0138                 return okay ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
0139                 ;
0140             }
0141         }
0142     }
0143     return KIO::ThumbnailResult::fail();
0144 }
0145 
0146 KIO::ThumbnailResult AudioCreator::create(const KIO::ThumbnailRequest &request)
0147 {
0148     QMimeDatabase db;
0149     QMimeType type = db.mimeTypeForName(request.mimeType());
0150 
0151     const QByteArray fileNameBytes = QFile::encodeName(request.url().toLocalFile());
0152     const char *fileName = fileNameBytes.constData();
0153 
0154     if (!type.isValid()) {
0155         return KIO::ThumbnailResult::fail();
0156     }
0157 
0158     if (type.inherits("audio/mpeg")) {
0159         TagLib::MPEG::File file(fileName);
0160 
0161         if (auto result = parseID3v2Tag(file); result.isValid()) {
0162             return result;
0163         }
0164 
0165         return parseAPETag(file);
0166     }
0167     if (type.inherits("audio/x-flac") || type.inherits("audio/flac")) {
0168         TagLib::FLAC::File file(fileName);
0169 
0170         if (auto result = parseFlacTag(file); result.isValid()) {
0171             return result;
0172         }
0173 
0174         return parseID3v2Tag(file);
0175     }
0176     if (type.inherits("audio/mp4") || type.inherits("audio/x-m4a") || type.inherits("audio/vnd.audible.aax")) {
0177         TagLib::MP4::File file(fileName);
0178         return parseMP4Tag(file);
0179     }
0180     if (type.inherits("audio/x-ape")) {
0181         TagLib::APE::File file(fileName);
0182         return parseAPETag(file);
0183     }
0184     if (type.inherits("audio/x-wavpack") || type.inherits("audio/x-vw")) {
0185         TagLib::WavPack::File file(fileName);
0186         return parseAPETag(file);
0187     }
0188     if (type.inherits("audio/x-musepack")) {
0189         TagLib::MPC::File file(fileName);
0190         return parseAPETag(file);
0191     }
0192     if (type.inherits("audio/ogg") || type.inherits("audio/vorbis")) {
0193         TagLib::FileRef fileRef(fileName);
0194         if (fileRef.isNull()) {
0195             return KIO::ThumbnailResult::fail();
0196         }
0197         auto xiphComment = dynamic_cast<TagLib::Ogg::XiphComment *>(fileRef.tag());
0198         if (!xiphComment || xiphComment->isEmpty()) {
0199             return KIO::ThumbnailResult::fail();
0200         }
0201         return parseFlacTag(*xiphComment);
0202     }
0203     if (type.inherits("audio/x-aiff") || type.inherits("audio/x-aifc")) {
0204         TagLib::RIFF::AIFF::FileExt file(fileName);
0205         return parseID3v2Tag(file);
0206     }
0207     if (type.inherits("audio/x-wav")) {
0208         TagLib::RIFF::WAV::File file(fileName);
0209         return parseID3v2Tag(file);
0210     }
0211     return KIO::ThumbnailResult::fail();
0212 }
0213 
0214 #include "audiocreator.moc"
0215 #include "moc_audiocreator.cpp"