File indexing completed on 2024-04-28 08:41:13
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 }