Commit 33eec634 authored by Hugo Beauzée-Luyssen's avatar Hugo Beauzée-Luyssen

Try to leverage the thumbnailer to detect unknown file types

This reduces the amount of assumptions we make based on extension.
If we fail to extract some tracks, we will start a playback as we would
for the thumbnailing process. If no video tracks are detected but no
error occur, assume it's an audio file.
parent fb3635ae
......@@ -322,17 +322,13 @@ std::shared_ptr<Media> MediaLibrary::addFile( const fs::IFile& fileFs, Folder& p
};
if ( std::find_if( begin( supportedVideoExtensions ), end( supportedVideoExtensions ),
predicate ) != end( supportedVideoExtensions ) )
predicate ) == end( supportedVideoExtensions ) &&
std::find_if( begin( supportedAudioExtensions ), end( supportedAudioExtensions ),
predicate ) == end( supportedAudioExtensions ) )
{
type = IMedia::Type::VideoType;
}
else if ( std::find_if( begin( supportedAudioExtensions ), end( supportedAudioExtensions ),
predicate ) != end( supportedAudioExtensions ) )
{
type = IMedia::Type::AudioType;
}
if ( type == IMedia::Type::UnknownType )
LOG_INFO( "Rejecting file ", fileFs.fullPath(), " due to its extension" );
return nullptr;
}
LOG_INFO( "Adding ", fileFs.fullPath() );
auto mptr = Media::create( this, type, fileFs );
......
......@@ -71,12 +71,17 @@ int MetadataParser::toInt( VLC::Media& vlcMedia, libvlc_meta_t meta, const char*
parser::Task::Status MetadataParser::run( parser::Task& task )
{
auto& media = task.media;
const auto& tracks = task.vlcMedia.tracks();
// If we failed to extract any tracks, don't make any assumption and forward to the
// thumbnailer. Since it starts an actual playback, it will have more information.
// Since the metadata steps won't be marked, it will run again once the thumbnailer has completed.
if ( tracks.empty() == true )
return parser::Task::Status::Success;
bool isAudio = true;
{
auto t = m_ml->getConn()->newTransaction();
// Some media (ogg/ts, most likely) won't have visible tracks, but shouldn't be considered audio files.
for ( const auto& t : tracks )
{
auto codec = t.codec();
......
......@@ -50,6 +50,10 @@ parser::Task::Status VLCMetadataService::run( parser::Task& task )
auto fromType = file->mrl().find( "://" ) != std::string::npos ? VLC::Media::FromType::FromLocation :
VLC::Media::FromType::FromPath;
// Having a valid media means we're re-executing this parser after the thumbnailer,
// which isn't expected, as we always mark this task as completed.
assert( task.vlcMedia.isValid() == false );
task.vlcMedia = VLC::Media( m_instance, file->mrl(), fromType );
std::unique_lock<compat::Mutex> lock( m_mutex );
......
......@@ -73,14 +73,7 @@ parser::Task::Status VLCThumbnailer::run( parser::Task& task )
auto media = task.media;
auto file = task.file;
if ( media->type() == IMedia::Type::UnknownType )
{
// If we don't know the media type yet, it actually looks more like a bug
// since this should run after media type deduction, and not run in case
// that step fails.
return parser::Task::Status::Fatal;
}
else if ( media->type() != IMedia::Type::VideoType )
if ( media->type() == IMedia::Type::AudioType )
{
// There's no point in generating a thumbnail for a non-video media.
task.file->markStepCompleted( File::ParserStep::Thumbnailer );
......@@ -98,12 +91,20 @@ parser::Task::Status VLCThumbnailer::run( parser::Task& task )
setupVout( mp );
auto res = startPlayback( mp );
auto res = startPlayback( task, mp );
if ( res != parser::Task::Status::Success )
{
// If the media became an audio file, it's not an error
if ( task.media->type() == Media::Type::AudioType )
{
LOG_INFO( file->mrl(), " type has changed to Audio. Skipping thumbnail generation" );
return parser::Task::Status::Success;
}
// Otherwise, we failed to start the playback and this is an error indeed
LOG_WARN( "Failed to generate ", file->mrl(), " thumbnail: Can't start playback" );
return res;
}
// Seek ahead to have a significant preview
res = seekAhead( mp );
if ( res != parser::Task::Status::Success )
......@@ -114,35 +115,52 @@ parser::Task::Status VLCThumbnailer::run( parser::Task& task )
return takeThumbnail( media, file, mp );
}
parser::Task::Status VLCThumbnailer::startPlayback( VLC::MediaPlayer &mp )
parser::Task::Status VLCThumbnailer::startPlayback( parser::Task& task, VLC::MediaPlayer &mp )
{
// Use a copy of the event manager to automatically unregister all events as soon
// as we leave this method.
auto em = mp.eventManager();
bool hasVideoTrack = false;
bool failedToStart = false;
em.onESAdded([this, &hasVideoTrack]( libvlc_track_type_t type, int ) {
if ( type == libvlc_track_video )
{
m_cond.notify_all();
std::lock_guard<compat::Mutex> lock( m_mutex );
hasVideoTrack = true;
m_cond.notify_all();
}
});
em.onEncounteredError([this]() {
em.onEncounteredError([this, &failedToStart]() {
std::lock_guard<compat::Mutex> lock( m_mutex );
failedToStart = true;
m_cond.notify_all();
});
std::unique_lock<compat::Mutex> lock( m_mutex );
mp.play();
bool success = m_cond.wait_for( lock, std::chrono::seconds( 1 ), [&mp, &hasVideoTrack]() {
auto s = mp.state();
return s == libvlc_Error || s == libvlc_Ended || hasVideoTrack == true;
bool success = m_cond.wait_for( lock, std::chrono::seconds( 1 ), [&failedToStart, &hasVideoTrack]() {
return failedToStart == true || hasVideoTrack == true;
});
if ( success == false || hasVideoTrack == false )
// If a video track was added, we can continue right away.
if ( hasVideoTrack == true )
{
// In case of timeout or error, don't go any further
return parser::Task::Status::Error;
assert( success == true );
return parser::Task::Status::Success;
}
return parser::Task::Status::Success;
// In case the playback failed, we probably won't fetch anything interesting anyway.
if ( failedToStart == true )
return parser::Task::Status::Fatal;
// We are now in the case of a timeout: No failure, but no video track either.
// The file might be an audio file we haven't detected yet:
if ( task.media->type() == Media::Type::UnknownType )
{
task.media->setType( Media::Type::AudioType );
if ( task.media->save() == false )
return parser::Task::Status::Fatal;
// We still return an error since we don't want to attempt the thumbnail generation for a
// file without video tracks
}
return parser::Task::Status::Error;
}
parser::Task::Status VLCThumbnailer::seekAhead( VLC::MediaPlayer& mp )
......
......@@ -42,7 +42,7 @@ public:
virtual File::ParserStep step() const override;
private:
parser::Task::Status startPlayback( VLC::MediaPlayer& mp );
parser::Task::Status startPlayback( parser::Task& task, VLC::MediaPlayer& mp );
parser::Task::Status seekAhead( VLC::MediaPlayer &mp );
void setupVout( VLC::MediaPlayer &mp );
parser::Task::Status takeThumbnail( std::shared_ptr<Media> media, std::shared_ptr<File> file, VLC::MediaPlayer &mp );
......
......@@ -346,14 +346,17 @@ TEST_F( Medias, SortByAlpha )
{
auto m1 = ml->addFile( "media1.mp3" );
m1->setTitle( "Abcd" );
m1->setType( Media::Type::AudioType );
m1->save();
auto m2 = ml->addFile( "media2.mp3" );
m2->setTitle( "Zyxw" );
m2->setType( Media::Type::AudioType );
m2->save();
auto m3 = ml->addFile( "media3.mp3" );
m3->setTitle( "afterA-beforeZ" );
m3->setType( Media::Type::AudioType );
m3->save();
auto media = ml->audioFiles( SortingCriteria::Alpha, false );
......@@ -374,10 +377,14 @@ TEST_F( Medias, SortByLastModifDate )
auto file1 = std::make_shared<mock::NoopFile>( "media.mkv" );
file1->setLastModificationDate( 666 );
auto m1 = ml->addFile( *file1 );
m1->setType( Media::Type::VideoType );
m1->save();
auto file2 = std::make_shared<mock::NoopFile>( "media2.mkv" );
file2->setLastModificationDate( 111 );
auto m2 = ml->addFile( *file2 );
m2->setType( Media::Type::VideoType );
m2->save();
auto media = ml->videoFiles( SortingCriteria::LastModificationDate, false );
ASSERT_EQ( 2u, media.size() );
......@@ -395,10 +402,14 @@ TEST_F( Medias, SortByFileSize )
auto file1 = std::make_shared<mock::NoopFile>( "media.mkv" );
file1->setSize( 666 );
auto m1 = ml->addFile( *file1 );
m1->setType( Media::Type::VideoType );
m1->save();
auto file2 = std::make_shared<mock::NoopFile>( "media2.mkv" );
file2->setSize( 111 );
auto m2 = ml->addFile( *file2 );
m2->setType( Media::Type::VideoType );
m2->save();
auto media = ml->videoFiles( SortingCriteria::FileSize, false );
ASSERT_EQ( 2u, media.size() );
......@@ -414,7 +425,7 @@ TEST_F( Medias, SortByFileSize )
TEST_F( Medias, SetType )
{
auto m1 = ml->addFile( "media1.mp3" );
ASSERT_EQ( IMedia::Type::AudioType, m1->type() );
ASSERT_EQ( IMedia::Type::UnknownType, m1->type() );
m1->setType( IMedia::Type::VideoType );
m1->save();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment