File indexing completed on 2024-04-21 04:49:03

0001 /*
0002     SPDX-FileCopyrightText: 2010 Dirk Vanden Boer <dirk.vdb@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "videothumbnailer.h"
0008 
0009 #include "moviedecoder.h"
0010 #include "filmstripfilter.h"
0011 #include "imagewriter.h"
0012 
0013 #include <QtGlobal>
0014 #include <QTime>
0015 
0016 #include <iostream>
0017 #include <cfloat>
0018 #include <cmath>
0019 #include <sys/stat.h>
0020 
0021 
0022 using namespace std;
0023 
0024 namespace ffmpegthumbnailer
0025 {
0026 
0027 static const int SMART_FRAME_ATTEMPTS = 25;
0028 
0029 VideoThumbnailer::VideoThumbnailer()
0030         : m_ThumbnailSize(128)
0031         , m_SeekPercentage(10)
0032         , m_OverlayFilmStrip(false)
0033         , m_WorkAroundIssues(false)
0034         , m_MaintainAspectRatio(true)
0035         , m_SmartFrameSelection(false)
0036 {
0037 }
0038 
0039 VideoThumbnailer::VideoThumbnailer(int thumbnailSize, bool workaroundIssues, bool maintainAspectRatio, bool smartFrameSelection)
0040         : m_ThumbnailSize(thumbnailSize)
0041         , m_SeekPercentage(10)
0042         , m_WorkAroundIssues(workaroundIssues)
0043         , m_MaintainAspectRatio(maintainAspectRatio)
0044         , m_SmartFrameSelection(smartFrameSelection)
0045 {
0046 }
0047 
0048 VideoThumbnailer::~VideoThumbnailer()
0049 {
0050 }
0051 
0052 void VideoThumbnailer::setSeekPercentage(int percentage)
0053 {
0054     m_SeekTime.clear();
0055     m_SeekPercentage = percentage > 95 ? 95 : percentage;
0056 }
0057 
0058 void VideoThumbnailer::setSeekTime(const QString& seekTime)
0059 {
0060     m_SeekTime = seekTime;
0061 }
0062 
0063 void VideoThumbnailer::setThumbnailSize(int size)
0064 {
0065     m_ThumbnailSize = size;
0066 }
0067 
0068 void VideoThumbnailer::setWorkAroundIssues(bool workAround)
0069 {
0070     m_WorkAroundIssues = workAround;
0071 }
0072 
0073 void VideoThumbnailer::setMaintainAspectRatio(bool enabled)
0074 {
0075     m_MaintainAspectRatio = enabled;
0076 }
0077 
0078 void VideoThumbnailer::setSmartFrameSelection(bool enabled)
0079 {
0080     m_SmartFrameSelection = enabled;
0081 }
0082 
0083 int timeToSeconds(const QString& time)
0084 {
0085     return QTime::fromString(time, QLatin1String("hh:mm:ss")).secsTo(QTime(0, 0, 0));
0086 }
0087 
0088 void VideoThumbnailer::generateThumbnail(const QString& videoFile, ImageWriter& imageWriter, QImage &image)
0089 {
0090     MovieDecoder movieDecoder(videoFile, nullptr);
0091     if (movieDecoder.getInitialized()) {
0092         if (!movieDecoder.decodeVideoFrame()) { //before seeking, a frame has to be decoded
0093             return;
0094         }
0095         
0096         if ((!m_WorkAroundIssues) || (movieDecoder.getCodec() != QLatin1String("h264"))) { //workaround for bug in older ffmpeg (100% cpu usage when seeking in h264 files)
0097             int secondToSeekTo = m_SeekTime.isEmpty() ? movieDecoder.getDuration() * m_SeekPercentage / 100 : timeToSeconds(m_SeekTime);
0098             movieDecoder.seek(secondToSeekTo);
0099         }
0100     
0101         VideoFrame videoFrame;
0102         
0103         if (m_SmartFrameSelection) {
0104             generateSmartThumbnail(movieDecoder, videoFrame);
0105         } else {
0106             movieDecoder.getScaledVideoFrame(m_ThumbnailSize, m_MaintainAspectRatio, videoFrame);
0107         }
0108         
0109         applyFilters(videoFrame);
0110         imageWriter.writeFrame(videoFrame, image, movieDecoder.transformations());
0111     }
0112 }
0113 
0114 void VideoThumbnailer::generateSmartThumbnail(MovieDecoder& movieDecoder, VideoFrame& videoFrame)
0115 {
0116     vector<VideoFrame> videoFrames(SMART_FRAME_ATTEMPTS);
0117     vector<Histogram<int> > histograms(SMART_FRAME_ATTEMPTS);
0118 
0119     for (int i = 0; i < SMART_FRAME_ATTEMPTS; ++i) {
0120         movieDecoder.decodeVideoFrame();
0121         movieDecoder.getScaledVideoFrame(m_ThumbnailSize, m_MaintainAspectRatio, videoFrames[i]);
0122         generateHistogram(videoFrames[i], histograms[i]);
0123     }
0124 
0125     int bestFrame = getBestThumbnailIndex(videoFrames, histograms);
0126 
0127     Q_ASSERT(bestFrame != -1);
0128     videoFrame = videoFrames[bestFrame];
0129 }
0130 
0131 void VideoThumbnailer::generateThumbnail(const QString& videoFile, QImage &image)
0132 {
0133     ImageWriter* imageWriter = new  ImageWriter();
0134     generateThumbnail(videoFile, *imageWriter, image);
0135     delete imageWriter;
0136 }
0137 
0138 void VideoThumbnailer::addFilter(IFilter* filter)
0139 {
0140     m_Filters.push_back(filter);
0141 }
0142 
0143 void VideoThumbnailer::removeFilter(IFilter* filter)
0144 {
0145     for (vector<IFilter*>::iterator iter = m_Filters.begin();
0146             iter != m_Filters.end();
0147             ++iter) {
0148         if (*iter == filter) {
0149             m_Filters.erase(iter);
0150             break;
0151         }
0152     }
0153 }
0154 
0155 void VideoThumbnailer::clearFilters()
0156 {
0157     m_Filters.clear();
0158 }
0159 
0160 void VideoThumbnailer::applyFilters(VideoFrame& videoFrame)
0161 {
0162     for (vector<IFilter*>::iterator iter = m_Filters.begin();
0163             iter != m_Filters.end();
0164             ++iter) {
0165         (*iter)->process(videoFrame);
0166     }
0167 }
0168 
0169 void VideoThumbnailer::generateHistogram(const VideoFrame& videoFrame, Histogram<int>& histogram)
0170 {
0171     for (quint32 i = 0; i < videoFrame.height; ++i) {
0172         int pixelIndex = i * videoFrame.lineSize;
0173         for (quint32 j = 0; j < videoFrame.width * 3; j += 3) {
0174             ++histogram.r[videoFrame.frameData[pixelIndex + j]];
0175             ++histogram.g[videoFrame.frameData[pixelIndex + j + 1]];
0176             ++histogram.b[videoFrame.frameData[pixelIndex + j + 2]];
0177         }
0178     }
0179 }
0180 
0181 int VideoThumbnailer::getBestThumbnailIndex(vector<VideoFrame>& videoFrames, const vector<Histogram<int> >& histograms)
0182 {
0183     Q_UNUSED(videoFrames);
0184     Histogram<float> avgHistogram;
0185     for (size_t i = 0; i < histograms.size(); ++i) {
0186         for (int j = 0; j < 255; ++j) {
0187             avgHistogram.r[j] += static_cast<float>(histograms[i].r[j]) / histograms.size();
0188             avgHistogram.g[j] += static_cast<float>(histograms[i].g[j]) / histograms.size();
0189             avgHistogram.b[j] += static_cast<float>(histograms[i].b[j]) / histograms.size();
0190         }
0191     }
0192 
0193     int bestFrame = -1;
0194     float minRMSE = FLT_MAX;
0195     for (size_t i = 0; i < histograms.size(); ++i) {
0196         //calculate root mean squared error
0197         float rmse = 0.0;
0198         for (int j = 0; j < 255; ++j) {
0199             float error = fabsf(avgHistogram.r[j] - histograms[i].r[j])
0200                           + fabsf(avgHistogram.g[j] - histograms[i].g[j])
0201                           + fabsf(avgHistogram.b[j] - histograms[i].b[j]);
0202             rmse += (error * error) / 255;
0203         }
0204 
0205         rmse = sqrtf(rmse);
0206         if (rmse < minRMSE) {
0207             minRMSE = rmse;
0208             bestFrame = i;
0209         }
0210     }
0211 #ifdef DEBUG_MODE
0212     cout << "Best frame was: " << bestFrame << "(RMSE: " << minRMSE << ")" << endl;
0213 #endif
0214     return bestFrame;
0215 }
0216 
0217 }