From f1128968b3789307e3008438eaca13825443c215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= Date: Fri, 26 Oct 2018 17:31:55 +0200 Subject: [PATCH] medialibrary: Add thumbnailing support --- include/vlc_media_library.h | 21 +++++ modules/misc/Makefile.am | 1 + modules/misc/medialibrary/Thumbnailer.cpp | 109 ++++++++++++++++++++++ modules/misc/medialibrary/entities.cpp | 16 +++- modules/misc/medialibrary/medialib.cpp | 34 ++++++- modules/misc/medialibrary/medialibrary.h | 14 +++ 6 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 modules/misc/medialibrary/Thumbnailer.cpp diff --git a/include/vlc_media_library.h b/include/vlc_media_library.h index 06ad9ce4c8..30079b0844 100644 --- a/include/vlc_media_library.h +++ b/include/vlc_media_library.h @@ -182,6 +182,9 @@ typedef struct vlc_ml_media_t char* psz_title; char* psz_artwork_mrl; + /* True if a thumbnail is available, or if thumbnail generation was + * attempted but failed */ + bool b_artwork_generated; bool b_is_favorite; union @@ -431,6 +434,7 @@ enum vlc_ml_control VLC_ML_MEDIA_GET_MEDIA_PLAYBACK_PREF, /**< arg1: media id; arg2: vlc_ml_playback_pref; arg3: char**; */ VLC_ML_MEDIA_SET_MEDIA_PLAYBACK_PREF, /**< arg1: media id; arg2: vlc_ml_playback_pref; arg3: const char*; */ VLC_ML_MEDIA_SET_THUMBNAIL, /**< arg1: media id; arg2: const char*; */ + VLC_ML_MEDIA_GENERATE_THUMBNAIL, /**< arg1: media id; */ VLC_ML_MEDIA_ADD_EXTERNAL_MRL, /**< arg1: media id; arg2: const char*; arg3: type(vlc_ml_file_type_t) */ }; @@ -568,6 +572,13 @@ enum vlc_ml_event_type * increase once all discovery operations are completed. */ VLC_ML_EVENT_PARSING_PROGRESS_UPDATED, + /** + * Sent after a media thumbnail was generated, or if it's generation failed. + * The media is stored in vlc_ml_event_t::media_thumbnail_generated::p_media + * and the success state is stored in + * vlc_ml_event_t::media_thumbnail_generated::b_success + */ + VLC_ML_EVENT_MEDIA_THUMBNAIL_GENERATED, }; typedef struct vlc_ml_event_t @@ -632,6 +643,11 @@ typedef struct vlc_ml_event_t { bool b_idle; } background_idle_changed; + struct + { + const vlc_ml_media_t* p_media; + bool b_success; + } media_thumbnail_generated; }; } vlc_ml_event_t; @@ -815,6 +831,11 @@ static inline int vlc_ml_media_set_thumbnail( vlc_medialibrary_t* p_ml, int64_t return vlc_ml_control( p_ml, VLC_ML_MEDIA_SET_THUMBNAIL, i_media_id, psz_mrl ); } +static inline int vlc_ml_media_generate_thumbnail( vlc_medialibrary_t* p_ml, int64_t i_media_id ) +{ + return vlc_ml_control( p_ml, VLC_ML_MEDIA_GENERATE_THUMBNAIL, i_media_id ); +} + static inline int vlc_ml_media_add_external_mrl( vlc_medialibrary_t* p_ml, int64_t i_media_id, const char* psz_mrl, int i_type ) { diff --git a/modules/misc/Makefile.am b/modules/misc/Makefile.am index 89946bbe96..7848080109 100644 --- a/modules/misc/Makefile.am +++ b/modules/misc/Makefile.am @@ -105,6 +105,7 @@ libmedialibrary_plugin_la_SOURCES = \ misc/medialibrary/medialib.cpp \ misc/medialibrary/MetadataExtractor.cpp \ misc/medialibrary/entities.cpp \ + misc/medialibrary/Thumbnailer.cpp \ misc/medialibrary/medialibrary.h libmedialibrary_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(MEDIALIBRARY_CFLAGS) diff --git a/modules/misc/medialibrary/Thumbnailer.cpp b/modules/misc/medialibrary/Thumbnailer.cpp new file mode 100644 index 0000000000..604f9932eb --- /dev/null +++ b/modules/misc/medialibrary/Thumbnailer.cpp @@ -0,0 +1,109 @@ +/***************************************************************************** + * Thumbnailer.cpp: medialibrary thumbnailer implementation using libvlccore + ***************************************************************************** + * Copyright © 2018 VLC authors, VideoLAN and VideoLabs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "medialibrary.h" + +#include +#include +#include +#include +#include + +Thumbnailer::Thumbnailer( vlc_medialibrary_module_t* ml, std::string thumbnailsDir ) + : m_ml( ml ) + , m_thumbnailDir( std::move( thumbnailsDir ) ) + , m_thumbnailer( nullptr, &vlc_thumbnailer_Release ) +{ + m_thumbnailer.reset( vlc_thumbnailer_Create( VLC_OBJECT( ml ) ) ); + if ( unlikely( m_thumbnailer == nullptr ) ) + throw std::runtime_error( "Failed to instantiate a vlc_thumbnailer_t" ); +} + +struct ThumbnailerCtx +{ + ~ThumbnailerCtx() + { + if ( item != nullptr ) + input_item_Release( item ); + if ( thumbnail != nullptr ) + picture_Release( thumbnail ); + } + vlc::threads::condition_variable cond; + vlc::threads::mutex mutex; + input_item_t* item; + bool done; + picture_t* thumbnail; +}; + +static void onThumbnailComplete( void* data, picture_t* thumbnail ) +{ + ThumbnailerCtx* ctx = static_cast( data ); + + { + vlc::threads::mutex_locker lock( ctx->mutex ); + ctx->done = true; + ctx->thumbnail = thumbnail ? picture_Hold( thumbnail ) : nullptr; + } + ctx->cond.signal(); +} + +bool Thumbnailer::generate( medialibrary::MediaPtr media, const std::string& mrl ) +{ + ThumbnailerCtx ctx{}; + ctx.item = input_item_New( mrl.c_str(), media->title().c_str() ); + if ( unlikely( ctx.item == nullptr ) ) + return false; + + ctx.done = false; + { + vlc::threads::mutex_locker lock( ctx.mutex ); + vlc_thumbnailer_RequestByPos( m_thumbnailer.get(), .3f, + VLC_THUMBNAILER_SEEK_FAST, ctx.item, + VLC_TICK_FROM_SEC( 3 ), + &onThumbnailComplete, &ctx ); + + while ( ctx.done == false ) + ctx.cond.wait( ctx.mutex ); + } + if ( ctx.thumbnail == nullptr ) + return false; + + block_t* block; + if ( picture_Export( VLC_OBJECT( m_ml ), &block, nullptr, ctx.thumbnail, + VLC_CODEC_JPEG, 512, 320 ) != VLC_SUCCESS ) + return false; + auto blockPtr = vlc::wrap_cptr( block, &block_Release ); + + std::string outputPath = m_thumbnailDir + std::to_string( media->id() ) + ".jpg"; + auto f = vlc::wrap_cptr( vlc_fopen( outputPath.c_str(), "wb" ), &fclose ); + if ( f == nullptr ) + return false; + if ( fwrite( block->p_buffer, block->i_buffer, 1, f.get() ) != 1 ) + return false; + auto thumbnailMrl = vlc::wrap_cptr( vlc_path2uri( outputPath.c_str(), nullptr ) ); + if ( thumbnailMrl == nullptr ) + return false; + + return media->setThumbnail( thumbnailMrl.get() ); +} diff --git a/modules/misc/medialibrary/entities.cpp b/modules/misc/medialibrary/entities.cpp index 0579e38475..f6476c76c7 100644 --- a/modules/misc/medialibrary/entities.cpp +++ b/modules/misc/medialibrary/entities.cpp @@ -215,12 +215,22 @@ bool Convert( const medialibrary::IMedia* input, vlc_ml_media_t& output ) if ( input->isThumbnailGenerated() == true ) { - output.psz_artwork_mrl = strdup( input->thumbnail().c_str() ); - if ( unlikely( output.psz_artwork_mrl == nullptr ) ) - return false; + output.b_artwork_generated = true; + const auto& thumbnail = input->thumbnail(); + if ( thumbnail.empty() == true ) + output.psz_artwork_mrl = nullptr; + else + { + output.psz_artwork_mrl = strdup( thumbnail.c_str() ); + if ( unlikely( output.psz_artwork_mrl == nullptr ) ) + return false; + } } else + { output.psz_artwork_mrl = nullptr; + output.b_artwork_generated = false; + } return true; } diff --git a/modules/misc/medialibrary/medialib.cpp b/modules/misc/medialibrary/medialib.cpp index bef9bf92cd..85689e26d5 100644 --- a/modules/misc/medialibrary/medialib.cpp +++ b/modules/misc/medialibrary/medialib.cpp @@ -269,8 +269,20 @@ void MediaLibrary::onBackgroundTasksIdleChanged( bool idle ) m_vlc_ml->cbs->pf_send_event( m_vlc_ml, &ev ); } -void MediaLibrary::onMediaThumbnailReady( medialibrary::MediaPtr, bool ) +void MediaLibrary::onMediaThumbnailReady( medialibrary::MediaPtr media, bool success ) { + vlc_ml_event_t ev; + ev.i_type = VLC_ML_EVENT_MEDIA_THUMBNAIL_GENERATED; + ev.media_thumbnail_generated.b_success = success; + auto mPtr = vlc::wrap_cptr( + static_cast( malloc( sizeof( vlc_ml_media_t ) ) ), + vlc_ml_media_release ); + if ( unlikely( mPtr == nullptr ) ) + return; + ev.media_thumbnail_generated.p_media = mPtr.get(); + if ( Convert( media.get(), *mPtr ) == false ) + return; + m_vlc_ml->cbs->pf_send_event( m_vlc_ml, &ev ); } MediaLibrary::MediaLibrary( vlc_medialibrary_module_t* ml ) @@ -291,8 +303,9 @@ bool MediaLibrary::Start() auto userDir = vlc::wrap_cptr( config_GetUserDir( VLC_USERDATA_DIR ) ); std::string mlDir = std::string{ userDir.get() } + "/ml/"; + auto thumbnailsDir = mlDir + "thumbnails/"; - auto initStatus = ml->initialize( mlDir + "ml.db", mlDir + "thumbnails/", this ); + auto initStatus = ml->initialize( mlDir + "ml.db", thumbnailsDir, this ); switch ( initStatus ) { case medialibrary::InitializeResult::AlreadyInitialized: @@ -310,6 +323,17 @@ bool MediaLibrary::Start() } ml->addParserService( std::make_shared( VLC_OBJECT( m_vlc_ml ) ) ); + try + { + ml->addThumbnailer( std::make_shared( + m_vlc_ml, std::move( thumbnailsDir ) ) ); + } + catch ( const std::runtime_error& ex ) + { + msg_Err( m_vlc_ml, "Failed to provide a thumbnailer module to the " + "medialib: %s", ex.what() ); + return false; + } if ( ml->start() == false ) { msg_Err( m_vlc_ml, "Failed to start the MediaLibrary" ); @@ -426,6 +450,7 @@ int MediaLibrary::Control( int query, va_list args ) case VLC_ML_MEDIA_GET_MEDIA_PLAYBACK_PREF: case VLC_ML_MEDIA_SET_MEDIA_PLAYBACK_PREF: case VLC_ML_MEDIA_SET_THUMBNAIL: + case VLC_ML_MEDIA_GENERATE_THUMBNAIL: case VLC_ML_MEDIA_ADD_EXTERNAL_MRL: return controlMedia( query, args ); default: @@ -873,6 +898,11 @@ int MediaLibrary::controlMedia( int query, va_list args ) m->setThumbnail( mrl ); return VLC_SUCCESS; } + case VLC_ML_MEDIA_GENERATE_THUMBNAIL: + { + auto res = m_ml->requestThumbnail( m ); + return res == true ? VLC_SUCCESS : VLC_EGENERIC; + } case VLC_ML_MEDIA_ADD_EXTERNAL_MRL: { auto mrl = va_arg( args, const char* ); diff --git a/modules/misc/medialibrary/medialibrary.h b/modules/misc/medialibrary/medialibrary.h index 1949682fda..47aac1f1f6 100644 --- a/modules/misc/medialibrary/medialibrary.h +++ b/modules/misc/medialibrary/medialibrary.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,7 @@ struct vlc_event_t; struct vlc_object_t; +struct vlc_thumbnailer_t; class Logger; @@ -92,6 +94,18 @@ private: vlc_object_t* m_obj; }; +class Thumbnailer : public medialibrary::IThumbnailer +{ +public: + Thumbnailer( vlc_medialibrary_module_t* ml, std::string thumbnailsDir); + virtual bool generate( medialibrary::MediaPtr media, const std::string& mrl ) override; + +private: + vlc_medialibrary_module_t* m_ml; + std::string m_thumbnailDir; + std::unique_ptr m_thumbnailer; +}; + class MediaLibrary : public medialibrary::IMediaLibraryCb { public: