diff --git a/libs/threadimageio/ffmpegthumbnailer/moviedecoder.cpp b/libs/threadimageio/ffmpegthumbnailer/moviedecoder.cpp index 0343b1c133..de19ab467e 100644 --- a/libs/threadimageio/ffmpegthumbnailer/moviedecoder.cpp +++ b/libs/threadimageio/ffmpegthumbnailer/moviedecoder.cpp @@ -1,260 +1,260 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2016-04-21 * Description : video thumbnails extraction based on ffmpeg * * Copyright (C) 2010 by Dirk Vanden Boer * Copyright (C) 2016-2018 by Maik Qualmann * Copyright (C) 2016-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "moviedecoder.h" #include "moviedecoder_p.h" // Local includes #include "digikam_debug.h" namespace Digikam { MovieDecoder::MovieDecoder(const QString& filename) : d(new Private) { initialize(filename); } MovieDecoder::~MovieDecoder() { destroy(); delete d; } void MovieDecoder::initialize(const QString& filename) { d->lastWidth = -1; d->lastHeight = -1; d->lastPixfmt = AV_PIX_FMT_NONE; av_register_all(); avcodec_register_all(); if (avformat_open_input(&d->pFormatContext, filename.toUtf8().data(), NULL, NULL) != 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Could not open input file: " << filename; return; } if (avformat_find_stream_info(d->pFormatContext, 0) < 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Could not find stream information"; return; } d->initializeVideo(); d->pFrame = av_frame_alloc(); if (d->pFrame) { - d->initialized=true; + d->initialized = true; } } bool MovieDecoder::getInitialized() const { return d->initialized; } void MovieDecoder::destroy() { d->deleteFilterGraph(); if (d->pVideoCodecContext) { avcodec_close(d->pVideoCodecContext); d->pVideoCodecContext = 0; } if (d->pFormatContext) { avformat_close_input(&d->pFormatContext); d->pFormatContext = 0; } if (d->pPacket) { av_packet_unref(d->pPacket); delete d->pPacket; d->pPacket = 0; } if (d->pFrame) { av_frame_free(&d->pFrame); d->pFrame = 0; } if (d->pFrameBuffer) { av_free(d->pFrameBuffer); d->pFrameBuffer = 0; } } QString MovieDecoder::getCodec() const { QString codecName; if (d->pVideoCodec) { codecName = QString::fromLatin1(d->pVideoCodec->name); } return codecName; } int MovieDecoder::getWidth() const { if (d->pVideoCodecContext) { return d->pVideoCodecContext->width; } return -1; } int MovieDecoder::getHeight() const { if (d->pVideoCodecContext) { return d->pVideoCodecContext->height; } return -1; } int MovieDecoder::getDuration() const { if (d->pFormatContext) { return static_cast(d->pFormatContext->duration / AV_TIME_BASE); } return 0; } void MovieDecoder::seek(int timeInSeconds) { if (!d->allowSeek) { return; } qint64 timestamp = AV_TIME_BASE * static_cast(timeInSeconds); if (timestamp < 0) { timestamp = 0; } int ret = av_seek_frame(d->pFormatContext, -1, timestamp, 0); if (ret >= 0) { avcodec_flush_buffers(d->pVideoCodecContext); } else { qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed"; return; } int keyFrameAttempts = 0; bool gotFrame = 0; do { int count = 0; gotFrame = 0; while (!gotFrame && count < 20) { d->getVideoPacket(); gotFrame = d->decodeVideoPacket(); count++; } keyFrameAttempts++; } while ((!gotFrame || !d->pFrame->key_frame) && keyFrameAttempts < 200); if (gotFrame == 0) { qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed"; } } void MovieDecoder::decodeVideoFrame() { bool frameFinished = false; while (!frameFinished && d->getVideoPacket()) { frameFinished = d->decodeVideoPacket(); } if (!frameFinished) { qDebug(DIGIKAM_GENERAL_LOG) << "decodeVideoFrame() failed: frame not finished"; return; } } void MovieDecoder::getScaledVideoFrame(int scaledSize, bool maintainAspectRatio, VideoFrame& videoFrame) { if (d->pFrame->interlaced_frame) { d->processFilterGraph(d->pFrame, d->pFrame, d->pVideoCodecContext->pix_fmt, d->pVideoCodecContext->width, d->pVideoCodecContext->height); } int scaledWidth, scaledHeight; d->convertAndScaleFrame(AV_PIX_FMT_RGB24, scaledSize, maintainAspectRatio, scaledWidth, scaledHeight); videoFrame.width = scaledWidth; videoFrame.height = scaledHeight; videoFrame.lineSize = d->pFrame->linesize[0]; videoFrame.frameData.clear(); videoFrame.frameData.resize(videoFrame.lineSize * videoFrame.height); memcpy((&(videoFrame.frameData.front())), d->pFrame->data[0], videoFrame.lineSize * videoFrame.height); } } // namespace Digikam diff --git a/libs/threadimageio/ffmpegthumbnailer/videothumbnailer.cpp b/libs/threadimageio/ffmpegthumbnailer/videothumbnailer.cpp index 67b69a52bc..206d5b21fa 100644 --- a/libs/threadimageio/ffmpegthumbnailer/videothumbnailer.cpp +++ b/libs/threadimageio/ffmpegthumbnailer/videothumbnailer.cpp @@ -1,296 +1,296 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2016-04-21 * Description : video thumbnails extraction based on ffmpeg * * Copyright (C) 2010 by Dirk Vanden Boer * Copyright (C) 2016-2018 by Maik Qualmann * Copyright (C) 2016-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "videothumbnailer.h" // C++ includes #include // Qt includes #include #include #include // Local includes #include "moviedecoder.h" #include "filmstripfilter.h" #include "imagewriter.h" #include "digikam_debug.h" using namespace std; namespace Digikam { class VideoThumbnailer::Private { public: Private() : SMART_FRAME_ATTEMPTS(25) { thumbnailSize = 256; seekPercentage = 10; overlayFilmStrip = false; workAroundIssues = false; maintainAspectRatio = true; smartFrameSelection = false; } - int thumbnailSize; - quint16 seekPercentage; - bool overlayFilmStrip; - bool workAroundIssues; - bool maintainAspectRatio; - bool smartFrameSelection; - QString seekTime; - std::vector filters; - - const int SMART_FRAME_ATTEMPTS; + int thumbnailSize; + quint16 seekPercentage; + bool overlayFilmStrip; + bool workAroundIssues; + bool maintainAspectRatio; + bool smartFrameSelection; + QString seekTime; + QVector filters; + + const int SMART_FRAME_ATTEMPTS; }; VideoThumbnailer::VideoThumbnailer() : d(new Private) { } VideoThumbnailer::VideoThumbnailer(int thumbnailSize, bool workaroundIssues, bool maintainAspectRatio, bool smartFrameSelection) : d(new Private) { d->thumbnailSize = thumbnailSize; d->workAroundIssues = workaroundIssues; d->maintainAspectRatio = maintainAspectRatio; d->smartFrameSelection = smartFrameSelection; } VideoThumbnailer::~VideoThumbnailer() { delete d; } void VideoThumbnailer::setSeekPercentage(int percentage) { d->seekTime.clear(); d->seekPercentage = percentage > 95 ? 95 : percentage; } void VideoThumbnailer::setSeekTime(const QString& seekTime) { d->seekTime = seekTime; } void VideoThumbnailer::setThumbnailSize(int size) { d->thumbnailSize = size; } void VideoThumbnailer::setWorkAroundIssues(bool workAround) { d->workAroundIssues = workAround; } void VideoThumbnailer::setMaintainAspectRatio(bool enabled) { d->maintainAspectRatio = enabled; } void VideoThumbnailer::setSmartFrameSelection(bool enabled) { d->smartFrameSelection = enabled; } int VideoThumbnailer::timeToSeconds(const QString& time) const { return QTime::fromString(time, QLatin1String("hh:mm:ss")).secsTo(QTime(0, 0, 0)); } void VideoThumbnailer::generateThumbnail(const QString& videoFile, ImageWriter& imageWriter, QImage &image) { MovieDecoder movieDecoder(videoFile); if (movieDecoder.getInitialized()) { movieDecoder.decodeVideoFrame(); // before seeking, a frame has to be decoded if ((!d->workAroundIssues) || (movieDecoder.getCodec() != QLatin1String("h264"))) { // workaround for bug in older ffmpeg (100% cpu usage when seeking in h264 files) int secondToSeekTo = d->seekTime.isEmpty() ? movieDecoder.getDuration() * d->seekPercentage / 100 : timeToSeconds(d->seekTime); movieDecoder.seek(secondToSeekTo); } VideoFrame videoFrame; if (d->smartFrameSelection) { generateSmartThumbnail(movieDecoder, videoFrame); } else { movieDecoder.getScaledVideoFrame(d->thumbnailSize, d->maintainAspectRatio, videoFrame); } applyFilters(videoFrame); imageWriter.writeFrame(videoFrame, image); } } void VideoThumbnailer::generateSmartThumbnail(MovieDecoder& movieDecoder, VideoFrame& videoFrame) { vector videoFrames(d->SMART_FRAME_ATTEMPTS); vector > histograms(d->SMART_FRAME_ATTEMPTS); for (int i = 0 ; i < d->SMART_FRAME_ATTEMPTS ; i++) { movieDecoder.decodeVideoFrame(); movieDecoder.getScaledVideoFrame(d->thumbnailSize, d->maintainAspectRatio, videoFrames[i]); generateHistogram(videoFrames[i], histograms[i]); } int bestFrame = getBestThumbnailIndex(videoFrames, histograms); Q_ASSERT(bestFrame != -1); videoFrame = videoFrames[bestFrame]; } void VideoThumbnailer::generateThumbnail(const QString& videoFile, QImage &image) { ImageWriter* const imageWriter = new ImageWriter(); generateThumbnail(videoFile, *imageWriter, image); delete imageWriter; } void VideoThumbnailer::addFilter(FilmStripFilter* const filter) { - d->filters.push_back(filter); + d->filters.append(filter); } void VideoThumbnailer::removeFilter(FilmStripFilter* const filter) { - for (vector::iterator it = d->filters.begin(); + for (QVector::iterator it = d->filters.begin(); it != d->filters.end(); ++it) { if (*it == filter) { d->filters.erase(it); break; } } } void VideoThumbnailer::clearFilters() { d->filters.clear(); } void VideoThumbnailer::applyFilters(VideoFrame& videoFrame) { - for (vector::iterator it = d->filters.begin(); + for (QVector::iterator it = d->filters.begin(); it != d->filters.end(); ++it) { (*it)->process(videoFrame); } } void VideoThumbnailer::generateHistogram(const VideoFrame& videoFrame, Histogram& histogram) { for (quint32 i = 0 ; i < videoFrame.height ; i++) { int pixelIndex = i * videoFrame.lineSize; for (quint32 j = 0 ; j < videoFrame.width * 3 ; j += 3) { ++histogram.r[videoFrame.frameData[pixelIndex + j]]; ++histogram.g[videoFrame.frameData[pixelIndex + j + 1]]; ++histogram.b[videoFrame.frameData[pixelIndex + j + 2]]; } } } int VideoThumbnailer::getBestThumbnailIndex(vector& videoFrames, const vector >& histograms) { Q_UNUSED(videoFrames); Histogram avgHistogram; for (size_t i = 0 ; i < histograms.size() ; i++) { for (int j = 0 ; j < 255 ; j++) { avgHistogram.r[j] += static_cast(histograms[i].r[j]) / histograms.size(); avgHistogram.g[j] += static_cast(histograms[i].g[j]) / histograms.size(); avgHistogram.b[j] += static_cast(histograms[i].b[j]) / histograms.size(); } } int bestFrame = -1; float minRMSE = FLT_MAX; - for (size_t i = 0 ; i < histograms.size(); i++) + for (size_t i = 0 ; i < histograms.size() ; i++) { // calculate root mean squared error float rmse = 0.0; for (int j = 0 ; j < 255 ; j++) { float error = qFabs(avgHistogram.r[j] - histograms[i].r[j]) + qFabs(avgHistogram.g[j] - histograms[i].g[j]) + qFabs(avgHistogram.b[j] - histograms[i].b[j]); rmse += (error * error) / 255; } rmse = qSqrt(rmse); if (rmse < minRMSE) { minRMSE = rmse; bestFrame = i; } } /* qCDebug(DIGIKAM_GENERAL_LOG) << "Best frame was: " << bestFrame << "(RMSE: " << minRMSE << ")" << endl; */ return bestFrame; } } // namespace Digikam