File indexing completed on 2024-05-05 04:22:00

0001 // SPDX-FileCopyrightText: 2012-2020 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 
0006 #include "ExtractOneVideoFrame.h"
0007 
0008 #include <DB/CategoryCollection.h>
0009 #include <DB/ImageDB.h>
0010 #include <MainWindow/DirtyIndicator.h>
0011 #include <MainWindow/FeatureDialog.h>
0012 #include <MainWindow/TokenEditor.h>
0013 #include <MainWindow/Window.h>
0014 #include <Utilities/Process.h>
0015 #include <kpabase/Logging.h>
0016 #include <kpabase/StringSet.h>
0017 
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 #include <QDir>
0021 #include <QTemporaryDir>
0022 
0023 namespace ImageManager
0024 {
0025 QString ExtractOneVideoFrame::s_tokenForShortVideos;
0026 
0027 #define STR(x) QString::fromUtf8(x)
0028 void ExtractOneVideoFrame::extract(const DB::FileName &fileName, double offset, QObject *receiver, const char *slot)
0029 {
0030     if (MainWindow::FeatureDialog::hasVideoThumbnailer())
0031         new ExtractOneVideoFrame(fileName, offset, receiver, slot);
0032 }
0033 
0034 void ExtractOneVideoFrame::processFinished(int exitCode, QProcess::ExitStatus status)
0035 {
0036     if (status == QProcess::ExitStatus::NormalExit && exitCode == 0) {
0037         frameFetched();
0038     } else {
0039         handleError(m_process->error());
0040     }
0041 }
0042 
0043 ExtractOneVideoFrame::ExtractOneVideoFrame(const DB::FileName &fileName, double offset, QObject *receiver, const char *slot)
0044 {
0045     m_fileName = fileName;
0046     const QString tmpPath = STR("%1/KPA-XXXXXX").arg(QDir::tempPath());
0047     m_workingDirectory = new QTemporaryDir(tmpPath);
0048     if (!m_workingDirectory->isValid())
0049         qCWarning(ImageManagerLog) << "Failed to create temporary directory!";
0050     m_process = new Utilities::Process(this);
0051     m_process->setWorkingDirectory(m_workingDirectory->path());
0052 
0053     connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &ExtractOneVideoFrame::processFinished);
0054     connect(this, SIGNAL(result(QImage)), receiver, slot);
0055 
0056     Q_ASSERT(MainWindow::FeatureDialog::hasVideoThumbnailer());
0057     QStringList arguments;
0058     // analyzeduration is for videos where the videostream starts later than the sound
0059     arguments << STR("-ss") << QString::number(offset, 'f', 4) << STR("-analyzeduration")
0060               << STR("200M") << STR("-i") << fileName.absolute() << STR("-vf") << STR("thumbnail")
0061               << STR("-vframes") << STR("20") << m_workingDirectory->filePath(STR("000000%02d.png"));
0062 
0063     qCDebug(ImageManagerLog, "%s %s", qPrintable(MainWindow::FeatureDialog::ffmpegBinary()), qPrintable(arguments.join(QString::fromLatin1(" "))));
0064 
0065     m_process->start(MainWindow::FeatureDialog::ffmpegBinary(), arguments);
0066 }
0067 
0068 void ExtractOneVideoFrame::frameFetched()
0069 {
0070     if (!QFile::exists(m_workingDirectory->filePath(STR("00000020.png"))))
0071         markShortVideo(m_fileName);
0072 
0073     QString name;
0074     for (int i = 20; i > 0; --i) {
0075         name = m_workingDirectory->filePath(STR("000000%1.png").arg(i, 2, 10, QChar::fromLatin1('0')));
0076         if (QFile::exists(name)) {
0077             qCDebug(ImageManagerLog) << "Using video frame " << i;
0078             break;
0079         }
0080     }
0081 
0082     QImage image(name);
0083     Q_EMIT result(image);
0084     delete m_workingDirectory;
0085     deleteLater();
0086 }
0087 
0088 void ExtractOneVideoFrame::handleError(QProcess::ProcessError error)
0089 {
0090     QString message;
0091     switch (error) {
0092     case QProcess::FailedToStart:
0093         message = i18n("Failed to start");
0094         break;
0095     case QProcess::Crashed:
0096         message = i18n("Crashed");
0097         break;
0098     case QProcess::Timedout:
0099         message = i18n("Timedout");
0100         break;
0101     case QProcess::ReadError:
0102         message = i18n("Read error");
0103         break;
0104     case QProcess::WriteError:
0105         message = i18n("Write error");
0106         break;
0107     case QProcess::UnknownError:
0108         message = i18n("Unknown error");
0109         break;
0110     }
0111 
0112     KMessageBox::information(MainWindow::Window::theMainWindow(),
0113                              i18n("<p>Error when extracting video thumbnails.<br/>Error was: %1</p>", message),
0114                              QString(), QLatin1String("errorWhenRunningQProcessFromExtractOneVideoFrame"));
0115     Q_EMIT result(QImage());
0116     deleteLater();
0117 }
0118 
0119 void ExtractOneVideoFrame::markShortVideo(const DB::FileName &fileName)
0120 {
0121     if (s_tokenForShortVideos.isNull()) {
0122         const auto tokensInUse = MainWindow::TokenEditor::tokensInUse();
0123         Utilities::StringSet usedTokens(tokensInUse.begin(), tokensInUse.end());
0124         for (int ch = 'A'; ch <= 'Z'; ++ch) {
0125             QString token = QChar::fromLatin1((char)ch);
0126             if (!usedTokens.contains(token)) {
0127                 s_tokenForShortVideos = token;
0128                 break;
0129             }
0130         }
0131 
0132         if (s_tokenForShortVideos.isNull()) {
0133             // Hmmm, no free token. OK lets just skip setting tokens.
0134             return;
0135         }
0136         KMessageBox::information(MainWindow::Window::theMainWindow(),
0137                                  i18n("Unable to extract video thumbnails from some files. "
0138                                       "Either the file is damaged in some way, or the video is ultra short. "
0139                                       "For your convenience, the token '%1' "
0140                                       "has been set on those videos.\n\n"
0141                                       "(You might need to wait till the video extraction led in your status bar has stopped blinking, "
0142                                       "to see all affected videos.)",
0143                                       s_tokenForShortVideos));
0144     }
0145 
0146     DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName);
0147     DB::CategoryPtr tokensCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::TokensCategory);
0148     info->addCategoryInfo(tokensCategory->name(), s_tokenForShortVideos);
0149     MainWindow::DirtyIndicator::markDirty();
0150 }
0151 
0152 } // namespace ImageManager
0153 // vi:expandtab:tabstop=4 shiftwidth=4:
0154 
0155 #include "moc_ExtractOneVideoFrame.cpp"