2018-07-10 13:04:35 +00:00
// SPDX-License-Identifier: GPL-2.0
# include "videoframeextractor.h"
# include "imagedownloader.h"
# include "core/pref.h"
2019-08-05 17:41:15 +00:00
# include "core/errorhelper.h"
2018-07-10 13:04:35 +00:00
# include <QtConcurrent>
# include <QProcess>
// Note: this is a global instead of a function-local variable on purpose.
// We don't want this to be generated in a different thread context if
// VideoFrameExtractor::instance() is called from a worker thread.
static VideoFrameExtractor frameExtractor ;
VideoFrameExtractor * VideoFrameExtractor : : instance ( )
{
return & frameExtractor ;
}
VideoFrameExtractor : : VideoFrameExtractor ( )
{
// Currently, we only process one video at a time.
// Eventually, we might want to increase this value.
pool . setMaxThreadCount ( 1 ) ;
}
void VideoFrameExtractor : : extract ( QString originalFilename , QString filename , duration_t duration )
{
QMutexLocker l ( & lock ) ;
if ( ! workingOn . contains ( originalFilename ) ) {
// We are not currently extracting this video - add it to the list.
workingOn . insert ( originalFilename , QtConcurrent : : run ( & pool , [ this , originalFilename , filename , duration ] ( )
{ processItem ( originalFilename , filename , duration ) ; } ) ) ;
}
}
void VideoFrameExtractor : : fail ( const QString & originalFilename , duration_t duration , bool isInvalid )
{
if ( isInvalid )
emit invalid ( originalFilename , duration ) ;
else
emit failed ( originalFilename , duration ) ;
QMutexLocker l ( & lock ) ;
workingOn . remove ( originalFilename ) ;
}
void VideoFrameExtractor : : clearWorkQueue ( )
{
QMutexLocker l ( & lock ) ;
for ( auto it = workingOn . begin ( ) ; it ! = workingOn . end ( ) ; + + it )
it - > cancel ( ) ;
workingOn . clear ( ) ;
}
// Trivial helper: bring value into given range
template < typename T >
T clamp ( T v , T lo , T hi )
{
return v < lo ? lo : v > hi ? hi : v ;
}
void VideoFrameExtractor : : processItem ( QString originalFilename , QString filename , duration_t duration )
{
// If video frame extraction is turned off (e.g. because we failed to start ffmpeg),
// abort immediately.
if ( ! prefs . extract_video_thumbnails ) {
QMutexLocker l ( & lock ) ;
workingOn . remove ( originalFilename ) ;
return ;
}
// Determine the time where we want to extract the image.
// If the duration is < 10 sec, just snap the first frame
2024-05-04 17:15:47 +00:00
duration_t position ;
2018-07-10 13:04:35 +00:00
if ( duration . seconds > 10 ) {
// We round to second-precision. To be sure that we don't attempt reading past the
// video's end, round down by one second.
- - duration . seconds ;
position . seconds = clamp ( duration . seconds * prefs . extract_video_thumbnails_position / 100 ,
0 , duration . seconds ) ;
}
QString posString = QString ( " %1:%2:%3 " ) . arg ( position . seconds / 3600 , 2 , 10 , QChar ( ' 0 ' ) )
. arg ( ( position . seconds % 3600 ) / 60 , 2 , 10 , QChar ( ' 0 ' ) )
. arg ( position . seconds % 60 , 2 , 10 , QChar ( ' 0 ' ) ) ;
QProcess ffmpeg ;
2024-06-13 20:59:32 +00:00
ffmpeg . start ( prefs . ffmpeg_executable . c_str ( ) , QStringList {
2018-07-10 13:04:35 +00:00
" -ss " , posString , " -i " , filename , " -vframes " , " 1 " , " -q:v " , " 2 " , " -f " , " image2 " , " - "
} ) ;
if ( ! ffmpeg . waitForStarted ( ) ) {
// Since we couldn't sart ffmpeg, turn off thumbnailing
// TODO: call the proper preferences-functions
prefs . extract_video_thumbnails = false ;
2024-03-12 08:17:50 +00:00
report_error ( " %s " , qPrintable ( tr ( " ffmpeg failed to start - video thumbnail creation suspended. To enable video thumbnailing, set working executable in preferences. " ) ) ) ;
2018-07-10 13:04:35 +00:00
return fail ( originalFilename , duration , false ) ;
}
if ( ! ffmpeg . waitForFinished ( ) ) {
2024-03-12 08:17:50 +00:00
report_error ( " %s " , qPrintable ( tr ( " Failed waiting for ffmpeg - video thumbnail creation suspended. To enable video thumbnailing, set working executable in preferences. " ) ) ) ;
2018-07-10 13:04:35 +00:00
return fail ( originalFilename , duration , false ) ;
}
QByteArray data = ffmpeg . readAll ( ) ;
QImage img ;
img . loadFromData ( data ) ;
if ( img . isNull ( ) ) {
qInfo ( ) < < " Failed reading ffmpeg output " ;
// For debugging:
//qInfo() << "stdout: " << QString::fromUtf8(data);
ffmpeg . setReadChannel ( QProcess : : StandardError ) ;
// For debugging:
//QByteArray stderr_output = ffmpeg.readAll();
//qInfo() << "stderr: " << QString::fromUtf8(stderr_output);
return fail ( originalFilename , duration , true ) ;
}
2024-01-16 16:39:19 +00:00
emit extracted ( originalFilename , std : : move ( img ) , duration , position ) ;
2018-07-10 13:04:35 +00:00
QMutexLocker l ( & lock ) ;
workingOn . remove ( originalFilename ) ;
}