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