[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