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 "videothumbdecoder_p.h"
0018 
0019 // Local includes
0020 
0021 #include "digikam_debug.h"
0022 
0023 namespace Digikam
0024 {
0025 
0026 VideoThumbDecoder::VideoThumbDecoder(const QString& filename)
0027     : d(new Private)
0028 {
0029     initialize(filename);
0030 }
0031 
0032 VideoThumbDecoder::~VideoThumbDecoder()
0033 {
0034     destroy();
0035     delete d;
0036 }
0037 
0038 void VideoThumbDecoder::initialize(const QString& filename)
0039 {
0040     d->lastWidth  = -1;
0041     d->lastHeight = -1;
0042     d->lastPixfmt = AV_PIX_FMT_NONE;
0043 
0044 #if LIBAVFORMAT_VERSION_MAJOR < 58
0045 
0046     av_register_all();
0047 
0048 #endif
0049 
0050 #if LIBAVCODEC_VERSION_MAJOR < 58
0051 
0052     avcodec_register_all();
0053 
0054 #endif
0055 
0056     if (avformat_open_input(&d->pFormatContext,
0057                             filename.toUtf8().data(), nullptr, nullptr) != 0)
0058     {
0059         qDebug(DIGIKAM_GENERAL_LOG) << "Could not open input file: "
0060                                     << filename;
0061         return;
0062     }
0063 
0064     if (avformat_find_stream_info(d->pFormatContext, nullptr) < 0)
0065     {
0066         qDebug(DIGIKAM_GENERAL_LOG) << "Could not find stream information";
0067         return;
0068     }
0069 
0070     if (!d->initializeVideo())
0071     {
0072         return;
0073     }
0074 
0075     d->pFrame = av_frame_alloc();
0076 
0077     if (d->pFrame)
0078     {
0079         d->initialized = true;
0080     }
0081 }
0082 
0083 bool VideoThumbDecoder::getInitialized() const
0084 {
0085     return d->initialized;
0086 }
0087 
0088 void VideoThumbDecoder::destroy()
0089 {
0090     d->deleteFilterGraph();
0091 
0092     if (d->pVideoCodecContext)
0093     {
0094         avcodec_close(d->pVideoCodecContext);
0095         d->pVideoCodecContext = nullptr;
0096     }
0097 
0098     if (d->pFormatContext)
0099     {
0100         avformat_close_input(&d->pFormatContext);
0101         d->pFormatContext = nullptr;
0102     }
0103 
0104     if (d->pPacket)
0105     {
0106         av_packet_unref(d->pPacket);
0107         delete d->pPacket;
0108         d->pPacket = nullptr;
0109     }
0110 
0111     if (d->pFrame)
0112     {
0113         av_frame_free(&d->pFrame);
0114         d->pFrame = nullptr;
0115     }
0116 
0117     if (d->pFrameBuffer)
0118     {
0119         av_free(d->pFrameBuffer);
0120         d->pFrameBuffer = nullptr;
0121     }
0122 }
0123 
0124 QString VideoThumbDecoder::getCodec() const
0125 {
0126     QString codecName;
0127 
0128     if (d->pVideoCodec)
0129     {
0130         codecName = QLatin1String(d->pVideoCodec->name);
0131     }
0132 
0133     return codecName;
0134 }
0135 
0136 int VideoThumbDecoder::getWidth() const
0137 {
0138     if (d->pVideoCodecContext)
0139     {
0140         return d->pVideoCodecContext->width;
0141     }
0142 
0143     return -1;
0144 }
0145 
0146 int VideoThumbDecoder::getHeight() const
0147 {
0148     if (d->pVideoCodecContext)
0149     {
0150         return d->pVideoCodecContext->height;
0151     }
0152 
0153     return -1;
0154 }
0155 
0156 int VideoThumbDecoder::getDuration() const
0157 {
0158     if (d->pFormatContext)
0159     {
0160         return static_cast<int>(d->pFormatContext->duration / AV_TIME_BASE);
0161     }
0162 
0163     return 0;
0164 }
0165 
0166 void VideoThumbDecoder::seek(int timeInSeconds)
0167 {
0168     if (!d->allowSeek)
0169     {
0170         return;
0171     }
0172 
0173     qint64 timestamp = AV_TIME_BASE * static_cast<qint64>(timeInSeconds);
0174 
0175     if (timestamp < 0)
0176     {
0177         timestamp = 0;
0178     }
0179 
0180     int ret = av_seek_frame(d->pFormatContext, -1, timestamp,
0181                             AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD);
0182 
0183     if (ret >= 0)
0184     {
0185         avcodec_flush_buffers(d->pVideoCodecContext);
0186     }
0187     else
0188     {
0189         qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed";
0190         return;
0191     }
0192 
0193     int keyFrameAttempts = 0;
0194     bool gotFrame        = false;
0195 
0196     do
0197     {
0198         int count = 0;
0199         gotFrame  = false;
0200 
0201         while (!gotFrame && (count < 20))
0202         {
0203             d->getVideoPacket();
0204             gotFrame = d->decodeVideoPacket();
0205             count++;
0206         }
0207 
0208         keyFrameAttempts++;
0209     }
0210     while ((!gotFrame || !d->pFrame->key_frame) &&
0211            (keyFrameAttempts < 200));
0212 
0213     if (!gotFrame)
0214     {
0215         qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed";
0216     }
0217 }
0218 
0219 bool VideoThumbDecoder::decodeVideoFrame() const
0220 {
0221     bool frameFinished = false;
0222 
0223     while (!frameFinished && d->getVideoPacket())
0224     {
0225         frameFinished = d->decodeVideoPacket();
0226     }
0227 
0228     if (!frameFinished)
0229     {
0230         qDebug(DIGIKAM_GENERAL_LOG) << "decodeVideoFrame() failed: frame not finished";
0231     }
0232 
0233     return frameFinished;
0234 }
0235 
0236 void VideoThumbDecoder::getScaledVideoFrame(int scaledSize,
0237                                        bool maintainAspectRatio,
0238                                        VideoFrame& videoFrame)
0239 {
0240     if (d->pFrame->interlaced_frame)
0241     {
0242         d->processFilterGraph(d->pFrame,
0243                               d->pFrame,
0244                               d->pVideoCodecContext->pix_fmt,
0245                               d->pVideoCodecContext->width,
0246                               d->pVideoCodecContext->height);
0247     }
0248 
0249     int scaledWidth, scaledHeight;
0250     d->convertAndScaleFrame(AV_PIX_FMT_RGB24,
0251                             scaledSize,
0252                             maintainAspectRatio,
0253                             scaledWidth,
0254                             scaledHeight);
0255 
0256     videoFrame.width    = scaledWidth;
0257     videoFrame.height   = scaledHeight;
0258     videoFrame.lineSize = d->pFrame->linesize[0];
0259 
0260     videoFrame.frameData.clear();
0261     videoFrame.frameData.resize(videoFrame.lineSize * videoFrame.height);
0262     memcpy((&(videoFrame.frameData.front())),
0263            d->pFrame->data[0],
0264            videoFrame.lineSize * videoFrame.height);
0265 }
0266 
0267 } // namespace Digikam