1
// Copyright (C) 2010 Dirk Vanden Boer <dirk.vdb@gmail.com>
3
// This program is free software; you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation; either version 2 of the License, or
6
// (at your option) any later version.
8
// This program is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
// GNU General Public License for more details.
13
// You should have received a copy of the GNU General Public License
14
// along with this program; if not, write to the Free Software
15
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
#include "videothumbnailer.h"
19
#include "moviedecoder.h"
20
#include "filmstripfilter.h"
21
#include "imagewriter.h"
33
namespace ffmpegthumbnailer
36
static const int SMART_FRAME_ATTEMPTS = 25;
38
VideoThumbnailer::VideoThumbnailer()
39
: m_ThumbnailSize(128)
40
, m_SeekPercentage(10)
41
, m_OverlayFilmStrip(false)
42
, m_WorkAroundIssues(false)
43
, m_MaintainAspectRatio(true)
44
, m_SmartFrameSelection(false)
48
VideoThumbnailer::VideoThumbnailer(int thumbnailSize, bool workaroundIssues, bool maintainAspectRatio, bool smartFrameSelection)
49
: m_ThumbnailSize(thumbnailSize)
50
, m_SeekPercentage(10)
51
, m_WorkAroundIssues(workaroundIssues)
52
, m_MaintainAspectRatio(maintainAspectRatio)
53
, m_SmartFrameSelection(smartFrameSelection)
57
VideoThumbnailer::~VideoThumbnailer()
61
void VideoThumbnailer::setSeekPercentage(int percentage)
64
m_SeekPercentage = percentage > 95 ? 95 : percentage;
67
void VideoThumbnailer::setSeekTime(const QString& seekTime)
69
m_SeekTime = seekTime;
72
void VideoThumbnailer::setThumbnailSize(int size)
74
m_ThumbnailSize = size;
77
void VideoThumbnailer::setWorkAroundIssues(bool workAround)
79
m_WorkAroundIssues = workAround;
82
void VideoThumbnailer::setMaintainAspectRatio(bool enabled)
84
m_MaintainAspectRatio = enabled;
87
void VideoThumbnailer::setSmartFrameSelection(bool enabled)
89
m_SmartFrameSelection = enabled;
92
int timeToSeconds(const QString& time)
94
return QTime::fromString(time, QLatin1String("hh:mm:ss")).secsTo(QTime(0, 0, 0));
97
void VideoThumbnailer::generateThumbnail(const QString& videoFile, ImageWriter& imageWriter, QImage &image)
99
MovieDecoder movieDecoder(videoFile, NULL);
100
if (movieDecoder.getInitialized()) {
101
movieDecoder.decodeVideoFrame(); //before seeking, a frame has to be decoded
103
if ((!m_WorkAroundIssues) || (movieDecoder.getCodec() != QLatin1String("h264"))) { //workaround for bug in older ffmpeg (100% cpu usage when seeking in h264 files)
104
int secondToSeekTo = m_SeekTime.isEmpty() ? movieDecoder.getDuration() * m_SeekPercentage / 100 : timeToSeconds(m_SeekTime);
105
movieDecoder.seek(secondToSeekTo);
108
VideoFrame videoFrame;
110
if (m_SmartFrameSelection) {
111
generateSmartThumbnail(movieDecoder, videoFrame);
113
movieDecoder.getScaledVideoFrame(m_ThumbnailSize, m_MaintainAspectRatio, videoFrame);
116
applyFilters(videoFrame);
117
imageWriter.writeFrame(videoFrame, image);
121
void VideoThumbnailer::generateSmartThumbnail(MovieDecoder& movieDecoder, VideoFrame& videoFrame)
123
vector<VideoFrame> videoFrames(SMART_FRAME_ATTEMPTS);
124
vector<Histogram<int> > histograms(SMART_FRAME_ATTEMPTS);
126
for (int i = 0; i < SMART_FRAME_ATTEMPTS; ++i) {
127
movieDecoder.decodeVideoFrame();
128
movieDecoder.getScaledVideoFrame(m_ThumbnailSize, m_MaintainAspectRatio, videoFrames[i]);
129
generateHistogram(videoFrames[i], histograms[i]);
132
int bestFrame = getBestThumbnailIndex(videoFrames, histograms);
134
Q_ASSERT(bestFrame != -1);
135
videoFrame = videoFrames[bestFrame];
138
void VideoThumbnailer::generateThumbnail(const QString& videoFile, QImage &image)
140
ImageWriter* imageWriter = new ImageWriter();
141
generateThumbnail(videoFile, *imageWriter, image);
145
void VideoThumbnailer::addFilter(IFilter* filter)
147
m_Filters.push_back(filter);
150
void VideoThumbnailer::removeFilter(IFilter* filter)
152
for (vector<IFilter*>::iterator iter = m_Filters.begin();
153
iter != m_Filters.end();
155
if (*iter == filter) {
156
m_Filters.erase(iter);
162
void VideoThumbnailer::clearFilters()
167
void VideoThumbnailer::applyFilters(VideoFrame& videoFrame)
169
for (vector<IFilter*>::iterator iter = m_Filters.begin();
170
iter != m_Filters.end();
172
(*iter)->process(videoFrame);
176
void VideoThumbnailer::generateHistogram(const VideoFrame& videoFrame, Histogram<int>& histogram)
178
for (int i = 0; i < videoFrame.height; ++i) {
179
int pixelIndex = i * videoFrame.lineSize;
180
for (int j = 0; j < videoFrame.width * 3; j += 3) {
181
++histogram.r[videoFrame.frameData[pixelIndex + j]];
182
++histogram.g[videoFrame.frameData[pixelIndex + j + 1]];
183
++histogram.b[videoFrame.frameData[pixelIndex + j + 2]];
188
int VideoThumbnailer::getBestThumbnailIndex(vector<VideoFrame>& videoFrames, const vector<Histogram<int> >& histograms)
190
Q_UNUSED(videoFrames);
191
Histogram<float> avgHistogram;
192
for (size_t i = 0; i < histograms.size(); ++i) {
193
for (int j = 0; j < 255; ++j) {
194
avgHistogram.r[j] += static_cast<float>(histograms[i].r[j]) / histograms.size();
195
avgHistogram.g[j] += static_cast<float>(histograms[i].g[j]) / histograms.size();
196
avgHistogram.b[j] += static_cast<float>(histograms[i].b[j]) / histograms.size();
201
float minRMSE = FLT_MAX;
202
for (size_t i = 0; i < histograms.size(); ++i) {
203
//calculate root mean squared error
205
for (int j = 0; j < 255; ++j) {
206
float error = fabsf(avgHistogram.r[j] - histograms[i].r[j])
207
+ fabsf(avgHistogram.g[j] - histograms[i].g[j])
208
+ fabsf(avgHistogram.b[j] - histograms[i].b[j]);
209
rmse += (error * error) / 255;
213
if (rmse < minRMSE) {
219
cout << "Best frame was: " << bestFrame << "(RMSE: " << minRMSE << ")" << endl;