File indexing completed on 2024-05-12 04:45:58

0001 #include "thumbnailer.h"
0002 
0003 #include <QImage>
0004 #include <QUrl>
0005 #include <QObject>
0006 
0007 #include <limits>
0008 
0009 #include <mp4file.h>
0010 
0011 extern "C" {
0012 #include <libavutil/log.h>
0013 }
0014 
0015 
0016 QQuickImageResponse *Thumbnailer::requestImageResponse(const QString &id, const QSize &requestedSize)
0017 {
0018     AsyncImageResponse *response = new AsyncImageResponse(id, requestedSize);
0019     return response;
0020 }
0021 
0022 AsyncImageResponse::AsyncImageResponse(const QString &id, const QSize &requestedSize)
0023     : m_id(id)
0024     , m_requestedSize(requestedSize)
0025 {
0026     auto surface = new Surface();
0027 
0028     connect(surface, &Surface::previewReady, [this](QImage img)
0029     {
0030        qDebug() << "image Ready" << img.height();
0031        m_image = img;
0032        Q_EMIT this->finished();
0033     });
0034 
0035     surface->request(QUrl::fromUserInput(id).toLocalFile(), requestedSize.width(), requestedSize.height());
0036 
0037     connect(surface, &Surface::error, [this](QString error)
0038     {
0039         m_error = error;
0040         this->cancel();
0041         Q_EMIT this->finished();
0042     });
0043 }
0044 
0045 QQuickTextureFactory *AsyncImageResponse::textureFactory() const
0046 {
0047     return QQuickTextureFactory::textureFactoryForImage(m_image);
0048 }
0049 
0050 QString AsyncImageResponse::errorString() const
0051 {
0052     return m_error;
0053 }
0054 
0055 
0056 Surface::Surface(QObject *p) : QObject(p)
0057 {
0058 
0059 }
0060 
0061 void Surface::request(const QString& path, int width, int /*height*/)
0062 {
0063     QImage img;
0064 
0065     int seqIdx = 0;
0066 
0067     QList<int> seekPercentages = {20,35,50,65,80};
0068 
0069     // We might have an embedded thumb in the video file, so we have to add 1. This gets corrected
0070     // later if we don't have one.
0071     seqIdx %= static_cast<int>(seekPercentages.size()) + 1;
0072 
0073     const QString cacheKey = QString("%1$%2@%3").arg(path).arg(seqIdx).arg(width);
0074 
0075     QImage* cachedImg = m_thumbCache[cacheKey];
0076     if (cachedImg) {
0077         img = *cachedImg;
0078 
0079         Q_EMIT this->previewReady(img);
0080         return;
0081     }
0082 
0083     // Try reading thumbnail embedded into video file
0084     QByteArray ba = path.toLocal8Bit();
0085     TagLib::MP4::File f(ba.data(), false);
0086 
0087     // No matter the seqIdx, we have to know if the video has an embedded cover, even if we then don't return
0088     // it. We could cache it to avoid repeating this for higher seqIdx values, but this should be fast enough
0089     // to not be noticeable and caching adds unnecessary complexity.
0090     if (f.isValid()) {
0091         TagLib::MP4::Tag* tag = f.tag();
0092         TagLib::MP4::ItemMap itemsListMap = tag->itemMap();
0093         TagLib::MP4::Item coverItem = itemsListMap["covr"];
0094         TagLib::MP4::CoverArtList coverArtList = coverItem.toCoverArtList();
0095 
0096         if (!coverArtList.isEmpty()) {
0097             TagLib::MP4::CoverArt coverArt = coverArtList.front();
0098             img.loadFromData((const uchar *)coverArt.data().data(),
0099                          coverArt.data().size());
0100         }
0101     }
0102 
0103     if (!img.isNull()) {
0104         // Video file has an embedded thumbnail -> return it for seqIdx=0 and shift the regular
0105         // seek percentages one to the right
0106 
0107         if (seqIdx == 0) {
0108 
0109             Q_EMIT this->previewReady(img);
0110             return;
0111         }
0112 
0113         seqIdx--;
0114     }
0115 
0116     // The previous modulo could be wrong now if the video had an embedded thumbnail.
0117     seqIdx %= seekPercentages.size();
0118 
0119     m_Thumbnailer.setThumbnailSize(width);
0120     m_Thumbnailer.setSeekPercentage(seekPercentages[seqIdx]);
0121     //Smart frame selection is very slow compared to the fixed detection
0122     //TODO: Use smart detection if the image is single colored.
0123     //m_Thumbnailer.setSmartFrameSelection(true);
0124     m_Thumbnailer.generateThumbnail(path, img);
0125 
0126     if (!img.isNull()) {
0127         // seqIdx 0 will be served from KIO's regular thumbnail cache.
0128         Q_EMIT this->previewReady(img);
0129         return;
0130     }
0131 
0132    Q_EMIT this->error("Image preview could not be generated");
0133 }
0134 
0135