diff --git a/configure.ac b/configure.ac index ba76fc124f6cf1cb73a33ad295c26f09616af872..243304fe780b41bb873aa9c0fbe4633e565478ac 100644 --- a/configure.ac +++ b/configure.ac @@ -4143,6 +4143,7 @@ AS_IF([test "$enable_libplacebo" != "no"], [ ]) AM_CONDITIONAL(HAVE_LIBPLACEBO, [test "$enable_libplacebo" != "no"]) +PKG_ENABLE_MODULES_VLC([MEDIALIBRARY], [medialibrary], [medialibrary], (medialibrary support), [auto]) dnl dnl Endianness check diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST index cda3b8b3ae2ed70ce59ded9df781c228e374fa5e..ee364c299e006f44036316713624ae496d888399 100644 --- a/modules/MODULES_LIST +++ b/modules/MODULES_LIST @@ -227,6 +227,7 @@ $Id$ * marq: Overlays a marquee on the video * mediacodec: Android Jelly Bean MediaCodec decoder module * mediadirs: Picture/Music/Video user directories as service discoveries + * medialibrary: Media collection management * memory_keystore: store secrets in memory * mft: Media Foundation Transform audio/video decoder * microdns: mDNS services discovery diff --git a/modules/misc/Makefile.am b/modules/misc/Makefile.am index f2d13ed2782369135b73ff031c38661ac8090db7..89946bbe96cb51554609439355f605b7af95703c 100644 --- a/modules/misc/Makefile.am +++ b/modules/misc/Makefile.am @@ -100,3 +100,16 @@ libxml_plugin_la_LIBADD = $(LIBXML2_LIBS) libxml_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(miscdir)' $(LDFLAGS_xml) EXTRA_LTLIBRARIES += libxml_plugin.la misc_LTLIBRARIES += $(LTLIBxml) + +libmedialibrary_plugin_la_SOURCES = \ + misc/medialibrary/medialib.cpp \ + misc/medialibrary/MetadataExtractor.cpp \ + misc/medialibrary/entities.cpp \ + misc/medialibrary/medialibrary.h + +libmedialibrary_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(MEDIALIBRARY_CFLAGS) +libmedialibrary_plugin_la_LIBADD = $(MEDIALIBRARY_LIBS) +libmedialibrary_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(miscdir)' +EXTRA_LTLIBRARIES += libmedialibrary_plugin.la +misc_LTLIBRARIES += $(LTLIBmedialibrary) + diff --git a/modules/misc/medialibrary/MetadataExtractor.cpp b/modules/misc/medialibrary/MetadataExtractor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..10b209a9d6535eb32e21606e37d7a87445fe2bbf --- /dev/null +++ b/modules/misc/medialibrary/MetadataExtractor.cpp @@ -0,0 +1,226 @@ +/***************************************************************************** + * MetadataExtractor.cpp: IParserService implementation using libvlccore + ***************************************************************************** + * Copyright © 2008-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" + +MetadataExtractor::MetadataExtractor( vlc_object_t* parent ) + : m_obj( parent ) +{ +} + +void MetadataExtractor::onInputEvent( const vlc_input_event* ev, + ParseContext& ctx ) +{ + if ( ev->type != INPUT_EVENT_DEAD && ev->type != INPUT_EVENT_STATE ) + return; + + if ( ev->type == INPUT_EVENT_STATE ) + { + vlc_mutex_locker lock( &ctx.m_mutex ); + ctx.state = ev->state; + return; + } + + { + vlc_mutex_locker lock( &ctx.m_mutex ); + // We need to probe the item now, but not from the input thread + ctx.needsProbing = true; + } + vlc_cond_signal( &ctx.m_cond ); +} + +void MetadataExtractor::populateItem( medialibrary::parser::IItem& item, input_item_t* inputItem ) +{ + vlc_mutex_locker lock( &inputItem->lock ); + + const auto emptyStringWrapper = []( const char* psz ) { + return psz != nullptr ? std::string{ psz } : std::string{}; + }; + + if ( inputItem->p_meta != nullptr ) + { + item.setMeta( medialibrary::parser::IItem::Metadata::Title, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_Title ) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::ArtworkUrl, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_ArtworkURL) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::ShowName, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_ShowName ) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::Episode, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_Episode) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::Album, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_Album) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::Genre, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_Genre ) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::Date, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_Date ) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::AlbumArtist, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_AlbumArtist ) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::Artist, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_Artist ) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::TrackNumber, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_TrackNumber ) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::DiscNumber, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_DiscNumber ) ) ); + item.setMeta( medialibrary::parser::IItem::Metadata::DiscTotal, + emptyStringWrapper( vlc_meta_Get( inputItem->p_meta, vlc_meta_DiscTotal ) ) ); + } + + item.setDuration( inputItem->i_duration ); + + for ( auto i = 0; i < inputItem->i_es; ++i ) + { + medialibrary::parser::IItem::Track t; + const es_format_t *p_es = inputItem->es[i]; + + switch ( p_es->i_cat ) + { + case AUDIO_ES: + t.type = medialibrary::parser::IItem::Track::Type::Audio; + t.a.nbChannels = p_es->audio.i_channels; + t.a.rate = p_es->audio.i_rate; + break; + case VIDEO_ES: + t.type = medialibrary::parser::IItem::Track::Type::Video; + t.v.fpsNum = p_es->video.i_frame_rate; + t.v.fpsDen = p_es->video.i_frame_rate_base; + t.v.width = p_es->video.i_width; + t.v.height = p_es->video.i_height; + t.v.sarNum = p_es->video.i_sar_num; + t.v.sarDen = p_es->video.i_sar_den; + break; + default: + continue; + } + + char fourcc[4]; + vlc_fourcc_to_char( p_es->i_codec, fourcc ); + t.codec = std::string{ fourcc, 4 }; + + t.bitrate = p_es->i_bitrate; + t.language = emptyStringWrapper( p_es->psz_language ); + t.description = emptyStringWrapper( p_es->psz_description ); + + item.addTrack( std::move( t ) ); + } +} + +void MetadataExtractor::onInputEvent( input_thread_t*, void *data, + const struct vlc_input_event *event ) +{ + auto* ctx = static_cast<ParseContext*>( data ); + ctx->mde->onInputEvent( event, *ctx ); +} + +void MetadataExtractor::onSubItemAdded( const vlc_event_t* event, ParseContext& ctx ) +{ + auto root = event->u.input_item_subitem_tree_added.p_root; + for ( auto i = 0; i < root->i_children; ++i ) + { + auto it = root->pp_children[i]->p_item; + auto& subItem = ctx.item.createSubItem( it->psz_uri, i ); + populateItem( subItem, it ); + } +} + +void MetadataExtractor::onSubItemAdded( const vlc_event_t* event, void* data ) +{ + auto* ctx = static_cast<ParseContext*>( data ); + ctx->mde->onSubItemAdded( event, *ctx ); +} + +medialibrary::parser::Status MetadataExtractor::run( medialibrary::parser::IItem& item ) +{ + const std::unique_ptr<ParseContext> ctx( new ParseContext{ this, item } ); + ctx->inputItem = { + input_item_New( item.mrl().c_str(), NULL ), + &input_item_Release + }; + if ( ctx->inputItem == nullptr ) + return medialibrary::parser::Status::Fatal; + + ctx->inputItem->i_preparse_depth = 1; + ctx->input = { + input_CreatePreparser( m_obj, &MetadataExtractor::onInputEvent, + ctx.get(), ctx->inputItem.get() ), + &input_Close + }; + if ( ctx->input == nullptr ) + return medialibrary::parser::Status::Fatal; + + vlc_event_attach( &ctx->inputItem->event_manager, vlc_InputItemSubItemTreeAdded, + &MetadataExtractor::onSubItemAdded, ctx.get() ); + + input_Start( ctx->input.get() ); + + { + vlc_mutex_locker lock( &ctx->m_mutex ); + while ( ctx->needsProbing == false ) + { + vlc_cond_wait( &ctx->m_cond, &ctx->m_mutex ); + if ( ctx->needsProbing == true ) + { + if ( ctx->state == END_S || ctx->state == ERROR_S ) + break; + // Reset the probing flag for next event + ctx->needsProbing = false; + } + } + } + + if ( ctx->state == ERROR_S ) + return medialibrary::parser::Status::Fatal; + assert( ctx->state == END_S ); + + populateItem( item, ctx->inputItem.get() ); + + return medialibrary::parser::Status::Success; +} + +const char* MetadataExtractor::name() const +{ + return "libvlccore extraction"; +} + +uint8_t MetadataExtractor::nbThreads() const +{ + return 1; +} + +medialibrary::parser::Step MetadataExtractor::targetedStep() const +{ + return medialibrary::parser::Step::MetadataExtraction; +} + +bool MetadataExtractor::initialize( medialibrary::IMediaLibrary* ) +{ + return true; +} + +void MetadataExtractor::onFlushing() +{ +} + +void MetadataExtractor::onRestarted() +{ +} diff --git a/modules/misc/medialibrary/entities.cpp b/modules/misc/medialibrary/entities.cpp new file mode 100644 index 0000000000000000000000000000000000000000..98a80ae14187cb1c46b9bfd9af85999221228154 --- /dev/null +++ b/modules/misc/medialibrary/entities.cpp @@ -0,0 +1,477 @@ +/***************************************************************************** + * entities.cpp: medialibrary C++ -> C entities conversion & management + ***************************************************************************** + * Copyright © 2008-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 <medialibrary/IMedia.h> +#include <medialibrary/IFile.h> +#include <medialibrary/IMovie.h> +#include <medialibrary/IShow.h> +#include <medialibrary/IShowEpisode.h> +#include <medialibrary/IArtist.h> +#include <medialibrary/IAlbum.h> +#include <medialibrary/IAlbumTrack.h> +#include <medialibrary/IGenre.h> +#include <medialibrary/ILabel.h> +#include <medialibrary/IPlaylist.h> +#include <medialibrary/IAudioTrack.h> +#include <medialibrary/IVideoTrack.h> + +bool Convert( const medialibrary::IAlbumTrack* input, vlc_ml_album_track_t& output ) +{ + output.i_artist_id = input->artistId(); + output.i_album_id = input->albumId(); + output.i_disc_nb = input->discNumber(); + output.i_genre_id = input->genreId(); + output.i_track_nb = input->trackNumber(); + return true; +} + +bool Convert( const medialibrary::IShowEpisode* input, vlc_ml_show_episode_t& output ) +{ + output.i_episode_nb = input->episodeNumber(); + output.i_season_number = input->seasonNumber(); + if ( input->shortSummary().empty() == false ) + { + output.psz_summary = strdup( input->shortSummary().c_str() ); + if ( unlikely( output.psz_summary == nullptr ) ) + return false; + } + else + output.psz_summary = nullptr; + if ( input->tvdbId().empty() == false ) + { + output.psz_tvdb_id = strdup( input->tvdbId().c_str() ); + if ( unlikely( output.psz_tvdb_id == nullptr ) ) + return false; + } + else + output.psz_tvdb_id = nullptr; + return true; +} + +bool Convert( const medialibrary::IMovie* input, vlc_ml_movie_t& output ) +{ + if ( input->imdbId().empty() == false ) + { + output.psz_imdb_id = strdup( input->imdbId().c_str() ); + if ( unlikely( output.psz_imdb_id == nullptr ) ) + return false; + } + else + output.psz_imdb_id = nullptr; + if ( input->shortSummary().empty() == false ) + { + output.psz_summary = strdup( input->shortSummary().c_str() ); + if ( unlikely( output.psz_summary == nullptr ) ) + return false; + } + else + output.psz_summary = nullptr; + return true; +} + +static bool convertTracksCommon( vlc_ml_media_track_t* output, const std::string& codec, + const std::string& language, const std::string& desc ) +{ + if ( codec.empty() == false ) + { + output->psz_codec = strdup( codec.c_str() ); + if ( unlikely( output->psz_codec == nullptr ) ) + return false; + } + else + output->psz_codec = nullptr; + + if ( language.empty() == false ) + { + output->psz_language = strdup( language.c_str() ); + if ( unlikely( output->psz_language == nullptr ) ) + return false; + } + else + output->psz_language = nullptr; + + if ( desc.empty() == false ) + { + output->psz_description = strdup( desc.c_str() ); + if ( unlikely( output->psz_description == nullptr ) ) + return false; + } + else + output->psz_description = nullptr; + return true; +} + +static bool convertTracks( const medialibrary::IMedia* inputMedia, vlc_ml_media_t& outputMedia ) +{ + auto videoTracks = inputMedia->videoTracks()->all(); + auto audioTracks = inputMedia->audioTracks()->all(); + auto nbItems = videoTracks.size() + audioTracks.size(); + outputMedia.p_tracks = static_cast<vlc_ml_media_track_list_t*>( + calloc( 1, sizeof( *outputMedia.p_tracks ) + + nbItems * sizeof( *outputMedia.p_tracks->p_items ) ) ); + if ( unlikely( outputMedia.p_tracks == nullptr ) ) + return false; + outputMedia.p_tracks->i_nb_items = 0; + + vlc_ml_media_track_t* items = outputMedia.p_tracks->p_items; + for ( const auto& t : videoTracks ) + { + vlc_ml_media_track_t* output = &items[outputMedia.p_tracks->i_nb_items++]; + + if ( convertTracksCommon( output, t->codec(), t->language(), t->description() ) == false ) + return false; + output->i_type = VLC_ML_TRACK_TYPE_VIDEO; + output->i_bitrate = t->bitrate(); + output->v.i_fpsNum = t->fpsNum(); + output->v.i_fpsDen = t->fpsDen(); + output->v.i_sarNum = t->sarNum(); + output->v.i_sarDen = t->sarDen(); + } + for ( const auto& t : audioTracks ) + { + vlc_ml_media_track_t* output = &items[outputMedia.p_tracks->i_nb_items++]; + + if ( convertTracksCommon( output, t->codec(), t->language(), t->description() ) == false ) + return false; + output->i_type = VLC_ML_TRACK_TYPE_AUDIO; + output->i_bitrate = t->bitrate(); + output->a.i_nbChannels = t->nbChannels(); + output->a.i_sampleRate = t->sampleRate(); + } + return true; +} + +bool Convert( const medialibrary::IMedia* input, vlc_ml_media_t& output ) +{ + output.i_id = input->id(); + + switch ( input->type() ) + { + case medialibrary::IMedia::Type::Audio: + output.i_type = VLC_ML_MEDIA_TYPE_AUDIO; + switch( input->subType() ) + { + case medialibrary::IMedia::SubType::AlbumTrack: + { + output.i_subtype = VLC_ML_MEDIA_SUBTYPE_ALBUMTRACK; + auto albumTrack = input->albumTrack(); + if ( albumTrack == nullptr ) + return false; + if ( Convert( albumTrack.get(), output.album_track ) == false ) + return false; + break; + } + default: + vlc_assert_unreachable(); + } + break; + case medialibrary::IMedia::Type::Video: + { + output.i_type = VLC_ML_MEDIA_TYPE_VIDEO; + switch( input->subType() ) + { + case medialibrary::IMedia::SubType::Movie: + { + output.i_subtype = VLC_ML_MEDIA_SUBTYPE_MOVIE; + auto movie = input->movie(); + if ( movie == nullptr ) + return false; + if ( Convert( movie.get(), output.movie ) == false ) + return false; + break; + } + case medialibrary::IMedia::SubType::ShowEpisode: + { + output.i_subtype = VLC_ML_MEDIA_SUBTYPE_SHOW_EPISODE; + auto episode = input->showEpisode(); + if ( episode == nullptr ) + return false; + if ( Convert( episode.get(), output.show_episode ) == false ) + return false; + break; + } + case medialibrary::IMedia::SubType::Unknown: + output.i_subtype = VLC_ML_MEDIA_SUBTYPE_UNKNOWN; + break; + case medialibrary::IMedia::SubType::AlbumTrack: + vlc_assert_unreachable(); + } + break; + } + case medialibrary::IMedia::Type::External: + output.i_type = VLC_ML_MEDIA_TYPE_EXTERNAL; + break; + case medialibrary::IMedia::Type::Stream: + output.i_type = VLC_ML_MEDIA_TYPE_STREAM; + break; + case medialibrary::IMedia::Type::Unknown: + vlc_assert_unreachable(); + } + output.i_year = input->releaseDate(); + output.i_duration = input->duration(); + output.b_is_favorite = input->isFavorite(); + output.i_playcount = input->playCount(); + output.i_last_played_date = input->lastPlayedDate(); + + output.psz_title = strdup( input->title().c_str() ); + if ( unlikely( output.psz_title == nullptr ) ) + return false; + + auto files = input->files(); + output.p_files = ml_convert_list<vlc_ml_file_list_t>( files ); + if ( output.p_files == nullptr ) + return false; + + if ( convertTracks( input, output ) == false ) + return false; + + if ( input->isThumbnailGenerated() == true ) + { + output.psz_artwork_mrl = strdup( input->thumbnail().c_str() ); + if ( unlikely( output.psz_artwork_mrl == nullptr ) ) + return false; + } + else + output.psz_artwork_mrl = nullptr; + + return true; +} + +bool Convert( const medialibrary::IFile* input, vlc_ml_file_t& output ) +{ + switch ( input->type() ) + { + case medialibrary::IFile::Type::Main: + output.i_type = VLC_ML_FILE_TYPE_MAIN; + break; + case medialibrary::IFile::Type::Part: + output.i_type = VLC_ML_FILE_TYPE_PART; + break; + case medialibrary::IFile::Type::Soundtrack: + output.i_type = VLC_ML_FILE_TYPE_SOUNDTRACK; + break; + case medialibrary::IFile::Type::Subtitles: + output.i_type = VLC_ML_FILE_TYPE_SUBTITLE; + break; + case medialibrary::IFile::Type::Playlist: + output.i_type = VLC_ML_FILE_TYPE_PLAYLIST; + break; + default: + vlc_assert_unreachable(); + } + output.psz_mrl = strdup( input->mrl().c_str() ); + if ( unlikely( output.psz_mrl == nullptr ) ) + return false; + output.b_external = input->isExternal(); + return true; +} + +bool Convert( const medialibrary::IAlbum* input, vlc_ml_album_t& output ) +{ + output.i_id = input->id(); + output.i_nb_tracks = input->nbTracks(); + output.i_duration = input->duration(); + output.i_year = input->releaseYear(); + if ( input->title().empty() == false ) + { + output.psz_title = strdup( input->title().c_str() ); + if ( unlikely( output.psz_title == nullptr ) ) + return false; + } + else + output.psz_title = nullptr; + if ( input->shortSummary().empty() == false ) + { + output.psz_summary = strdup( input->shortSummary().c_str() ); + if ( unlikely( output.psz_summary == nullptr ) ) + return false; + } + else + output.psz_summary = nullptr; + if ( input->artworkMrl().empty() == false ) + { + output.psz_artwork_mrl = strdup( input->artworkMrl().c_str() ); + if ( unlikely( output.psz_artwork_mrl == nullptr ) ) + return false; + } + else + output.psz_artwork_mrl = nullptr; + auto artist = input->albumArtist(); + if ( artist != nullptr ) + { + output.i_artist_id = artist->id(); + switch ( artist->id() ) + { + case medialibrary::UnknownArtistID: + output.psz_artist = strdup( _( "Unknown Artist" ) ); + break; + case medialibrary::VariousArtistID: + output.psz_artist = strdup( _( "Various Artist" ) ); + break; + default: + output.psz_artist = strdup( artist->name().c_str() ); + break; + } + if ( unlikely( output.psz_artist == nullptr ) ) + return false; + } + return true; +} + +bool Convert( const medialibrary::IArtist* input, vlc_ml_artist_t& output ) +{ + output.i_id = input->id(); + output.i_nb_album = input->nbAlbums(); + output.i_nb_tracks = input->nbTracks(); + switch ( input->id() ) + { + case medialibrary::UnknownArtistID: + output.psz_name = strdup( _( "Unknown Artist" ) ); + break; + case medialibrary::VariousArtistID: + output.psz_name = strdup( _( "Various Artist" ) ); + break; + default: + output.psz_name = strdup( input->name().c_str() ); + break; + } + if ( unlikely( output.psz_name == nullptr ) ) + return false; + if ( input->shortBio().empty() == false ) + { + output.psz_shortbio = strdup( input->shortBio().c_str() ); + if ( unlikely( output.psz_shortbio == nullptr ) ) + return false; + } + else + output.psz_shortbio = nullptr; + if ( input->artworkMrl().empty() == false ) + { + output.psz_artwork_mrl = strdup( input->artworkMrl().c_str() ); + if ( unlikely( output.psz_artwork_mrl == nullptr ) ) + return false; + } + else + output.psz_artwork_mrl = nullptr; + if ( input->musicBrainzId().empty() == false ) + { + output.psz_mb_id = strdup( input->musicBrainzId().c_str() ); + if ( unlikely( output.psz_mb_id == nullptr ) ) + return false; + } + else + output.psz_mb_id = nullptr; + return true; +} + +void Release( vlc_ml_genre_t& genre ) +{ + free( genre.psz_name ); +} + +bool Convert( const medialibrary::IGenre* input, vlc_ml_genre_t& output ) +{ + output.i_id = input->id(); + output.i_nb_tracks = input->nbTracks(); + assert( input->name().empty() == false ); + output.psz_name = strdup( input->name().c_str() ); + if ( unlikely( output.psz_name == nullptr ) ) + return false; + return true; +} + +bool Convert( const medialibrary::IShow* input, vlc_ml_show_t& output ) +{ + output.i_id = input->id(); + output.i_release_year = input->releaseDate(); + output.i_nb_episodes = input->nbEpisodes(); + output.i_nb_seasons = input->nbSeasons(); + if ( input->title().empty() == false ) + { + output.psz_name = strdup( input->title().c_str() ); + if ( output.psz_name == nullptr ) + return false; + } + else + output.psz_name = nullptr; + if ( input->artworkMrl().empty() == false ) + { + output.psz_artwork_mrl = strdup( input->artworkMrl().c_str() ); + if ( unlikely( output.psz_artwork_mrl == nullptr ) ) + return false; + } + else + output.psz_artwork_mrl = nullptr; + if ( input->tvdbId().empty() == false ) + { + output.psz_tvdb_id = strdup( input->tvdbId().c_str() ); + if ( unlikely( output.psz_tvdb_id == nullptr ) ) + return false; + } + else + output.psz_tvdb_id = nullptr; + if ( input->shortSummary().empty() == false ) + { + output.psz_summary = strdup( input->shortSummary().c_str() ); + if ( unlikely( output.psz_summary == nullptr ) ) + return false; + } + else + output.psz_summary = nullptr; + return true; +} + +bool Convert( const medialibrary::ILabel* input, vlc_ml_label_t& output ) +{ + assert( input->name().empty() == false ); + output.psz_name = strdup( input->name().c_str() ); + if ( unlikely( output.psz_name == nullptr ) ) + return false; + return true; +} + +bool Convert( const medialibrary::IPlaylist* input, vlc_ml_playlist_t& output ) +{ + output.i_id = input->id(); + if ( input->name().empty() == false ) + { + output.psz_name = strdup( input->name().c_str() ); + if ( unlikely( output.psz_name == nullptr ) ) + return false; + } + else + output.psz_name = nullptr; + if ( input->artworkMrl().empty() != false ) + { + output.psz_artwork_mrl = strdup( input->artworkMrl().c_str() ); + if ( unlikely( output.psz_artwork_mrl == nullptr ) ) + return false; + } + else + output.psz_artwork_mrl = nullptr; + output.i_creation_date = input->creationDate(); + return true; +} diff --git a/modules/misc/medialibrary/medialib.cpp b/modules/misc/medialibrary/medialib.cpp new file mode 100644 index 0000000000000000000000000000000000000000..20607e28ce67c4a90be391a68ffb0660868b8bc0 --- /dev/null +++ b/modules/misc/medialibrary/medialib.cpp @@ -0,0 +1,1195 @@ +/***************************************************************************** + * medialib.cpp: medialibrary module + ***************************************************************************** + * Copyright © 2008-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 <vlc_common.h> +#include <vlc_plugin.h> +#include <vlc_media_library.h> +#include "medialibrary.h" + +#include <medialibrary/IFolder.h> +#include <medialibrary/IMedia.h> +#include <medialibrary/IAlbumTrack.h> +#include <medialibrary/IAlbum.h> +#include <medialibrary/IArtist.h> +#include <medialibrary/IGenre.h> +#include <medialibrary/IMetadata.h> +#include <medialibrary/IShow.h> +#include <medialibrary/IPlaylist.h> + +#include <sstream> + +class Logger : public medialibrary::ILogger +{ +public: + Logger( vlc_object_t* obj ) : m_obj( obj ) {} + +private: + virtual void Error( const std::string& msg ) override + { + msg_Err( m_obj, "%s", msg.c_str() ); + } + virtual void Warning( const std::string& msg ) override + { + msg_Warn( m_obj, "%s", msg.c_str() ); + } + virtual void Info( const std::string& msg ) override + { + msg_Dbg( m_obj, "%s", msg.c_str() ); + } + virtual void Debug( const std::string& msg ) override + { + msg_Dbg( m_obj, "%s", msg.c_str() ); + } + +private: + vlc_object_t* m_obj; +}; + +void MediaLibrary::onMediaAdded( std::vector<medialibrary::MediaPtr> ) +{ +} + +void MediaLibrary::onMediaUpdated( std::vector<medialibrary::MediaPtr> ) +{ +} + +void MediaLibrary::onMediaDeleted( std::vector<int64_t> ) +{ +} + +void MediaLibrary::onArtistsAdded( std::vector<medialibrary::ArtistPtr> ) +{ +} + +void MediaLibrary::onArtistsModified( std::vector<medialibrary::ArtistPtr> ) +{ +} + +void MediaLibrary::onArtistsDeleted( std::vector<int64_t> ) +{ +} + +void MediaLibrary::onAlbumsAdded( std::vector<medialibrary::AlbumPtr> ) +{ +} + +void MediaLibrary::onAlbumsModified( std::vector<medialibrary::AlbumPtr> ) +{ +} + +void MediaLibrary::onAlbumsDeleted( std::vector<int64_t> ) +{ +} + +void MediaLibrary::onTracksAdded( std::vector<medialibrary::AlbumTrackPtr> ) +{ +} + +void MediaLibrary::onTracksDeleted( std::vector<int64_t> ) +{ +} + +void MediaLibrary::onPlaylistsAdded( std::vector<medialibrary::PlaylistPtr> ) +{ +} + +void MediaLibrary::onPlaylistsModified( std::vector<medialibrary::PlaylistPtr> ) +{ +} + +void MediaLibrary::onPlaylistsDeleted( std::vector<int64_t> ) +{ +} + +void MediaLibrary::onDiscoveryStarted( const std::string& ) +{ +} + +void MediaLibrary::onDiscoveryProgress( const std::string& ) +{ +} + +void MediaLibrary::onDiscoveryCompleted( const std::string& ) +{ +} + +void MediaLibrary::onReloadStarted( const std::string& ) +{ +} + +void MediaLibrary::onReloadCompleted( const std::string& ) +{ +} + +void MediaLibrary::onEntryPointRemoved( const std::string&, bool ) +{ +} + +void MediaLibrary::onEntryPointBanned( const std::string&, bool ) +{ +} + +void MediaLibrary::onEntryPointUnbanned( const std::string&, bool ) +{ +} + +void MediaLibrary::onParsingStatsUpdated( uint32_t ) +{ +} + +void MediaLibrary::onBackgroundTasksIdleChanged( bool ) +{ +} + +void MediaLibrary::onMediaThumbnailReady( medialibrary::MediaPtr, bool ) +{ +} + +MediaLibrary::MediaLibrary( vlc_object_t* obj ) + : m_obj( obj ) +{ +} + +bool MediaLibrary::Start() +{ + if ( m_ml != nullptr ) + return true; + + std::unique_ptr<medialibrary::IMediaLibrary> ml( NewMediaLibrary() ); + + m_logger.reset( new Logger( m_obj ) ); + ml->setVerbosity( medialibrary::LogLevel::Info ); + ml->setLogger( m_logger.get() ); + + auto userDir = vlc::wrap_cptr( config_GetUserDir( VLC_USERDATA_DIR ) ); + std::string mlDir = std::string{ userDir.get() } + "/ml/"; + + auto initStatus = ml->initialize( mlDir + "ml.db", mlDir + "thumbnails/", this ); + switch ( initStatus ) + { + case medialibrary::InitializeResult::AlreadyInitialized: + msg_Info( m_obj, "MediaLibrary was already initialized" ); + return true; + case medialibrary::InitializeResult::Failed: + msg_Err( m_obj, "Medialibrary failed to initialize" ); + return false; + case medialibrary::InitializeResult::DbReset: + msg_Info( m_obj, "Database was reset" ); + break; + case medialibrary::InitializeResult::Success: + msg_Dbg( m_obj, "MediaLibrary successfully initialized" ); + break; + } + + ml->addParserService( std::make_shared<MetadataExtractor>( m_obj ) ); + auto res = ml->start(); + if ( res == false ) + { + msg_Err( m_obj, "Failed to start the MediaLibrary" ); + return false; + } + + // Reload entry points we already know about, and then add potential new ones. + // Doing it the other way around would cause the initial scan to be performed + // twice, as we start discovering the new folders, then reload them. + ml->reload(); + + auto folders = vlc::wrap_cptr( var_InheritString( m_obj, "ml-folders" ) ); + if ( folders != nullptr && strlen( folders.get() ) > 0 ) + { + std::stringstream ss( folders.get() ); + std::string folder; + while ( std::getline( ss, folder, ';' ) ) + ml->discover( folder ); + } + else + { + auto videoFolder = vlc::wrap_cptr( config_GetUserDir( VLC_VIDEOS_DIR ) ); + std::string varValue; + if ( videoFolder != nullptr ) + { + auto mrl = std::string{ "file://" } + videoFolder.get(); + ml->discover( mrl ); + varValue = mrl; + } + auto musicFolder = vlc::wrap_cptr( config_GetUserDir( VLC_MUSIC_DIR ) ); + if ( musicFolder != nullptr ) + { + auto mrl = std::string{ "file://" } + musicFolder.get(); + ml->discover( mrl ); + if ( varValue.empty() == false ) + varValue += ";"; + varValue += mrl; + } + if ( varValue.empty() == false ) + config_PutPsz( "ml-folders", varValue.c_str() ); + } + m_ml = std::move( ml ); + return true; +} + +int MediaLibrary::Control( int query, va_list args ) +{ + if ( Start() == false ) + return VLC_EGENERIC; + + switch ( query ) + { + case VLC_ML_ADD_FOLDER: + case VLC_ML_REMOVE_FOLDER: + case VLC_ML_BAN_FOLDER: + case VLC_ML_UNBAN_FOLDER: + { + const char* mrl = va_arg( args, const char* ); + switch( query ) + { + case VLC_ML_ADD_FOLDER: + m_ml->discover( mrl ); + break; + case VLC_ML_REMOVE_FOLDER: + m_ml->removeEntryPoint( mrl ); + break; + case VLC_ML_BAN_FOLDER: + m_ml->banFolder( mrl ); + break; + case VLC_ML_UNBAN_FOLDER: + m_ml->unbanFolder( mrl ); + break; + } + break; + } + case VLC_ML_LIST_FOLDERS: + { + auto entryPoints = m_ml->entryPoints()->all(); + auto nbItem = entryPoints.size(); + auto list = vlc::wrap_carray( static_cast<vlc_ml_entrypoint_t*>( + calloc( entryPoints.size(), sizeof( vlc_ml_entrypoint_t ) ) ), + [nbItem]( vlc_ml_entrypoint_t* ptr ) { + vlc_ml_entrypoints_release( ptr, nbItem ); + }); + if ( unlikely( list == nullptr ) ) + return VLC_ENOMEM; + for ( auto i = 0u; i < entryPoints.size(); ++i ) + { + const auto ep = entryPoints[i].get(); + if ( ep->isPresent() == true ) + { + list[i].psz_mrl = strdup( ep->mrl().c_str() ); + if ( unlikely( list[i].psz_mrl == nullptr ) ) + return VLC_ENOMEM; + list[i].b_present = true; + } + else + { + list[i].psz_mrl = nullptr; + list[i].b_present = false; + } + list[i].b_banned = ep->isBanned(); + } + *(va_arg( args, vlc_ml_entrypoint_t**) ) = list.release(); + *(va_arg( args, size_t*) ) = entryPoints.size(); + break; + } + case VLC_ML_PAUSE_BACKGROUND: + m_ml->pauseBackgroundOperations(); + break; + case VLC_ML_RESUME_BACKGROUND: + m_ml->resumeBackgroundOperations(); + break; + case VLC_ML_CLEAR_HISTORY: + m_ml->clearHistory(); + break; + case VLC_ML_NEW_EXTERNAL_MEDIA: + { + auto mrl = va_arg( args, const char* ); + auto media = m_ml->addExternalMedia( mrl ); + if ( media == nullptr ) + return VLC_EGENERIC; + *va_arg( args, vlc_ml_media_t**) = CreateAndConvert<vlc_ml_media_t>( media.get() ); + return VLC_SUCCESS; + } + case VLC_ML_NEW_STREAM: + { + auto mrl = va_arg( args, const char* ); + auto media = m_ml->addStream( mrl ); + if ( media == nullptr ) + return VLC_EGENERIC; + *va_arg( args, vlc_ml_media_t**) = CreateAndConvert<vlc_ml_media_t>( media.get() ); + return VLC_SUCCESS; + } + case VLC_ML_MEDIA_INCREASE_PLAY_COUNT: + case VLC_ML_MEDIA_GET_MEDIA_PLAYBACK_PREF: + case VLC_ML_MEDIA_SET_MEDIA_PLAYBACK_PREF: + case VLC_ML_MEDIA_SET_THUMBNAIL: + return controlMedia( query, args ); + default: + return VLC_EGENERIC; + } + return VLC_SUCCESS; +} + +int MediaLibrary::List( int listQuery, const vlc_ml_query_params_t* params, va_list args ) +{ + if ( Start() == false ) + return VLC_EGENERIC; + + medialibrary::QueryParameters p{}; + medialibrary::QueryParameters* paramsPtr = nullptr; + uint32_t nbItems = 0; + uint32_t offset = 0; + const char* psz_pattern = nullptr; + if ( params ) + { + p.desc = params->b_desc; + p.sort = sortingCriteria( params->i_sort ); + nbItems = params->i_nbResults; + offset = params->i_offset; + psz_pattern = params->psz_pattern; + paramsPtr = &p; + } + switch ( listQuery ) + { + case VLC_ML_LIST_MEDIA_OF: + case VLC_ML_COUNT_MEDIA_OF: + case VLC_ML_LIST_ARTISTS_OF: + case VLC_ML_COUNT_ARTISTS_OF: + case VLC_ML_LIST_ALBUMS_OF: + case VLC_ML_COUNT_ALBUMS_OF: + { + auto parentType = va_arg( args, int ); + listQuery = filterListChildrenQuery( listQuery, parentType ); + } + default: + break; + } + switch( listQuery ) + { + case VLC_ML_LIST_ALBUM_TRACKS: + case VLC_ML_COUNT_ALBUM_TRACKS: + case VLC_ML_LIST_ALBUM_ARTISTS: + case VLC_ML_COUNT_ALBUM_ARTISTS: + return listAlbums( listQuery, paramsPtr, psz_pattern, nbItems, offset, args ); + + case VLC_ML_LIST_ARTIST_ALBUMS: + case VLC_ML_COUNT_ARTIST_ALBUMS: + case VLC_ML_LIST_ARTIST_TRACKS: + case VLC_ML_COUNT_ARTIST_TRACKS: + return listArtists( listQuery, paramsPtr, psz_pattern, nbItems, offset, args ); + + case VLC_ML_LIST_VIDEOS: + { + medialibrary::Query<medialibrary::IMedia> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchVideo( psz_pattern, paramsPtr ); + else + query = m_ml->videoFiles( paramsPtr ); + auto res = ml_convert_list<vlc_ml_media_list_t>( query->items( nbItems, offset ) ); + *va_arg( args, vlc_ml_media_list_t**) = res; + break; + } + case VLC_ML_COUNT_VIDEOS: + { + medialibrary::Query<medialibrary::IMedia> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchVideo( psz_pattern, paramsPtr ); + else + query = m_ml->videoFiles( paramsPtr ); + *va_arg( args, size_t* ) = query->count(); + break; + } + case VLC_ML_LIST_AUDIOS: + { + medialibrary::Query<medialibrary::IMedia> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchAudio( psz_pattern, paramsPtr ); + else + query = m_ml->audioFiles( paramsPtr ); + auto res = ml_convert_list<vlc_ml_media_list_t>( query->items( nbItems, offset ) ); + *va_arg( args, vlc_ml_media_list_t**) = res; + break; + } + case VLC_ML_COUNT_AUDIOS: + { + medialibrary::Query<medialibrary::IMedia> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchAudio( psz_pattern, paramsPtr ); + else + query = m_ml->audioFiles( paramsPtr ); + *va_arg( args, size_t* ) = query->count(); + break; + } + case VLC_ML_LIST_ALBUMS: + { + medialibrary::Query<medialibrary::IAlbum> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchAlbums( psz_pattern, paramsPtr ); + else + query = m_ml->albums( paramsPtr ); + auto res = ml_convert_list<vlc_ml_album_list_t>( query->items( nbItems, offset ) ); + *va_arg( args, vlc_ml_album_list_t**) = res; + break; + } + case VLC_ML_COUNT_ALBUMS: + { + medialibrary::Query<medialibrary::IAlbum> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchAlbums( psz_pattern, paramsPtr ); + else + query = m_ml->albums( paramsPtr ); + *va_arg( args, size_t* ) = query->count(); + break; + } + case VLC_ML_LIST_GENRES: + { + medialibrary::Query<medialibrary::IGenre> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchGenre( psz_pattern, paramsPtr ); + else + query = m_ml->genres( paramsPtr ); + auto res = ml_convert_list<vlc_ml_genre_list_t>( query->items( nbItems, offset ) ); + *va_arg( args, vlc_ml_genre_list_t**) = res; + break; + } + case VLC_ML_COUNT_GENRES: + { + medialibrary::Query<medialibrary::IGenre> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchGenre( psz_pattern, paramsPtr ); + else + query = m_ml->genres( paramsPtr ); + *va_arg( args, size_t* ) = query->count(); + break; + } + case VLC_ML_LIST_ARTISTS: + { + medialibrary::Query<medialibrary::IArtist> query; + bool includeAll = va_arg( args, int ) != 0; + if ( psz_pattern != nullptr ) + query = m_ml->searchArtists( psz_pattern, paramsPtr ); + else + { + query = m_ml->artists( includeAll, paramsPtr ); + } + auto res = ml_convert_list<vlc_ml_artist_list_t>( query->items( nbItems, offset ) ); + *va_arg( args, vlc_ml_artist_list_t**) = res; + break; + } + case VLC_ML_COUNT_ARTISTS: + { + medialibrary::Query<medialibrary::IArtist> query; + bool includeAll = va_arg( args, int ) != 0; + if ( psz_pattern != nullptr ) + query = m_ml->searchArtists( psz_pattern, paramsPtr ); + else + query = m_ml->artists( includeAll, paramsPtr ); + *va_arg( args, size_t* ) = query->count(); + break; + } + case VLC_ML_LIST_GENRE_ARTISTS: + case VLC_ML_COUNT_GENRE_ARTISTS: + case VLC_ML_LIST_GENRE_TRACKS: + case VLC_ML_COUNT_GENRE_TRACKS: + case VLC_ML_LIST_GENRE_ALBUMS: + case VLC_ML_COUNT_GENRE_ALBUMS: + return listGenre( listQuery, paramsPtr, psz_pattern, nbItems, offset, args ); + + case VLC_ML_LIST_MEDIA_LABELS: + case VLC_ML_COUNT_MEDIA_LABELS: + { + auto media = m_ml->media( va_arg( args, int64_t ) ); + if ( media == nullptr ) + return VLC_EGENERIC; + auto query = media->labels(); + switch ( listQuery ) + { + case VLC_ML_LIST_MEDIA_LABELS: + *va_arg( args, vlc_ml_label_list_t**) = ml_convert_list<vlc_ml_label_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_MEDIA_LABELS: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + case VLC_ML_LIST_SHOWS: + { + medialibrary::Query<medialibrary::IShow> query; + if ( psz_pattern != nullptr ) + query = m_ml->searchShows( psz_pattern, paramsPtr ); + else + query = m_ml->shows( paramsPtr ); + *va_arg( args, vlc_ml_show_list_t** ) = ml_convert_list<vlc_ml_show_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + } + case VLC_ML_COUNT_SHOWS: + { + auto query = m_ml->shows( paramsPtr ); + *va_arg( args, int64_t* ) = query->count(); + return VLC_SUCCESS; + } + case VLC_ML_LIST_SHOW_EPISODES: + case VLC_ML_COUNT_SHOW_EPISODES: + { + auto show = m_ml->show( va_arg( args, int64_t ) ); + if ( show == nullptr ) + return VLC_EGENERIC; + medialibrary::Query<medialibrary::IMedia> query; + if ( psz_pattern != nullptr ) + query = show->searchEpisodes( psz_pattern, paramsPtr ); + else + query = show->episodes( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_SHOW_EPISODES: + *va_arg( args, vlc_ml_media_list_t**) = ml_convert_list<vlc_ml_media_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_SHOW_EPISODES: + *va_arg( args, int64_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + case VLC_ML_LIST_PLAYLIST_MEDIA: + case VLC_ML_COUNT_PLAYLIST_MEDIA: + case VLC_ML_LIST_PLAYLISTS: + case VLC_ML_COUNT_PLAYLISTS: + return listPlaylist( listQuery, paramsPtr, psz_pattern, nbItems, offset, args ); + case VLC_ML_LIST_HISTORY: + { + auto query = m_ml->history(); + *va_arg( args, vlc_ml_media_list_t**) = ml_convert_list<vlc_ml_media_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + } + case VLC_ML_LIST_STREAM_HISTORY: + { + auto query = m_ml->streamHistory(); + *va_arg( args, vlc_ml_media_list_t**) = ml_convert_list<vlc_ml_media_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + } + } + return VLC_SUCCESS; +} + +void* MediaLibrary::Get( int query, int64_t id ) +{ + if ( Start() == false ) + return nullptr; + + switch ( query ) + { + case VLC_ML_GET_MEDIA: + { + auto media = m_ml->media( id ); + return CreateAndConvert<vlc_ml_media_t>( media.get() ); + } + case VLC_ML_GET_ALBUM: + { + auto album = m_ml->album( id ); + return CreateAndConvert<vlc_ml_album_t>( album.get() ); + } + case VLC_ML_GET_ARTIST: + { + auto artist = m_ml->artist( id ); + return CreateAndConvert<vlc_ml_artist_t>( artist.get() ); + } + case VLC_ML_GET_GENRE: + { + auto genre = m_ml->genre( id ); + return CreateAndConvert<vlc_ml_genre_t>( genre.get() ); + } + case VLC_ML_GET_SHOW: + { + auto show = m_ml->show( id ); + return CreateAndConvert<vlc_ml_show_t>( show.get() ); + } + case VLC_ML_GET_PLAYLIST: + { + auto playlist = m_ml->playlist( id ); + return CreateAndConvert<vlc_ml_playlist_t>( playlist.get() ); + } + default: + vlc_assert_unreachable(); + + } + return nullptr; +} + +medialibrary::IMedia::MetadataType MediaLibrary::metadataType( int meta ) +{ + switch ( meta ) + { + case VLC_ML_PLAYBACK_PREF_RATING: + return medialibrary::IMedia::MetadataType::Rating; + case VLC_ML_PLAYBACK_PREF_PROGRESS: + return medialibrary::IMedia::MetadataType::Progress; + case VLC_ML_PLAYBACK_PREF_SPEED: + return medialibrary::IMedia::MetadataType::Speed; + case VLC_ML_PLAYBACK_PREF_TITLE: + return medialibrary::IMedia::MetadataType::Title; + case VLC_ML_PLAYBACK_PREF_CHAPTER: + return medialibrary::IMedia::MetadataType::Chapter; + case VLC_ML_PLAYBACK_PREF_PROGRAM: + return medialibrary::IMedia::MetadataType::Program; + case VLC_ML_PLAYBACK_PREF_SEEN: + return medialibrary::IMedia::MetadataType::Seen; + case VLC_ML_PLAYBACK_PREF_VIDEO_TRACK: + return medialibrary::IMedia::MetadataType::VideoTrack; + case VLC_ML_PLAYBACK_PREF_ASPECT_RATIO: + return medialibrary::IMedia::MetadataType::AspectRatio; + case VLC_ML_PLAYBACK_PREF_ZOOM: + return medialibrary::IMedia::MetadataType::Zoom; + case VLC_ML_PLAYBACK_PREF_CROP: + return medialibrary::IMedia::MetadataType::Crop; + case VLC_ML_PLAYBACK_PREF_DEINTERLACE: + return medialibrary::IMedia::MetadataType::Deinterlace; + case VLC_ML_PLAYBACK_PREF_VIDEO_FILTER: + return medialibrary::IMedia::MetadataType::VideoFilter; + case VLC_ML_PLAYBACK_PREF_AUDIO_TRACK: + return medialibrary::IMedia::MetadataType::AudioTrack; + case VLC_ML_PLAYBACK_PREF_GAIN: + return medialibrary::IMedia::MetadataType::Gain; + case VLC_ML_PLAYBACK_PREF_AUDIO_DELAY: + return medialibrary::IMedia::MetadataType::AudioDelay; + case VLC_ML_PLAYBACK_PREF_SUBTITLE_TRACK: + return medialibrary::IMedia::MetadataType::SubtitleTrack; + case VLC_ML_PLAYBACK_PREF_SUBTITLE_DELAY: + return medialibrary::IMedia::MetadataType::SubtitleDelay; + case VLC_ML_PLAYBACK_PREF_APP_SPECIFIC: + return medialibrary::IMedia::MetadataType::ApplicationSpecific; + default: + vlc_assert_unreachable(); + } +} + +medialibrary::SortingCriteria MediaLibrary::sortingCriteria(int sort) +{ + switch ( sort ) + { + case VLC_ML_SORTING_DEFAULT: + return medialibrary::SortingCriteria::Default; + case VLC_ML_SORTING_ALPHA: + return medialibrary::SortingCriteria::Alpha; + case VLC_ML_SORTING_DURATION: + return medialibrary::SortingCriteria::Duration; + case VLC_ML_SORTING_INSERTIONDATE: + return medialibrary::SortingCriteria::InsertionDate; + case VLC_ML_SORTING_LASTMODIFICATIONDATE: + return medialibrary::SortingCriteria::LastModificationDate; + case VLC_ML_SORTING_RELEASEDATE: + return medialibrary::SortingCriteria::ReleaseDate; + case VLC_ML_SORTING_FILESIZE: + return medialibrary::SortingCriteria::FileSize; + case VLC_ML_SORTING_ARTIST: + return medialibrary::SortingCriteria::Artist; + case VLC_ML_SORTING_PLAYCOUNT: + return medialibrary::SortingCriteria::PlayCount; + case VLC_ML_SORTING_ALBUM: + return medialibrary::SortingCriteria::Album; + case VLC_ML_SORTING_FILENAME: + return medialibrary::SortingCriteria::Filename; + case VLC_ML_SORTING_TRACKNUMBER: + return medialibrary::SortingCriteria::TrackNumber; + default: + vlc_assert_unreachable(); + } +} + +int MediaLibrary::getMeta( const medialibrary::IMedia& media, int meta, char** result ) +{ + auto& md = media.metadata( metadataType( meta ) ); + if ( md.isSet() == false ) + { + *result = nullptr; + return VLC_SUCCESS; + } + *result = strdup( md.str().c_str() ); + if ( *result == nullptr ) + return VLC_ENOMEM; + return VLC_SUCCESS; +} + +int MediaLibrary::setMeta( medialibrary::IMedia& media, int meta, const char* value ) +{ + bool res; + if ( value == nullptr ) + res = media.unsetMetadata( metadataType( meta ) ); + else + res = media.setMetadata( metadataType( meta ), value ); + if ( res == false ) + return VLC_EGENERIC; + return VLC_SUCCESS; +} + +int MediaLibrary::controlMedia( int query, va_list args ) +{ + auto mediaId = va_arg( args, int64_t ); + auto m = m_ml->media( mediaId ); + if ( m == nullptr ) + return VLC_EGENERIC; + switch( query ) + { + case VLC_ML_MEDIA_INCREASE_PLAY_COUNT: + if ( m->increasePlayCount() == false ) + return VLC_EGENERIC; + return VLC_SUCCESS; + case VLC_ML_MEDIA_GET_MEDIA_PLAYBACK_PREF: + { + auto meta = va_arg( args, int ); + auto res = va_arg( args, char** ); + return getMeta( *m, meta, res ); + } + case VLC_ML_MEDIA_SET_MEDIA_PLAYBACK_PREF: + { + auto meta = va_arg( args, int ); + auto value = va_arg( args, const char* ); + return setMeta( *m, meta, value ); + } + case VLC_ML_MEDIA_SET_THUMBNAIL: + { + auto mrl = va_arg( args, const char* ); + m->setThumbnail( mrl ); + return VLC_SUCCESS; + } + case VLC_ML_MEDIA_ADD_EXTERNAL_MRL: + { + auto mrl = va_arg( args, const char* ); + auto type = va_arg( args, int ); + medialibrary::IFile::Type mlType; + switch ( type ) + { + case VLC_ML_FILE_TYPE_UNKNOWN: + // The type can't be main since this is added to an existing media + // which must already have a file + case VLC_ML_FILE_TYPE_MAIN: + case VLC_ML_FILE_TYPE_PLAYLIST: + return VLC_EGENERIC; + case VLC_ML_FILE_TYPE_PART: + mlType = medialibrary::IFile::Type::Part; + break; + case VLC_ML_FILE_TYPE_SOUNDTRACK: + mlType = medialibrary::IFile::Type::Soundtrack; + break; + case VLC_ML_FILE_TYPE_SUBTITLE: + mlType = medialibrary::IFile::Type::Subtitles; + break; + } + if ( m->addExternalMrl( mrl, mlType ) == nullptr ) + return VLC_EGENERIC; + return VLC_SUCCESS; + } + default: + vlc_assert_unreachable(); + } +} + +int MediaLibrary::filterListChildrenQuery( int query, int parentType ) +{ + switch( query ) + { + case VLC_ML_LIST_MEDIA_OF: + switch ( parentType ) + { + case VLC_ML_PARENT_ALBUM: + return VLC_ML_LIST_ALBUM_TRACKS; + case VLC_ML_PARENT_ARTIST: + return VLC_ML_LIST_ALBUM_TRACKS; + case VLC_ML_PARENT_SHOW: + return VLC_ML_LIST_SHOW_EPISODES; + case VLC_ML_PARENT_GENRE: + return VLC_ML_LIST_GENRE_TRACKS; + case VLC_ML_PARENT_PLAYLIST: + return VLC_ML_LIST_PLAYLIST_MEDIA; + default: + vlc_assert_unreachable(); + } + case VLC_ML_COUNT_MEDIA_OF: + switch ( parentType ) + { + case VLC_ML_PARENT_ALBUM: + return VLC_ML_COUNT_ALBUM_TRACKS; + case VLC_ML_PARENT_ARTIST: + return VLC_ML_COUNT_ALBUM_TRACKS; + case VLC_ML_PARENT_SHOW: + return VLC_ML_COUNT_SHOW_EPISODES; + case VLC_ML_PARENT_GENRE: + return VLC_ML_COUNT_GENRE_TRACKS; + case VLC_ML_PARENT_PLAYLIST: + return VLC_ML_COUNT_PLAYLIST_MEDIA; + default: + vlc_assert_unreachable(); + } + case VLC_ML_LIST_ALBUMS_OF: + switch ( parentType ) + { + case VLC_ML_PARENT_ARTIST: + return VLC_ML_LIST_ARTIST_ALBUMS; + case VLC_ML_PARENT_GENRE: + return VLC_ML_LIST_GENRE_ALBUMS; + default: + vlc_assert_unreachable(); + } + case VLC_ML_COUNT_ALBUMS_OF: + switch ( parentType ) + { + case VLC_ML_PARENT_ARTIST: + return VLC_ML_COUNT_ARTIST_ALBUMS; + case VLC_ML_PARENT_GENRE: + return VLC_ML_COUNT_GENRE_ALBUMS; + default: + vlc_assert_unreachable(); + } + case VLC_ML_LIST_ARTISTS_OF: + switch ( parentType ) + { + case VLC_ML_PARENT_ALBUM: + return VLC_ML_LIST_ALBUM_ARTISTS; + case VLC_ML_PARENT_ARTIST: + return VLC_ML_LIST_GENRE_ARTISTS; + default: + vlc_assert_unreachable(); + } + case VLC_ML_COUNT_ARTISTS_OF: + switch ( parentType ) + { + case VLC_ML_PARENT_ALBUM: + return VLC_ML_COUNT_ALBUM_ARTISTS; + case VLC_ML_PARENT_ARTIST: + return VLC_ML_COUNT_GENRE_ARTISTS; + default: + vlc_assert_unreachable(); + } + default: + vlc_assert_unreachable(); + } +} + +int MediaLibrary::listAlbums( int listQuery, const medialibrary::QueryParameters* paramsPtr, + const char* pattern, uint32_t nbItems, uint32_t offset, va_list args ) +{ + auto album = m_ml->album( va_arg( args, int64_t ) ); + if ( album == nullptr ) + return VLC_EGENERIC; + switch ( listQuery ) + { + case VLC_ML_LIST_ALBUM_TRACKS: + case VLC_ML_COUNT_ALBUM_TRACKS: + { + medialibrary::Query<medialibrary::IMedia> query; + if ( pattern != nullptr ) + query = album->searchTracks( pattern, paramsPtr ); + else + query = album->tracks( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_ALBUM_TRACKS: + *va_arg( args, vlc_ml_media_list_t**) = ml_convert_list<vlc_ml_media_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_ALBUM_TRACKS: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + case VLC_ML_LIST_ALBUM_ARTISTS: + case VLC_ML_COUNT_ALBUM_ARTISTS: + { + auto query = album->artists( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_ALBUM_ARTISTS: + *va_arg( args, vlc_ml_artist_list_t**) = ml_convert_list<vlc_ml_artist_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_ALBUM_ARTISTS: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + default: + vlc_assert_unreachable(); + } +} + +int MediaLibrary::listArtists( int listQuery, const medialibrary::QueryParameters* paramsPtr, + const char* pattern, uint32_t nbItems, uint32_t offset, + va_list args ) +{ + auto artist = m_ml->artist( va_arg( args, int64_t ) ); + if ( artist == nullptr ) + return VLC_EGENERIC; + switch( listQuery ) + { + case VLC_ML_LIST_ARTIST_ALBUMS: + case VLC_ML_COUNT_ARTIST_ALBUMS: + { + medialibrary::Query<medialibrary::IAlbum> query; + if ( pattern != nullptr ) + query = artist->searchAlbums( pattern, paramsPtr ); + else + query = artist->albums( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_ARTIST_ALBUMS: + *va_arg( args, vlc_ml_album_list_t**) = ml_convert_list<vlc_ml_album_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_ARTIST_ALBUMS: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + case VLC_ML_LIST_ARTIST_TRACKS: + case VLC_ML_COUNT_ARTIST_TRACKS: + { + medialibrary::Query<medialibrary::IMedia> query; + if ( pattern != nullptr ) + query = artist->searchTracks( pattern, paramsPtr ); + else + query = artist->tracks( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_ARTIST_TRACKS: + *va_arg( args, vlc_ml_media_list_t**) = ml_convert_list<vlc_ml_media_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_ARTIST_TRACKS: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + default: + vlc_assert_unreachable(); + } +} + +int MediaLibrary::listGenre( int listQuery, const medialibrary::QueryParameters* paramsPtr, + const char* pattern, uint32_t nbItems, uint32_t offset, va_list args ) +{ + auto genre = m_ml->genre( va_arg( args, int64_t ) ); + if ( genre == nullptr ) + return VLC_EGENERIC; + switch( listQuery ) + { + case VLC_ML_LIST_GENRE_ARTISTS: + case VLC_ML_COUNT_GENRE_ARTISTS: + { + medialibrary::Query<medialibrary::IArtist> query; + if ( pattern != nullptr ) + query = genre->searchArtists( pattern, paramsPtr ); + else + query = genre->artists( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_GENRE_ARTISTS: + *va_arg( args, vlc_ml_artist_list_t**) = ml_convert_list<vlc_ml_artist_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_GENRE_ARTISTS: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + case VLC_ML_LIST_GENRE_TRACKS: + case VLC_ML_COUNT_GENRE_TRACKS: + { + medialibrary::Query<medialibrary::IMedia> query; + if ( pattern != nullptr ) + query = genre->searchTracks( pattern, paramsPtr ); + else + query = genre->tracks( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_GENRE_TRACKS: + *va_arg( args, vlc_ml_media_list_t**) = ml_convert_list<vlc_ml_media_list_t>( query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_GENRE_TRACKS: + *va_arg( args, size_t*) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + case VLC_ML_LIST_GENRE_ALBUMS: + case VLC_ML_COUNT_GENRE_ALBUMS: + { + medialibrary::Query<medialibrary::IAlbum> query; + if ( pattern != nullptr ) + query = genre->searchAlbums( pattern, paramsPtr ); + else + query = genre->albums( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_GENRE_ALBUMS: + *va_arg( args, vlc_ml_album_list_t**) = ml_convert_list<vlc_ml_album_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_GENRE_ALBUMS: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + default: + vlc_assert_unreachable(); + } +} + +int MediaLibrary::listPlaylist( int listQuery, const medialibrary::QueryParameters* paramsPtr, + const char* pattern, uint32_t nbItems, uint32_t offset, va_list args ) +{ + switch( listQuery ) + { + case VLC_ML_LIST_PLAYLISTS: + case VLC_ML_COUNT_PLAYLISTS: + { + medialibrary::Query<medialibrary::IPlaylist> query; + if ( pattern != nullptr ) + query = m_ml->searchPlaylists( pattern, paramsPtr ); + else + query = m_ml->playlists( paramsPtr ); + switch ( listQuery ) + { + case VLC_ML_LIST_PLAYLISTS: + *va_arg( args, vlc_ml_playlist_list_t** ) = ml_convert_list<vlc_ml_playlist_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_PLAYLISTS: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + case VLC_ML_LIST_PLAYLIST_MEDIA: + case VLC_ML_COUNT_PLAYLIST_MEDIA: + { + auto playlist = m_ml->playlist( va_arg( args, int64_t ) ); + if ( playlist == nullptr ) + return VLC_EGENERIC; + medialibrary::Query<medialibrary::IMedia> query; + if ( pattern != nullptr ) + query = playlist->searchMedia( pattern, paramsPtr ); + else + query = playlist->media(); + switch ( listQuery ) + { + case VLC_ML_LIST_PLAYLIST_MEDIA: + *va_arg( args, vlc_ml_media_list_t**) = ml_convert_list<vlc_ml_media_list_t>( + query->items( nbItems, offset ) ); + return VLC_SUCCESS; + case VLC_ML_COUNT_PLAYLIST_MEDIA: + *va_arg( args, size_t* ) = query->count(); + return VLC_SUCCESS; + default: + vlc_assert_unreachable(); + } + } + default: + vlc_assert_unreachable(); + } +} + +static void* Get( vlc_medialibrary_t* module, int query, int64_t id ) +{ + auto ml = static_cast<MediaLibrary*>( module->p_sys ); + return ml->Get( query, id ); +} + +static int List( vlc_medialibrary_t* module, int query, + const vlc_ml_query_params_t* params, ... ) +{ + va_list args; + va_start( args, params ); + auto ml = static_cast<MediaLibrary*>( module->p_sys ); + auto res = ml->List( query, params, args ); + va_end( args ); + return res; +} + +static int Control( vlc_medialibrary_t* module, int query, ... ) +{ + va_list args; + va_start( args, query ); + auto ml = static_cast<MediaLibrary*>( module->p_sys ); + int res = ml->Control( query, args ); + va_end( args ); + return res; +} + +static int Open( vlc_object_t* obj ) +{ + vlc_medialibrary_t* p_module = reinterpret_cast<vlc_medialibrary_t*>( obj ); + + try + { + p_module->p_sys = new MediaLibrary( obj ); + } + catch ( const std::exception& ex ) + { + msg_Err( obj, "Failed to instantiate/initialize medialibrary: %s", ex.what() ); + return VLC_EGENERIC; + } + p_module->pf_control = Control; + p_module->pf_get = Get; + p_module->pf_list = List; + return VLC_SUCCESS; +} + +static void Close( vlc_medialibrary_t* module ) +{ + MediaLibrary* p_ml = static_cast<MediaLibrary*>( module->p_sys ); + delete p_ml; +} + +#define ML_FOLDER_TEXT _( "Folders discovered by the media library" ) +#define ML_FOLDER_LONGTEXT _( "Semicolon separated list of folders to discover " \ + "media from" ) + +vlc_module_begin() + set_shortname(N_("media library")) + set_description(N_( "Organize your media" )) + set_category(CAT_ADVANCED) + set_subcategory(SUBCAT_ADVANCED_MISC) + set_capability("medialibrary", 100) + set_callbacks(Open, Close) + add_string( "ml-folders", nullptr, ML_FOLDER_TEXT, ML_FOLDER_LONGTEXT, false ) +vlc_module_end() diff --git a/modules/misc/medialibrary/medialibrary.h b/modules/misc/medialibrary/medialibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..0b2a86abe0416c673dbaf1b3640b34a438234bc7 --- /dev/null +++ b/modules/misc/medialibrary/medialibrary.h @@ -0,0 +1,225 @@ +/***************************************************************************** + * medialibrary.h: medialibrary module common declarations + ***************************************************************************** + * Copyright © 2015-2016 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. + *****************************************************************************/ + +#ifndef MEDIALIBRARY_H +#define MEDIALIBRARY_H + +#include <medialibrary/IMediaLibrary.h> +#include <medialibrary/parser/IParserService.h> +#include <medialibrary/parser/IItem.h> +#include <medialibrary/parser/Parser.h> +#include <medialibrary/IMedia.h> + +#include <vlc_common.h> +#include <vlc_threads.h> +#include <vlc_input_item.h> +#include <vlc_input.h> +#include <vlc_media_library.h> +#include <vlc_cxx_helpers.hpp> + +#include <cstdarg> + +struct vlc_event_t; +struct vlc_object_t; + +class Logger; + +class MetadataExtractor : public medialibrary::parser::IParserService +{ +private: + struct ParseContext + { + ParseContext( MetadataExtractor* mde, medialibrary::parser::IItem& item ) + : inputItem( nullptr, &input_item_Release ) + , input( nullptr, &input_Close ) + , needsProbing( false ) + , state( INIT_S ) + , mde( mde ) + , item( item ) + { + vlc_mutex_init( &m_mutex ); + vlc_cond_init( &m_cond ); + } + ~ParseContext() + { + vlc_cond_destroy( &m_cond ); + vlc_mutex_destroy( &m_mutex ); + } + + std::unique_ptr<input_item_t, decltype(&input_item_Release)> inputItem; + std::unique_ptr<input_thread_t, decltype(&input_Close)> input; + vlc_cond_t m_cond; + vlc_mutex_t m_mutex; + bool needsProbing; + input_state_e state; + MetadataExtractor* mde; + medialibrary::parser::IItem& item; + }; + +public: + MetadataExtractor( vlc_object_t* parent ); + virtual ~MetadataExtractor() = default; + + // All methods are meant to be accessed through IParserService, not directly + // hence they are all private +private: + virtual medialibrary::parser::Status run( medialibrary::parser::IItem& item ) override; + virtual const char*name() const override; + virtual uint8_t nbThreads() const override; + virtual medialibrary::parser::Step targetedStep() const override; + virtual bool initialize( medialibrary::IMediaLibrary* ml ) override; + virtual void onFlushing() override; + virtual void onRestarted() override; + + void onInputEvent( const vlc_input_event* event, ParseContext& ctx ); + void onSubItemAdded( const vlc_event_t* event, ParseContext& ctx ); + void populateItem( medialibrary::parser::IItem& item, input_item_t* inputItem ); + + static void onInputEvent( input_thread_t *input, void *user_data, + const struct vlc_input_event *event ); + static void onSubItemAdded( const vlc_event_t* event, void* data ); + +private: + vlc_object_t* m_obj; +}; + +class MediaLibrary : public medialibrary::IMediaLibraryCb +{ +public: + MediaLibrary( vlc_object_t* obj ); + bool Start(); + int Control( int query, va_list args ); + int List( int query, const vlc_ml_query_params_t* params, va_list args ); + void* Get( int query, int64_t id ); + +private: + int controlMedia( int query, va_list args ); + int getMeta( const medialibrary::IMedia& media, int meta, char** result ); + int setMeta( medialibrary::IMedia& media, int meta, const char* value ); + int filterListChildrenQuery( int query, int parentType ); + int listAlbums( int listQuery, const medialibrary::QueryParameters* paramsPtr, + const char* pattern, uint32_t nbItems, uint32_t offset, va_list args ); + int listArtists( int listQuery, const medialibrary::QueryParameters* paramsPtr, + const char* pattern, uint32_t nbItems, uint32_t offset, va_list args ); + int listGenre( int listQuery, const medialibrary::QueryParameters* paramsPtr, + const char* pattern, uint32_t nbItems, uint32_t offset, va_list args ); + int listPlaylist( int listQuery, const medialibrary::QueryParameters* paramsPtr, + const char* pattern, uint32_t nbItems, uint32_t offset, va_list args ); + + static medialibrary::IMedia::MetadataType metadataType( int meta ); + static medialibrary::SortingCriteria sortingCriteria( int sort ); + +private: + vlc_object_t* m_obj; + std::unique_ptr<Logger> m_logger; + std::unique_ptr<medialibrary::IMediaLibrary> m_ml; + + // IMediaLibraryCb interface +public: + virtual void onMediaAdded(std::vector<medialibrary::MediaPtr> media) override; + virtual void onMediaUpdated(std::vector<medialibrary::MediaPtr> media) override; + virtual void onMediaDeleted(std::vector<int64_t> mediaIds) override; + virtual void onArtistsAdded(std::vector<medialibrary::ArtistPtr> artists) override; + virtual void onArtistsModified(std::vector<medialibrary::ArtistPtr> artists) override; + virtual void onArtistsDeleted(std::vector<int64_t> artistsIds) override; + virtual void onAlbumsAdded(std::vector<medialibrary::AlbumPtr> albums) override; + virtual void onAlbumsModified(std::vector<medialibrary::AlbumPtr> albums) override; + virtual void onAlbumsDeleted(std::vector<int64_t> albumsIds) override; + virtual void onTracksAdded(std::vector<medialibrary::AlbumTrackPtr> tracks) override; + virtual void onTracksDeleted(std::vector<int64_t> trackIds) override; + virtual void onPlaylistsAdded(std::vector<medialibrary::PlaylistPtr> playlists) override; + virtual void onPlaylistsModified(std::vector<medialibrary::PlaylistPtr> playlists) override; + virtual void onPlaylistsDeleted(std::vector<int64_t> playlistIds) override; + virtual void onDiscoveryStarted(const std::string& entryPoint) override; + virtual void onDiscoveryProgress(const std::string& entryPoint) override; + virtual void onDiscoveryCompleted(const std::string& entryPoint) override; + virtual void onReloadStarted(const std::string& entryPoint) override; + virtual void onReloadCompleted(const std::string& entryPoint) override; + virtual void onEntryPointRemoved(const std::string& entryPoint, bool success) override; + virtual void onEntryPointBanned(const std::string& entryPoint, bool success) override; + virtual void onEntryPointUnbanned(const std::string& entryPoint, bool success) override; + virtual void onParsingStatsUpdated(uint32_t percent) override; + virtual void onBackgroundTasksIdleChanged(bool isIdle) override; + virtual void onMediaThumbnailReady(medialibrary::MediaPtr media, bool success) override; +}; + +bool Convert( const medialibrary::IMedia* input, vlc_ml_media_t& output ); +bool Convert( const medialibrary::IFile* input, vlc_ml_file_t& output ); +bool Convert( const medialibrary::IMovie* input, vlc_ml_movie_t& output ); +bool Convert( const medialibrary::IShowEpisode* input, vlc_ml_show_episode_t& output ); +bool Convert( const medialibrary::IAlbumTrack* input, vlc_ml_album_track_t& output ); +bool Convert( const medialibrary::IAlbum* input, vlc_ml_album_t& output ); +bool Convert( const medialibrary::IArtist* input, vlc_ml_artist_t& output ); +bool Convert( const medialibrary::IGenre* input, vlc_ml_genre_t& output ); +bool Convert( const medialibrary::IShow* input, vlc_ml_show_t& output ); +bool Convert( const medialibrary::ILabel* input, vlc_ml_label_t& output ); +bool Convert( const medialibrary::IPlaylist* input, vlc_ml_playlist_t& output ); + +template <typename To, typename From> +To* ml_convert_list( const std::vector<std::shared_ptr<From>>& input ) +{ + // This function uses duck typing and assumes all lists have a p_items member + static_assert( std::is_pointer<To>::value == false, + "Destination type must not be a pointer" ); + static_assert( std::is_array<decltype(To::p_items)>::value == true, + "Missing or invalid p_items member" ); + + // Allocate the ml_*_list_t + using ItemType = typename std::remove_extent<decltype(To::p_items)>::type; + auto list = vlc::wrap_cptr( + static_cast<To*>( malloc( sizeof( To ) + input.size() * sizeof( ItemType ) ) ), + static_cast<void(*)(To*)>( &vlc_ml_release_obj ) ); + if ( unlikely( list == nullptr ) ) + return nullptr; + + list->i_nb_items = 0; + + for ( auto i = 0u; i < input.size(); ++i ) + { + if ( Convert( input[i].get(), list->p_items[i] ) == false ) + return nullptr; + list->i_nb_items++; + } + return list.release(); +} + +template <typename T, typename Input> +T* CreateAndConvert( const Input* input ) +{ + if ( input == nullptr ) + return nullptr; + auto res = vlc::wrap_cptr( + static_cast<T*>( malloc( sizeof( T ) ) ), + static_cast<void(*)(T*)>( &vlc_ml_release_obj ) ); + if ( unlikely( res == nullptr ) ) + return nullptr; + if ( Convert( input, *res ) == false ) + return nullptr; + // Override the pf_relase that each Convert<T> helper will assign. + // The Convert function will use the ReleaseRef variant of the release function, + // as it converts in place, and doesn't have to free the allocated pointer. + // When CreateAndConvert is used, we heap-allocate an instance of T, and therefor + // we also need to release it. + return res.release(); +} + + + +#endif // MEDIALIBRARY_H diff --git a/po/POTFILES.in b/po/POTFILES.in index c189881851134ac30d89669f78312dd4c15c17ce..0e69ba8ec45459f5573151256b4d534cc9e91147 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -990,6 +990,7 @@ modules/misc/gnutls.c modules/misc/inhibit/dbus.c modules/misc/inhibit/wl-idle-inhibit.c modules/misc/inhibit/xdg.c +modules/misc/medialibrary/medialib.cpp modules/misc/playlist/export.c modules/misc/playlist/html.c modules/misc/playlist/m3u.c