[KPhotoAlbum] restructuring video thumbnail generation with ffmpeg
Andreas Schleth
schleth_es at web.de
Thu Sep 7 23:25:26 BST 2017
Hi!
with friendly help from Tobias, I prepared a patch for video thumbnail
generation with ffmpeg / ffprobe. In my installation it builds and works
for me (TM).
Purpose:
Some strange videos do not have duration info in the stream data.
These videos produced the length "N/A". Thus, here the duration info is
taken
from somewhere else and parsed accordingly. Here is one of the
offending videos (a short one): https://my.hidrive.com/lnk/3BrpUFZJ
Then, "short" videos were marked when *any* of the 10 extraction
processes would produce less than 20 images - this seems strange to me.
Thus, I moved the markShort-function from ExtractOneVideoFrame.cpp
to VideoLengthExtractor.cpp and call it for videos of less than 0.5 s
length.
At 24 fps this would mean: less 12 frames or less is "short".
I hope, this patch does not break the mplayer functionality. I do not
have mplayer. Please, could someone test this against mplayer?
I will be a.f.k. for a few days - so discussions next week...
Cheers, Andreas
PS: I found two conventions for writing the {}-clauses:
a) (my preference)
if (...) {
do something
} else {
whatever
}
b)
if (...)
{
do something
}
Which one is the preferred one in KPA?
-------------- next part --------------
diff --git a/ImageManager/ExtractOneVideoFrame.cpp b/ImageManager/ExtractOneVideoFrame.cpp
index da29a1e8..684d3d79 100644
--- a/ImageManager/ExtractOneVideoFrame.cpp
+++ b/ImageManager/ExtractOneVideoFrame.cpp
@@ -28,15 +28,12 @@
#include <DB/CategoryCollection.h>
#include <DB/ImageDB.h>
-#include <MainWindow/DirtyIndicator.h>
#include <MainWindow/FeatureDialog.h>
-#include <MainWindow/TokenEditor.h>
#include <MainWindow/Window.h>
#include <Utilities/Process.h>
#include <Utilities/Set.h>
namespace ImageManager {
-QString ExtractOneVideoFrame::s_tokenForShortVideos;
#define STR(x) QString::fromUtf8(x)
void ExtractOneVideoFrame::extract(const DB::FileName &fileName, double offset, QObject* receiver, const char* slot)
@@ -67,9 +64,18 @@ ExtractOneVideoFrame::ExtractOneVideoFrame(const DB::FileName &fileName, double
} else {
QStringList arguments;
arguments << STR("-ss") << QString::number(offset, 'f', 4) << STR("-i") << fileName.absolute()
- << STR("-sn") << STR("-an")
- << STR("-vframes") << STR("20")
+ << STR("-vf") << STR("thumbnail") << STR("-vframes") << STR("20")
<< m_workingDirectory + STR("/000000%02d.png");
+ // old:
+ // << STR("-sn") << STR("-an")
+ // << STR("-vframes") << STR("20")
+ // << m_workingDirectory + STR("/000000%02d.png");
+ //
+ // The -sn and -an parameters are unnecessary if output is an image
+ // This additional parameter directly finds frames suitable for thumbnails (usually 20 times the same frame): -vf thumbnail
+ // Thus it might be unnecessary to produce 20 pngs and then sift through them.
+ // "Shortness" detection should be done with parsing the duration parameter in VideoLenghtExtractor.cpp
+ //
//qDebug( "%s %s", qPrintable(MainWindow::FeatureDialog::ffmpegBinary()), qPrintable(arguments.join(QString::fromLatin1(" "))));
m_process->start(MainWindow::FeatureDialog::ffmpegBinary(), arguments);
@@ -78,21 +84,27 @@ ExtractOneVideoFrame::ExtractOneVideoFrame(const DB::FileName &fileName, double
void ExtractOneVideoFrame::frameFetched()
{
- if (!QFile::exists(m_workingDirectory + STR("/00000020.png")))
- markShortVideo(m_fileName);
-
+ // if (!QFile::exists(m_workingDirectory + STR("/00000020.png")))
+ // markShortVideo(m_fileName);
+ // moved to VideoLengthExtractor.cpp
+ // these 20 files are just there for compatibility with the mplayer-variant
QString name;
for (int i = 20; i>0; --i) {
- name = m_workingDirectory +STR("/000000%1.png").arg(i,2, 10, QChar::fromLatin1('0'));
- if (QFile::exists(name))
- {
- //qDebug() << "Using video frame " << i;
- break;
- }
+ name = m_workingDirectory +STR("/000000%1.png").arg(i,2, 10, QChar::fromLatin1('0'));
+ if (QFile::exists(name)) {
+ //qDebug() << "Using video frame " << i;
+ break;
+ }
+ }
+ if (!QFile::exists(name)) {
+ // the old code was missing any error action
+ // is there something, we could emit instead?
+ // This is incomplete!
+ qDebug( "%s", qPrintable(QString::fromUtf8("ExtractOneVideoFrame::fframeFetched(): **no frame found**")));
+ } else {
+ QImage image(name);
+ emit result(image);
}
-
- QImage image(name);
- emit result(image);
deleteWorkingDirectory();
deleteLater();
}
@@ -132,37 +144,5 @@ void ExtractOneVideoFrame::deleteWorkingDirectory()
dir.rmdir(m_workingDirectory);
}
-void ExtractOneVideoFrame::markShortVideo(const DB::FileName &fileName)
-{
- if (s_tokenForShortVideos.isNull()) {
- Utilities::StringSet usedTokens = MainWindow::TokenEditor::tokensInUse().toSet();
- for ( int ch = 'A'; ch <= 'Z'; ++ch ) {
- QString token = QChar::fromLatin1( (char) ch );
- if (!usedTokens.contains(token)) {
- s_tokenForShortVideos = token;
- break;
- }
- }
-
- if (s_tokenForShortVideos.isNull()) {
- // Hmmm, no free token. OK lets just skip setting tokens.
- return;
- }
- KMessageBox::information(MainWindow::Window::theMainWindow(),
- i18n("Unable to extract video thumbnails from some files. "
- "Either the file is damaged in some way, or the video is ultra short. "
- "For your convenience, the token '%1' "
- "has been set on those videos.\n\n"
- "(You might need to wait till the video extraction led in your status bar has stopped blinking, "
- "to see all affected videos.)",
- s_tokenForShortVideos));
- }
-
- DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName);
- DB::CategoryPtr tokensCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::TokensCategory);
- info->addCategoryInfo(tokensCategory->name(), s_tokenForShortVideos);
- MainWindow::DirtyIndicator::markDirty();
-}
-
} // namespace ImageManager
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/ImageManager/ExtractOneVideoFrame.h b/ImageManager/ExtractOneVideoFrame.h
index 123f0b96..4e6ad441 100644
--- a/ImageManager/ExtractOneVideoFrame.h
+++ b/ImageManager/ExtractOneVideoFrame.h
@@ -1,5 +1,5 @@
/* Copyright 2012 Jesper K. Pedersen <blackie at kde.org>
-
+
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 of
@@ -7,12 +7,12 @@
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
-
+
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.
-
+
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -52,12 +52,10 @@ private:
ExtractOneVideoFrame(const DB::FileName& filename, double offset, QObject* receiver, const char* slot);
void setupWorkingDirectory();
void deleteWorkingDirectory();
- void markShortVideo(const DB::FileName& fileName);
QString m_workingDirectory;
Utilities::Process* m_process;
DB::FileName m_fileName;
- static QString s_tokenForShortVideos;
};
} // namespace ImageManager
diff --git a/ImageManager/VideoLengthExtractor.cpp b/ImageManager/VideoLengthExtractor.cpp
index 06d6c088..5dee33d8 100644
--- a/ImageManager/VideoLengthExtractor.cpp
+++ b/ImageManager/VideoLengthExtractor.cpp
@@ -19,9 +19,17 @@
#include "VideoLengthExtractor.h"
#include <Utilities/Process.h>
+#include <Utilities/Set.h>
#include <QDir>
+#include <MainWindow/DirtyIndicator.h>
#include <MainWindow/FeatureDialog.h>
+#include <MainWindow/TokenEditor.h>
+#include <MainWindow/Window.h>
#include <QDebug>
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <DB/CategoryCollection.h>
+#include <DB/ImageDB.h>
#define STR(x) QString::fromUtf8(x)
@@ -35,6 +43,7 @@ ImageManager::VideoLengthExtractor::VideoLengthExtractor(QObject *parent) :
QObject(parent), m_process(nullptr)
{
}
+QString ImageManager::VideoLengthExtractor::s_tokenForShortVideos;
void ImageManager::VideoLengthExtractor::extract(const DB::FileName &fileName)
{
@@ -55,8 +64,7 @@ void ImageManager::VideoLengthExtractor::extract(const DB::FileName &fileName)
m_process->setWorkingDirectory(QDir::tempPath());
connect( m_process, SIGNAL(finished(int)), this, SLOT(processEnded()));
- if (MainWindow::FeatureDialog::ffmpegBinary().isEmpty())
- {
+ if (MainWindow::FeatureDialog::ffmpegBinary().isEmpty()) {
QStringList arguments;
arguments << STR("-identify") << STR("-frames") << STR("0") << STR("-vc") << STR("null")
<< STR("-vo") << STR("null") << STR("-ao") << STR("null") << fileName.absolute();
@@ -64,11 +72,12 @@ void ImageManager::VideoLengthExtractor::extract(const DB::FileName &fileName)
m_process->start(MainWindow::FeatureDialog::mplayerBinary(), arguments);
} else {
QStringList arguments;
- arguments << STR("-v") << STR("error") << STR("-select_streams") << STR("v:0")
- << STR("-show_entries") << STR("stream=duration")
- << STR("-of") << STR("default=noprint_wrappers=1:nokey=1")
- << fileName.absolute();
-
+ arguments << STR("-hide_banner") << fileName.absolute();
+ // original command was:
+ // ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 [filename]
+ // This command fails for some videos, if the stream has no duration data.
+ // The old cmd wrote to stdout, the new writes the interesting things to stderr
+ //
//qDebug( "%s %s", qPrintable(MainWindow::FeatureDialog::ffprobeBinary()), qPrintable(arguments.join(QString::fromLatin1(" "))));
m_process->start(MainWindow::FeatureDialog::ffprobeBinary(), arguments);
}
@@ -80,9 +89,12 @@ void ImageManager::VideoLengthExtractor::processEnded()
Debug() << m_process->stderr();
QString lenStr;
+ QStringList list;
+ bool ok = false;
+ double length;
if (MainWindow::FeatureDialog::ffmpegBinary().isEmpty())
{
- QStringList list = m_process->stdout().split(QChar::fromLatin1('\n'));
+ list = m_process->stdout().split(QChar::fromLatin1('\n'));
list = list.filter(STR("ID_LENGTH="));
if ( list.count() == 0 ) {
qWarning() << "Unable to find ID_LENGTH in output from MPlayer for file " << m_fileName.absolute() << "\n"
@@ -102,23 +114,46 @@ void ImageManager::VideoLengthExtractor::processEnded()
}
lenStr = regexp.cap(1);
+ length = lenStr.toDouble(&ok);
} else {
- QStringList list = m_process->stdout().split(QChar::fromLatin1('\n'));
- // one line-break -> 2 parts
- // some videos with subtitles or other additional streams might have more than one line
- // in these cases, we just take the first one as both lengths should be the same anyways
- if ( list.count() < 2 ) {
- qWarning() << "Unable to parse video length from ffprobe output!"
+ list = m_process->stderr().split(QChar::fromLatin1('\n'));
+ list = list.filter(STR("Duration:"));
+ if ( list.count() == 0 ) {
+ qWarning() << "Unable to find Duration in stderr from ffprobe for file " << m_fileName.absolute() << "\n"
<< "Output was:\n"
- << m_process->stdout();
+ << m_process->stderr();
+ emit unableToDetermineLength();
+ return;
+ }
+ // The string we have is: " Duration: 01:28:32.34, somewhat more data"
+ // We split 3 times: first at the blanks, then at the comma and then at the colons
+ // To be independet of the number of blanks, we filter for the first comma
+ list = list[0].split(QChar::fromLatin1(' '));
+ list = list.filter(STR(","));
+ if ( list.count() > 0 ) {
+ lenStr = list[0];
+ list = list[0].split(QChar::fromLatin1(','));
+ list = list[0].split(QChar::fromLatin1(':'));
+ } else {
+ qWarning() << "Unable find sexagesimal time value from ffprobe for file " << m_fileName.absolute() << "\n";
+ emit unableToDetermineLength();
+ return;
+ }
+ const double hh = list[0].toDouble(&ok);
+ const double mm = list[1].toDouble(&ok);
+ const double ss = list[2].toDouble(&ok);
+ if(ok) {
+ length = 3600.*hh + 60.*mm + ss;
+ if(length < 0.5) {
+ markShortVideo(m_fileName);
+ }
+ } else {
+ qWarning() << "Unable assemble double from sexagesimal time value for file " << m_fileName.absolute() << "\n";
emit unableToDetermineLength();
return;
}
- lenStr = list[0].trimmed();
}
- bool ok = false;
- const double length = lenStr.toDouble(&ok);
if ( !ok ) {
qWarning() << STR("Unable to convert string \"%1\"to double (for file %2)").arg(lenStr).arg(m_fileName.absolute());
emit unableToDetermineLength();
@@ -135,4 +170,39 @@ void ImageManager::VideoLengthExtractor::processEnded()
m_process->deleteLater();
m_process = nullptr;
}
+
+// moved here from ExtractOneVideoFrame.cpp and renamed
+// don't know if all dependencies are here (moved it in .h as well)
+void ImageManager::VideoLengthExtractor::markShortVideo(const DB::FileName &fileName)
+{
+ if (s_tokenForShortVideos.isNull()) {
+ Utilities::StringSet usedTokens = MainWindow::TokenEditor::tokensInUse().toSet();
+ for ( int ch = 'A'; ch <= 'Z'; ++ch ) {
+ QString token = QChar::fromLatin1( (char) ch );
+ if (!usedTokens.contains(token)) {
+ s_tokenForShortVideos = token;
+ break;
+ }
+ }
+
+ if (s_tokenForShortVideos.isNull()) {
+ // Hmmm, no free token. OK lets just skip setting tokens.
+ return;
+ }
+ KMessageBox::information(MainWindow::Window::theMainWindow(),
+ i18n("Unable to extract video thumbnails from some files. "
+ "Either the file is damaged in some way, or the video is ultra short. "
+ "For your convenience, the token '%1' "
+ "has been set on those videos.\n\n"
+ "(You might need to wait till the video extraction led in your status bar has stopped blinking, "
+ "to see all affected videos.)",
+ s_tokenForShortVideos));
+ }
+
+ DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName);
+ DB::CategoryPtr tokensCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::TokensCategory);
+ info->addCategoryInfo(tokensCategory->name(), s_tokenForShortVideos);
+ MainWindow::DirtyIndicator::markDirty();
+}
+
// vi:expandtab:tabstop=4 shiftwidth=4:
diff --git a/ImageManager/VideoLengthExtractor.h b/ImageManager/VideoLengthExtractor.h
index ff925dcb..facdbf11 100644
--- a/ImageManager/VideoLengthExtractor.h
+++ b/ImageManager/VideoLengthExtractor.h
@@ -37,7 +37,7 @@ class VideoLengthExtractor : public QObject
public:
explicit VideoLengthExtractor(QObject *parent = nullptr);
void extract(const DB::FileName& fileName );
-
+
signals:
void lengthFound(int length);
void unableToDetermineLength();
@@ -48,6 +48,8 @@ private slots:
private:
Utilities::Process* m_process;
DB::FileName m_fileName;
+ void markShortVideo(const DB::FileName& fileName);
+ static QString s_tokenForShortVideos;
};
}
More information about the Kphotoalbum
mailing list