File indexing completed on 2025-04-27 03:58:10

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2016-04-21
0007  * Description : video thumbnails extraction based on ffmpeg
0008  *
0009  * SPDX-FileCopyrightText: 2010      by Dirk Vanden Boer <dirk dot vdb at gmail dot com>
0010  * SPDX-FileCopyrightText: 2016-2018 by Maik Qualmann <metzpinguin at gmail dot com>
0011  * SPDX-FileCopyrightText: 2016-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "videothumbnailer.h"
0018 
0019 // C++ includes
0020 
0021 #include <cfloat>
0022 
0023 // Qt includes
0024 
0025 #include <QtGlobal>
0026 #include <QtMath>
0027 #include <QTime>
0028 
0029 // Local includes
0030 
0031 #include "videothumbdecoder.h"
0032 #include "videostripfilter.h"
0033 #include "videothumbwriter.h"
0034 #include "digikam_debug.h"
0035 
0036 using namespace std;
0037 
0038 namespace Digikam
0039 {
0040 
0041 class Q_DECL_HIDDEN VideoThumbnailer::Private
0042 {
0043 public:
0044 
0045     template <typename T>
0046     class Q_DECL_HIDDEN Histogram
0047     {
0048 
0049     public:
0050 
0051         explicit Histogram()
0052         {
0053             memset(r, 0, 255 * sizeof(T));
0054             memset(g, 0, 255 * sizeof(T));
0055             memset(b, 0, 255 * sizeof(T));
0056         }
0057 
0058         ~Histogram()
0059         {
0060         }
0061 
0062     public:
0063 
0064         T r[256];
0065         T g[256];
0066         T b[256];
0067     };
0068 
0069 public:
0070 
0071     explicit Private()
0072       : thumbnailSize       (256),
0073         seekPercentage      (10),
0074         overlayFilmStrip    (false),
0075         workAroundIssues    (false),
0076         maintainAspectRatio (true),
0077         smartFrameSelection (false),
0078         SMART_FRAME_ATTEMPTS(25)
0079     {
0080     }
0081 
0082     void generateHistogram(const VideoFrame& videoFrame, Histogram<int>& histogram);
0083     int  getBestThumbnailIndex(std::vector<VideoFrame>& videoFrames,
0084                                const std::vector<Histogram<int> >& histograms);
0085 
0086 public:
0087 
0088     int                        thumbnailSize;
0089     quint16                    seekPercentage;
0090     bool                       overlayFilmStrip;
0091     bool                       workAroundIssues;
0092     bool                       maintainAspectRatio;
0093     bool                       smartFrameSelection;
0094     QString                    seekTime;
0095     QVector<VideoStripFilter*> filters;
0096 
0097     const int                  SMART_FRAME_ATTEMPTS;
0098 };
0099 
0100 VideoThumbnailer::VideoThumbnailer()
0101     : d(new Private)
0102 {
0103 }
0104 
0105 VideoThumbnailer::VideoThumbnailer(int thumbnailSize,
0106                                    bool workaroundIssues,
0107                                    bool maintainAspectRatio,
0108                                    bool smartFrameSelection)
0109     : d(new Private)
0110 {
0111     d->thumbnailSize       = thumbnailSize;
0112     d->workAroundIssues    = workaroundIssues;
0113     d->maintainAspectRatio = maintainAspectRatio;
0114     d->smartFrameSelection = smartFrameSelection;
0115 }
0116 
0117 VideoThumbnailer::~VideoThumbnailer()
0118 {
0119     delete d;
0120 }
0121 
0122 void VideoThumbnailer::setSeekPercentage(int percentage)
0123 {
0124     d->seekTime.clear();
0125     d->seekPercentage = (percentage > 95) ? 95 : percentage;
0126 }
0127 
0128 void VideoThumbnailer::setSeekTime(const QString& seekTime)
0129 {
0130     d->seekTime = seekTime;
0131 }
0132 
0133 void VideoThumbnailer::setThumbnailSize(int size)
0134 {
0135     d->thumbnailSize = size;
0136 }
0137 
0138 void VideoThumbnailer::setWorkAroundIssues(bool workAround)
0139 {
0140     d->workAroundIssues = workAround;
0141 }
0142 
0143 void VideoThumbnailer::setMaintainAspectRatio(bool enabled)
0144 {
0145     d->maintainAspectRatio = enabled;
0146 }
0147 
0148 void VideoThumbnailer::setSmartFrameSelection(bool enabled)
0149 {
0150     d->smartFrameSelection = enabled;
0151 }
0152 
0153 int VideoThumbnailer::timeToSeconds(const QString& time) const
0154 {
0155     return QTime::fromString(time, QLatin1String("hh:mm:ss")).secsTo(QTime(0, 0, 0));
0156 }
0157 
0158 void VideoThumbnailer::generateThumbnail(const QString& videoFile,
0159                                          VideoThumbWriter& imageWriter,
0160                                          QImage &image)
0161 {
0162     VideoThumbDecoder movieDecoder(videoFile);
0163 
0164     if (movieDecoder.getInitialized())
0165     {
0166         // before seeking, a frame has to be decoded
0167 
0168         if (!movieDecoder.decodeVideoFrame())
0169         {
0170             return;
0171         }
0172 
0173         if ((!d->workAroundIssues) || (movieDecoder.getCodec() != QLatin1String("h264")))
0174         {
0175             // workaround for bug in older ffmpeg (100% cpu usage when seeking in h264 files)
0176 
0177             int secondToSeekTo = d->seekTime.isEmpty() ? movieDecoder.getDuration() * d->seekPercentage / 100
0178                                                        : timeToSeconds(d->seekTime);
0179             movieDecoder.seek(secondToSeekTo);
0180         }
0181 
0182         VideoFrame videoFrame;
0183 
0184         if (d->smartFrameSelection)
0185         {
0186             generateSmartThumbnail(movieDecoder, videoFrame);
0187         }
0188         else
0189         {
0190             movieDecoder.getScaledVideoFrame(d->thumbnailSize, d->maintainAspectRatio, videoFrame);
0191         }
0192 
0193         applyFilters(videoFrame);
0194         imageWriter.writeFrame(videoFrame, image);
0195     }
0196 }
0197 
0198 void VideoThumbnailer::generateSmartThumbnail(VideoThumbDecoder& movieDecoder,
0199                                               VideoFrame& videoFrame)
0200 {
0201     vector<VideoFrame> videoFrames(d->SMART_FRAME_ATTEMPTS);
0202     vector<Private::Histogram<int> > histograms(d->SMART_FRAME_ATTEMPTS);
0203 
0204     for (int i = 0 ; i < d->SMART_FRAME_ATTEMPTS ; ++i)
0205     {
0206         movieDecoder.decodeVideoFrame();
0207         movieDecoder.getScaledVideoFrame(d->thumbnailSize, d->maintainAspectRatio, videoFrames[i]);
0208         d->generateHistogram(videoFrames[i], histograms[i]);
0209     }
0210 
0211     int bestFrame = d->getBestThumbnailIndex(videoFrames, histograms);
0212 
0213     if (bestFrame == -1)
0214     {
0215         bestFrame = 0;
0216     }
0217 
0218     videoFrame = videoFrames[bestFrame];
0219 }
0220 
0221 void VideoThumbnailer::generateThumbnail(const QString& videoFile,
0222                                          QImage &image)
0223 {
0224     VideoThumbWriter* const imageWriter = new  VideoThumbWriter();
0225     generateThumbnail(videoFile, *imageWriter, image);
0226     delete imageWriter;
0227 }
0228 
0229 void VideoThumbnailer::addFilter(VideoStripFilter* const filter)
0230 {
0231     d->filters.append(filter);
0232 }
0233 
0234 void VideoThumbnailer::removeFilter(const VideoStripFilter* const filter)
0235 {
0236     for (QVector<VideoStripFilter*>::iterator it = d->filters.begin() ;
0237          it != d->filters.end() ; ++it)
0238     {
0239         if (*it == filter)
0240         {
0241             d->filters.erase(it);
0242             break;
0243         }
0244     }
0245 }
0246 
0247 void VideoThumbnailer::clearFilters()
0248 {
0249     d->filters.clear();
0250 }
0251 
0252 void VideoThumbnailer::applyFilters(VideoFrame& videoFrame)
0253 {
0254     for (QVector<VideoStripFilter*>::iterator it = d->filters.begin() ;
0255          it != d->filters.end() ; ++it)
0256     {
0257         (*it)->process(videoFrame);
0258     }
0259 }
0260 
0261 void VideoThumbnailer::Private::generateHistogram(const VideoFrame& videoFrame,
0262                                                   Private::Histogram<int>& histogram)
0263 {
0264     for (quint32 i = 0 ; i < videoFrame.height ; ++i)
0265     {
0266         int pixelIndex = i * videoFrame.lineSize;
0267 
0268         for (quint32 j = 0 ; j < videoFrame.width * 3 ; j += 3)
0269         {
0270             ++histogram.r[videoFrame.frameData[pixelIndex + j    ]];
0271             ++histogram.g[videoFrame.frameData[pixelIndex + j + 1]];
0272             ++histogram.b[videoFrame.frameData[pixelIndex + j + 2]];
0273         }
0274     }
0275 }
0276 
0277 int VideoThumbnailer::Private::getBestThumbnailIndex(vector<VideoFrame>& videoFrames,
0278                                                      const vector<Private::Histogram<int> >& histograms)
0279 {
0280     Q_UNUSED(videoFrames);
0281     Private::Histogram<float> avgHistogram;
0282 
0283     for (size_t i = 0 ; i < histograms.size() ; ++i)
0284     {
0285         for (int j = 0 ; j < 255 ; ++j)
0286         {
0287             avgHistogram.r[j] += static_cast<float>(histograms[i].r[j]) / histograms.size();
0288             avgHistogram.g[j] += static_cast<float>(histograms[i].g[j]) / histograms.size();
0289             avgHistogram.b[j] += static_cast<float>(histograms[i].b[j]) / histograms.size();
0290         }
0291     }
0292 
0293     int bestFrame = -1;
0294     float minRMSE = FLT_MAX;
0295 
0296     for (size_t i = 0 ; i < histograms.size() ; ++i)
0297     {
0298         // calculate root mean squared error
0299 
0300         float rmse = 0.0;
0301 
0302         for (int j = 0 ; j < 255 ; ++j)
0303         {
0304             float error = qFabs(avgHistogram.r[j] - histograms[i].r[j]) +
0305                           qFabs(avgHistogram.g[j] - histograms[i].g[j]) +
0306                           qFabs(avgHistogram.b[j] - histograms[i].b[j]);
0307             rmse += (error * error) / 255;
0308         }
0309 
0310         rmse = qSqrt(rmse);
0311 
0312         if (rmse < minRMSE)
0313         {
0314             minRMSE   = rmse;
0315             bestFrame = i;
0316         }
0317     }
0318 /*
0319     qCDebug(DIGIKAM_GENERAL_LOG) << "Best frame was: "
0320                                  << bestFrame
0321                                  << "(RMSE: "
0322                                  << minRMSE
0323                                  << ")" << QT_ENDL;
0324 */
0325     return bestFrame;
0326 }
0327 
0328 } // namespace Digikam