[KPhotoAlbum] new version of the videopatch
Andreas Schleth
schleth_es at web.de
Mon Sep 11 22:31:51 BST 2017
Hi,
this is a better version of the patch against v5.2-23-g9e32396e:
The old patch still had a few problems with broken video files and a
segfault in one case. Now, it should produce the thumbs for most files,
even ones, where the stream is broken somewhere. Then it outputs a dummy
image / pixmap. In my case it ran at 20-25 films per minute (1-2 GB
files over NFS) and successfully completed.
The only exceptions is the case, when ffprobe does not show any
indication of video duration.
I still could not test, if the Mplayer-functionality is still there and
working properly.
Cheers, Andreas
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.kde.org/pipermail/kphotoalbum/attachments/20170911/3326dece/attachment.htm>
-------------- next part --------------
diff --git a/ImageManager/ExtractOneVideoFrame.cpp b/ImageManager/ExtractOneVideoFrame.cpp
index da29a1e8..38e90a92 100644
--- a/ImageManager/ExtractOneVideoFrame.cpp
+++ b/ImageManager/ExtractOneVideoFrame.cpp
@@ -22,21 +22,19 @@
#include <cstdlib>
#include <QDir>
+#include <QIcon>
#include <KLocalizedString>
#include <KMessageBox>
#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)
@@ -56,8 +54,7 @@ ExtractOneVideoFrame::ExtractOneVideoFrame(const DB::FileName &fileName, double
connect( m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(handleError(QProcess::ProcessError)));
connect( this, SIGNAL(result(QImage)), receiver, slot);
- if (MainWindow::FeatureDialog::ffmpegBinary().isEmpty())
- {
+ if (MainWindow::FeatureDialog::ffmpegBinary().isEmpty()) {
QStringList arguments;
arguments << STR("-nosound") << STR("-ss") << QString::number(offset,'f',4) << STR("-vf")
<< STR("screenshot") << STR("-frames") << STR("20") << STR("-vo") << STR("png:z=9") << fileName.absolute();
@@ -66,33 +63,55 @@ ExtractOneVideoFrame::ExtractOneVideoFrame(const DB::FileName &fileName, double
m_process->start(MainWindow::FeatureDialog::mplayerBinary(), arguments);
} else {
QStringList arguments;
- arguments << STR("-ss") << QString::number(offset, 'f', 4) << STR("-i") << fileName.absolute()
- << STR("-sn") << STR("-an")
- << STR("-vframes") << STR("20")
- << m_workingDirectory + STR("/000000%02d.png");
+ arguments << STR("-ss") << QString::number(offset, 'f', 4) << STR("-analyzeduration")
+ << STR("1G") << STR("-i") << fileName.absolute() << 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");
+ //
+ // -sn and -an: both are unnecessary if output is an image
+ // -analyzeduration 1G: some videos have some junk at the beginning thus 1G microseconds
+ // will be searched before deciding whether there is a video stream
+ // -vf thumbnail: This finds frames suitable for thumbnails (usually 20x the same frame).
+ // Thus it might be unnecessary to produce 20 pngs and then sift through them.
+ //
//qDebug( "%s %s", qPrintable(MainWindow::FeatureDialog::ffmpegBinary()), qPrintable(arguments.join(QString::fromLatin1(" "))));
-
m_process->start(MainWindow::FeatureDialog::ffmpegBinary(), arguments);
}
}
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
+ // we need to emit something instead, otherwise KPA hangs
+ // This is from AsyncLoader.cpp and an ugly workaround
+ QIcon brokenFileIcon = QIcon::fromTheme( QLatin1String("file-broken") ); // krazy:exclude=iconnames
+ if ( brokenFileIcon.isNull() ) {
+ brokenFileIcon = QIcon::fromTheme( QLatin1String("image-x-generic") );
}
+ QImage m_brokenImage = brokenFileIcon.pixmap(256).toImage();
+ emit result(m_brokenImage);
+ qDebug( "%s %s %s", qPrintable(QString::fromUtf8("ExtractOneVideoFrame::fframeFetched(): frame not found:")),
+ qPrintable(name), qPrintable(m_fileName.relative()));
+ } else {
+ QImage image(name);
+ emit result(image);
}
-
- QImage image(name);
- emit result(image);
deleteWorkingDirectory();
deleteLater();
}
@@ -132,37 +151,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..35526a39 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,25 +72,33 @@ 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();
-
- //qDebug( "%s %s", qPrintable(MainWindow::FeatureDialog::ffprobeBinary()), qPrintable(arguments.join(QString::fromLatin1(" "))));
+ 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
+ // Nevertheless, sometimes even this results in "N/A" (then the stream also shows N/A)
+ //
+ // qDebug( "%s %s", qPrintable(MainWindow::FeatureDialog::ffprobeBinary()), qPrintable(arguments.join(QString::fromLatin1(" "))));
m_process->start(MainWindow::FeatureDialog::ffprobeBinary(), arguments);
}
}
void ImageManager::VideoLengthExtractor::processEnded()
{
- if ( !m_process->stderr().isEmpty() )
+ if ( !m_process->stderr().isEmpty() ) {
Debug() << m_process->stderr();
+ }
QString lenStr;
- if (MainWindow::FeatureDialog::ffmpegBinary().isEmpty())
- {
- QStringList list = m_process->stdout().split(QChar::fromLatin1('\n'));
+ QStringList list;
+ bool ok = false;
+ double length;
+ double hh;
+ double mm;
+ double ss;
+ if (MainWindow::FeatureDialog::ffmpegBinary().isEmpty()) {
+ 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"
@@ -94,31 +110,60 @@ void ImageManager::VideoLengthExtractor::processEnded()
const QString match = list[0];
const QRegExp regexp(STR("ID_LENGTH=([0-9.]+)"));
- if (!regexp.exactMatch(match))
- {
+ if (!regexp.exactMatch(match)) {
qWarning() << STR("Unable to match regexp for string: %1 (for file %2)").arg(match).arg(m_fileName.absolute());
emit unableToDetermineLength();
return;
}
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() < 1 ) {
+ 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;
+ }
+ //qDebug( "%s %s", qPrintable(MainWindow::FeatureDialog::ffprobeBinary()), qPrintable(list.join(QString::fromLatin1(":"))));
+ if ( list.count() == 3) {
+ hh = list[0].toDouble(&ok);
+ mm = list[1].toDouble(&ok);
+ ss = list[2].toDouble(&ok);
+ } else {
+ ok = false;
+ qDebug( "%s %s", qPrintable(m_fileName.absolute()), qPrintable(list.join(QString::fromLatin1("*"))));
+ qWarning() << "Unable calculate time value for file " << m_fileName.absolute() << "\n";
+ }
+ if(ok) {
+ length = 3600.*hh + 60.*mm + ss;
+ if(length < 0.5) {
+ markShortVideo(m_fileName);
+ }
+ } else {
+ qWarning() << "Unable assemble double from time value(" << list.join(QString::fromLatin1("*")) << ")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 +180,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