File indexing completed on 2025-01-19 03:57:00

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2017-07-04
0007  * Description : FFmpeg launcher to encode frames as video
0008  *
0009  * SPDX-FileCopyrightText: 2017-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "ffmpeglauncher.h"
0016 
0017 // C++ includes
0018 
0019 #include <cmath>
0020 
0021 // Qt includes
0022 
0023 #include <QEventLoop>
0024 #include <QDateTime>
0025 #include <QStringList>
0026 
0027 // Local includes
0028 
0029 #include "digikam_debug.h"
0030 #include "digikam_version.h"
0031 
0032 namespace Digikam
0033 {
0034 
0035 FFmpegLauncher::FFmpegLauncher(QObject* const parent)
0036     : ProcessLauncher(parent)
0037 {
0038 }
0039 
0040 FFmpegLauncher::~FFmpegLauncher()
0041 {
0042 }
0043 
0044 void FFmpegLauncher::setSettings(VidSlideSettings* const settings)
0045 {
0046     m_settings = settings;
0047 }
0048 
0049 void FFmpegLauncher::encodeFrames()
0050 {
0051     // Run FFmpeg CLI to encode temporary JPEG frames
0052     // https://shotstack.io/learn/use-ffmpeg-to-convert-images-to-video/
0053     // ffmpeg -f concat -i fileslist.txt -b:v 200000 -r 25 -vcodec mpeg4 -y output.mp4
0054 
0055     qCDebug(DIGIKAM_GENERAL_LOG) << "Start encoding with FFmpeg";
0056 
0057     setWorkingDirectory(m_settings->outputDir);
0058     setProgram(m_settings->ffmpegPath);
0059     QStringList args;
0060 
0061     // Frames input
0062 
0063     args << QLatin1String("-f")
0064          << QLatin1String("concat")
0065          << QLatin1String("-i")
0066          << m_settings->filesList;                                // File list of frames to encode.
0067 
0068     // Audio input
0069 
0070     if (!m_settings->audioTrack.isEmpty())
0071     {
0072         args << QLatin1String("-stream_loop")
0073              << QLatin1String("-1")
0074              << QLatin1String("-i")
0075              << m_settings->audioTrack                            // Audio file to use as soundtrack.
0076              << QLatin1String("-c:a")
0077              << QLatin1String("copy")                             // Do not reencode
0078              << QLatin1String("-shortest");
0079     }
0080 
0081     // Metadata (not supported by all media containers - work fine with MP4)
0082 
0083     args << QLatin1String("-metadata")
0084          << QString::fromLatin1("date=\"%1\"").arg(QDateTime::currentDateTime().toString(Qt::ISODate))
0085          << QLatin1String("-metadata")
0086          << QString::fromLatin1("description=\"Encoded with digiKam VideoSlideShow tool version %1\"").arg(digiKamVersion());
0087 
0088     // Egualize video luminosity.
0089     // https://ffmpeg.org/ffmpeg-filters.html#tmidequalizer
0090 
0091     if (m_settings->equalize)
0092     {
0093         args << QLatin1String("-vf")
0094              << QString::fromLatin1("tmidequalizer=sigma=%1")
0095                     .arg(m_settings->strength / 10.0);
0096     }
0097 
0098     // Video encoding
0099 
0100     args << QLatin1String("-b:v")                                 // Video bits-rate/
0101          << QString::number(m_settings->videoBitRate())
0102          << QLatin1String("-r")                                   // Video frames-rate.
0103          << QString::number(ceil(m_settings->videoFrameRate()))
0104          << QLatin1String("-vcodec")                              // Video codec.
0105          << m_settings->videoCodec()
0106          << QLatin1String("-y")                                   // Overwrite target.
0107          << m_settings->outputFile;                               // Target video stream.
0108 
0109     setArguments(args);
0110     startProcess();
0111 }
0112 
0113 QMap<QString, QString> FFmpegLauncher::supportedCodecs()
0114 {
0115     qCDebug(DIGIKAM_GENERAL_LOG) << "Get FFmpeg supported codecs";
0116 
0117     setConsoleTraces(false);
0118     setProgram(m_settings->ffmpegPath);
0119     setArguments(QStringList() << QLatin1String("-v")
0120                                << QLatin1String("quiet")
0121                                << QLatin1String("-codecs"));
0122 
0123     QEventLoop loop;
0124 
0125     connect(this, &ProcessLauncher::signalComplete,
0126             &loop, &QEventLoop::quit);
0127 
0128     startProcess();
0129     loop.exec();
0130 
0131     QMap<QString, QString> codecMap;        // name, features
0132     QString out        = output().section(QLatin1String("-------"), -1);
0133     QStringList codecs = out.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0134 
0135     Q_FOREACH (const QString& line, codecs)
0136     {
0137         QStringList sections = line.simplified().split(QLatin1Char(' '), Qt::SkipEmptyParts);
0138 
0139         if (sections.size() >= 2)
0140         {
0141             codecMap.insert(sections[1], sections[0]);
0142         }
0143     }
0144 
0145     return codecMap;
0146 }
0147 
0148 QMap<QString, QString> FFmpegLauncher::supportedFormats()
0149 {
0150     qCDebug(DIGIKAM_GENERAL_LOG) << "Get FFmpeg supported formats";
0151 
0152     setConsoleTraces(false);
0153     setProgram(m_settings->ffmpegPath);
0154     setArguments(QStringList() << QLatin1String("-v")
0155                                << QLatin1String("quiet")
0156                                << QLatin1String("-formats"));
0157 
0158     QEventLoop loop;
0159 
0160     connect(this, &ProcessLauncher::signalComplete,
0161             &loop, &QEventLoop::quit);
0162 
0163     startProcess();
0164     loop.exec();
0165 
0166     QMap<QString, QString> formatMap;        // name, features
0167     QString out         = output().section(QLatin1String("--"), -1);
0168     QStringList formats = out.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0169 
0170     Q_FOREACH (const QString& line, formats)
0171     {
0172         QStringList sections = line.simplified().split(QLatin1Char(' '), Qt::SkipEmptyParts);
0173 
0174         if (sections.size() >= 2)
0175         {
0176             formatMap.insert(sections[1], sections[0]);
0177         }
0178     }
0179 
0180     return formatMap;
0181 }
0182 
0183 QTime FFmpegLauncher::soundTrackLength(const QString& audioPath)
0184 {
0185     qCDebug(DIGIKAM_GENERAL_LOG) << "Get soundtrack length with FFmpeg";
0186 
0187     setConsoleTraces(false);
0188     setProgram(m_settings->ffmpegPath);
0189     setArguments(QStringList() << QLatin1String("-i")
0190                                << audioPath);
0191 
0192     QEventLoop loop;
0193 
0194     connect(this, &ProcessLauncher::signalComplete,
0195             &loop, &QEventLoop::quit);
0196 
0197     startProcess();
0198     loop.exec();
0199 
0200     QStringList lines = output().split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0201 
0202     Q_FOREACH (const QString& line, lines)
0203     {
0204         if (line.contains(QLatin1String("Duration")))
0205         {
0206             QStringList sections = line.simplified().split(QLatin1Char(','), Qt::SkipEmptyParts);
0207 
0208             if (!sections.isEmpty())
0209             {
0210                 QStringList sections2 = sections[0].split(QLatin1String(" "), Qt::SkipEmptyParts);
0211 
0212                 if (sections2.size() == 2)
0213                 {
0214                     return QTime::fromString(sections2[1], QLatin1String("hh:mm:ss.zz"));
0215                 }
0216             }
0217         }
0218     }
0219 
0220     return QTime();
0221 }
0222 
0223 } // namespace Digikam
0224 
0225 #include "moc_ffmpeglauncher.cpp"