Add a new medialibrary module
authorHugo Beauzée-Luyssen <hugo@beauzee.fr>
Tue, 19 Jun 2018 09:39:27 +0000 (11:39 +0200)
committerHugo Beauzée-Luyssen <hugo@beauzee.fr>
Tue, 17 Jul 2018 22:16:39 +0000 (00:16 +0200)
configure.ac
modules/MODULES_LIST
modules/misc/Makefile.am
modules/misc/medialibrary/MetadataExtractor.cpp [new file with mode: 0644]
modules/misc/medialibrary/entities.cpp [new file with mode: 0644]
modules/misc/medialibrary/medialib.cpp [new file with mode: 0644]
modules/misc/medialibrary/medialibrary.h [new file with mode: 0644]
po/POTFILES.in

index ba76fc1..243304f 100644 (file)
@@ -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
index cda3b8b..ee364c2 100644 (file)
@@ -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
index f2d13ed..89946bb 100644 (file)
@@ -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 (file)
index 0000000..10b209a
--- /dev/null
@@ -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 (file)
index 0000000..98a80ae
--- /dev/null
@@ -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 (file)
index 0000000..20607e2
--- /dev/null
@@ -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 (file)
index 0000000..0b2a86a
--- /dev/null
@@ -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
index c189881..0e69ba8 100644 (file)
@@ -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