Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
fa10c711 by Gabriel Lafond-Thenaille at 2026-01-07T08:09:14+00:00
medialibrary: Create LazyPreparser class
* Add a new class LazePreparser to only create a `vlc_preparser_t` at
the first request.
- - - - -
4bb4d5a1 by Gabriel Lafond-Thenaille at 2026-01-07T08:09:14+00:00
medialibrary: thumbnailer: Replace `vlc_preparser_t` by LazyPreparser
- - - - -
8962a2dc by Gabriel Lafond-Thenaille at 2026-01-07T08:09:14+00:00
medialibrary: metadata: Replace `input_item_Parse` calls
* Use the `vlc_preparser` api insteed of the `input_item_Parse`.
* Use a LazyPreparser to prevent useless preparser creation.
- - - - -
8a187726 by Gabriel Lafond-Thenaille at 2026-01-07T08:09:14+00:00
medialibrary: directory parsing: Replace `input_item_Parse` calls
* Use the `vlc_preparser` api insteed of the `input_item_Parse`.
* Use a LazyPreparser to prevent useless preparser creation.
- - - - -
8 changed files:
- + modules/misc/medialibrary/LazyPreparser.h
- modules/misc/medialibrary/MetadataExtractor.cpp
- modules/misc/medialibrary/Thumbnailer.cpp
- modules/misc/medialibrary/fs/directory.cpp
- modules/misc/medialibrary/fs/directory.h
- modules/misc/medialibrary/fs/fs.cpp
- modules/misc/medialibrary/fs/fs.h
- modules/misc/medialibrary/medialibrary.h
Changes:
=====================================
modules/misc/medialibrary/LazyPreparser.h
=====================================
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*****************************************************************************
+ * LazyPreparser.h: class to create a preparser at the first request
+ *****************************************************************************
+ * Copyright © 2025 Videolabs, VideoLAN and VLC authors
+ *
+ * Authors: Gabriel Lafond Thenaille <[email protected]>
+ *****************************************************************************/
+
+#ifndef LAZYPREPARSER_H
+#define LAZYPREPARSER_H
+
+#include <vlc_common.h>
+#include <vlc_preparser.h>
+
+class LazyPreparser
+{
+ public:
+ LazyPreparser(vlc_object_t *obj, const struct vlc_preparser_cfg cfg)
+ : m_obj(obj)
+ , m_cfg(cfg)
+ , m_preparser(nullptr, &vlc_preparser_Delete)
+ {
+ assert(obj != NULL);
+ }
+
+ vlc_preparser_t *instance()
+ {
+ vlc::threads::mutex_locker locker(m_mutex);
+
+ vlc_preparser_t *preparser = m_preparser.get();
+ if (preparser == nullptr) {
+ m_preparser.reset(vlc_preparser_New(m_obj, &m_cfg));
+ preparser = m_preparser.get();
+ if (preparser == nullptr) {
+ msg_Warn(m_obj, "LazyPreparser: Failed to instantiate a
vlc_preparser_t!");
+ } else {
+ msg_Dbg(m_obj, "LazyPreparser: vlc_preparser_t created!");
+ }
+ }
+ return preparser;
+ }
+
+ vlc_preparser_t *get()
+ {
+ vlc::threads::mutex_locker locker(m_mutex);
+ return m_preparser.get();
+ }
+
+ private:
+ vlc::threads::mutex m_mutex;
+ vlc_object_t *m_obj;
+ const struct vlc_preparser_cfg m_cfg;
+ std::unique_ptr<vlc_preparser_t, void(*)(vlc_preparser_t*)>
m_preparser;
+};
+
+#endif
=====================================
modules/misc/medialibrary/MetadataExtractor.cpp
=====================================
@@ -27,6 +27,7 @@
#include <vlc_image.h>
#include <vlc_hash.h>
#include <vlc_fs.h>
+#include <vlc_preparser.h>
EmbeddedThumbnail::EmbeddedThumbnail( input_attachment_t* a, vlc_fourcc_t fcc )
: m_attachment( vlc_input_attachment_Hold( a ) )
@@ -88,19 +89,15 @@ std::string EmbeddedThumbnail::extension() const
MetadataExtractor::MetadataExtractor( vlc_object_t* parent )
: m_currentCtx( nullptr )
, m_obj( parent )
+ , m_parser(parent, {
+ .types = VLC_PREPARSER_TYPE_PARSE | VLC_PREPARSER_OPTION_SUBITEMS,
+ .max_parser_threads = 1,
+ .max_thumbnailer_threads = 0,
+ .timeout = VLC_TICK_FROM_SEC(5),
+ })
{
}
-void MetadataExtractor::onParserEnded( ParseContext& ctx, int status )
-{
- vlc::threads::mutex_locker lock( ctx.mde->m_mutex );
-
- // We need to probe the item now, but not from the input thread
- ctx.success = status == VLC_SUCCESS;
- ctx.needsProbing = true;
- ctx.mde->m_cond.signal();
-}
-
void MetadataExtractor::populateItem( medialibrary::parser::IItem& item,
input_item_t* inputItem )
{
vlc_mutex_locker lock( &inputItem->lock );
@@ -176,22 +173,45 @@ void MetadataExtractor::populateItem(
medialibrary::parser::IItem& item, input_i
}
}
-void MetadataExtractor::onParserEnded( input_item_t *, int status, void *data )
+void MetadataExtractor::onParserEnded(vlc_preparser_req *req, int status, void
*data)
{
auto* ctx = static_cast<ParseContext*>( data );
- ctx->mde->onParserEnded( *ctx, status );
+
+ vlc::threads::mutex_locker lock( ctx->mde->m_mutex );
+ if (status == VLC_SUCCESS) {
+ if (ctx->item.fileType() == medialibrary::IFile::Type::Playlist
+ && ctx->item.nbLinkedItems() == 0) {
+ ctx->status = medialibrary::parser::Status::Fatal;
+ } else {
+ ctx->mde->populateItem(ctx->item, vlc_preparser_req_GetItem(req));
+ ctx->status = medialibrary::parser::Status::Success;
+ }
+ } else {
+ ctx->status = medialibrary::parser::Status::Fatal;
+ }
+ ctx->done = true;
+ ctx->mde->m_currentCtx = nullptr;
+ ctx->mde->m_cond.broadcast();
+ vlc_preparser_req_Release(req);
}
-void MetadataExtractor::onParserSubtreeAdded( input_item_t *,
+void MetadataExtractor::onParserSubtreeAdded( vlc_preparser_req *,
input_item_node_t *subtree,
void *data )
{
auto* ctx = static_cast<ParseContext*>( data );
- ctx->mde->addSubtree( *ctx, subtree );
+
+ for ( auto i = 0; i < subtree->i_children; ++i )
+ {
+ auto it = subtree->pp_children[i]->p_item;
+ auto& subItem = ctx->item.createLinkedItem( it->psz_uri,
+
medialibrary::IFile::Type::Main, i );
+ ctx->mde->populateItem( subItem, it );
+ }
input_item_node_Delete(subtree);
}
-void MetadataExtractor::onAttachmentsAdded( input_item_t *,
+void MetadataExtractor::onAttachmentsAdded( vlc_preparser_req *,
input_attachment_t *const *array,
size_t count, void *data )
{
@@ -206,77 +226,46 @@ void MetadataExtractor::onAttachmentsAdded( input_item_t
*,
}
}
-void MetadataExtractor::addSubtree( ParseContext& ctx, input_item_node_t *root
)
-{
- for ( auto i = 0; i < root->i_children; ++i )
- {
- auto it = root->pp_children[i]->p_item;
- auto& subItem = ctx.item.createLinkedItem( it->psz_uri,
-
medialibrary::IFile::Type::Main, i );
- populateItem( subItem, it );
- }
-}
-
medialibrary::parser::Status MetadataExtractor::run(
medialibrary::parser::IItem& item )
{
ParseContext ctx( this, item );
- ctx.inputItem = {
- input_item_New( item.mrl().c_str(), NULL ),
- &input_item_Release
- };
- if ( ctx.inputItem == nullptr )
- return medialibrary::parser::Status::Fatal;
-
- static const input_item_parser_cbs_t cbs = {
- &MetadataExtractor::onParserEnded,
- &MetadataExtractor::onParserSubtreeAdded,
- &MetadataExtractor::onAttachmentsAdded,
- };
-
- const struct input_item_parser_cfg cfg= {
- .cbs = &cbs,
- .cbs_data = std::addressof( ctx ),
- .subitems = true,
- .interact = false,
- };
- m_currentCtx = &ctx;
- ctx.inputParser = {
- input_item_Parse( m_obj, ctx.inputItem.get(), &cfg ),
- &input_item_parser_id_Release
- };
- if ( ctx.inputParser == nullptr )
- {
- m_currentCtx = nullptr;
+ auto inputItem = vlc::wrap_cptr(input_item_New(item.mrl().c_str(), NULL),
+ &input_item_Release);
+ if (inputItem == nullptr)
return medialibrary::parser::Status::Fatal;
- }
{
vlc::threads::mutex_locker lock( m_mutex );
- auto deadline = vlc_tick_now() + VLC_TICK_FROM_SEC( 5 );
- while ( ctx.needsProbing == false && ctx.inputParser != nullptr )
+ m_currentCtx = &ctx;
+
+ int options = VLC_PREPARSER_TYPE_PARSE | VLC_PREPARSER_OPTION_SUBITEMS;
+
+ static const struct vlc_preparser_cbs cbs = {
+ .on_ended = onParserEnded,
+ .on_subtree_added = onParserSubtreeAdded,
+ .on_attachments_added = onAttachmentsAdded,
+ };
+
+ vlc_preparser_t *preparser = m_parser.instance();
+ if (preparser == nullptr) {
+ return medialibrary::parser::Status::Fatal;
+ }
+
+ vlc_preparser_req *req = vlc_preparser_Push(preparser,
+ inputItem.get(), options,
+ &cbs, &ctx);
+ if (req == nullptr)
{
- auto res = m_cond.timedwait( m_mutex, deadline );
- if ( res != 0 )
- {
- msg_Dbg( m_obj, "Timed out while extracting %s metadata",
- item.mrl().c_str() );
- break;
- }
+ m_currentCtx = nullptr;
+ return medialibrary::parser::Status::Fatal;
}
+ while (ctx.done == false)
+ m_cond.wait(m_mutex);
m_currentCtx = nullptr;
}
- if ( !ctx.success || ctx.inputParser == nullptr )
- return medialibrary::parser::Status::Fatal;
-
- if ( item.fileType() == medialibrary::IFile::Type::Playlist &&
- item.nbLinkedItems() == 0 )
- return medialibrary::parser::Status::Fatal;
-
- populateItem( item, ctx.inputItem.get() );
-
- return medialibrary::parser::Status::Success;
+ return ctx.status;
}
const char* MetadataExtractor::name() const
@@ -304,7 +293,18 @@ void MetadataExtractor::onRestarted()
void MetadataExtractor::stop()
{
- vlc::threads::mutex_locker lock{ m_mutex };
- if ( m_currentCtx != nullptr )
- input_item_parser_id_Interrupt( m_currentCtx->inputParser.get() );
+ vlc_preparser_t *preparser = m_parser.get();
+ if (preparser == nullptr) {
+ return;
+ }
+
+ /* vlc_preparser_Cancel can call the callback from this thread so the mutex
+ * must be unlock */
+ vlc_preparser_Cancel(preparser, nullptr);
+
+ vlc::threads::mutex_locker lock(m_mutex);
+ if (m_currentCtx != nullptr) {
+ while (m_currentCtx != nullptr && m_currentCtx->done == false)
+ m_cond.wait(m_mutex);
+ }
}
=====================================
modules/misc/medialibrary/Thumbnailer.cpp
=====================================
@@ -34,19 +34,14 @@
#include <stdexcept>
Thumbnailer::Thumbnailer( vlc_medialibrary_module_t* ml )
- : m_currentContext( nullptr )
- , m_thumbnailer( nullptr, &vlc_preparser_Delete )
+ : m_currentContext(nullptr)
+ , m_thumbnailer(VLC_OBJECT(ml), {
+ .types = VLC_PREPARSER_TYPE_THUMBNAIL_TO_FILES,
+ .max_parser_threads = 0,
+ .max_thumbnailer_threads = 1,
+ .timeout = VLC_TICK_FROM_SEC( 3 ),
+ })
{
- const struct vlc_preparser_cfg cfg = []{
- struct vlc_preparser_cfg cfg{};
- cfg.types = VLC_PREPARSER_TYPE_THUMBNAIL_TO_FILES;
- cfg.timeout = VLC_TICK_FROM_SEC( 3 );
- cfg.max_thumbnailer_threads = 1;
- return cfg;
- }();
- m_thumbnailer.reset( vlc_preparser_New( VLC_OBJECT( ml ), &cfg ) );
- if ( unlikely( m_thumbnailer == nullptr ) )
- throw std::runtime_error( "Failed to instantiate a vlc_preparser_t" );
}
void Thumbnailer::onThumbnailToFilesComplete(vlc_preparser_req *req, int ,
@@ -111,8 +106,13 @@ bool Thumbnailer::generate( const medialibrary::IMedia&,
const std::string& mrl,
.on_ended = onThumbnailToFilesComplete,
};
+ vlc_preparser_t *thumbnailer = m_thumbnailer.instance();
+ if (thumbnailer == nullptr) {
+ return false;
+ }
+
vlc_preparser_req *preparserReq;
- preparserReq =
vlc_preparser_GenerateThumbnailToFiles(m_thumbnailer.get(),
+ preparserReq = vlc_preparser_GenerateThumbnailToFiles(thumbnailer,
item.get(),
&thumb_arg,
&thumb_out, 1,
@@ -133,9 +133,16 @@ bool Thumbnailer::generate( const medialibrary::IMedia&,
const std::string& mrl,
void Thumbnailer::stop()
{
- vlc_preparser_Cancel(m_thumbnailer.get(), NULL);
+ vlc_preparser_t *thumbnailer = m_thumbnailer.get();
+ if (thumbnailer == nullptr) {
+ return;
+ }
+
+ /* vlc_preparser_Cancel can call the callback from this thread so the mutex
+ * must be unlock */
+ vlc_preparser_Cancel(thumbnailer, nullptr);
- vlc::threads::mutex_locker lock{ m_mutex };
+ vlc::threads::mutex_locker lock(m_mutex);
if ( m_currentContext != nullptr )
{
while (m_currentContext != nullptr && m_currentContext->done == false)
=====================================
modules/misc/medialibrary/fs/directory.cpp
=====================================
@@ -41,10 +41,6 @@
#include <system_error>
#include <vector>
-using InputItemPtr = ::vlc::vlc_shared_data_ptr<input_item_t,
- &input_item_Hold,
- &input_item_Release>;
-
namespace vlc {
namespace medialibrary {
@@ -114,7 +110,7 @@ struct metadata_request {
vlc::threads::mutex lock;
vlc::threads::condition_variable cond;
/* results */
- bool success;
+ int status;
bool probe;
std::vector<InputItemPtr> *children;
};
@@ -124,17 +120,18 @@ struct metadata_request {
extern "C" {
-static void onParserEnded( input_item_t *, int status, void *data )
+static void onParserEnded( vlc_preparser_req *preparser_req, int status, void
*data )
{
auto req = static_cast<vlc::medialibrary::metadata_request*>( data );
vlc::threads::mutex_locker lock( req->lock );
- req->success = status == VLC_SUCCESS;
+ req->status = status;
req->probe = true;
req->cond.signal();
+ vlc_preparser_req_Release(preparser_req);
}
-static void onParserSubtreeAdded( input_item_t *, input_item_node_t *subtree,
+static void onParserSubtreeAdded( vlc_preparser_req *, input_item_node_t
*subtree,
void *data )
{
auto req = static_cast<vlc::medialibrary::metadata_request*>( data );
@@ -155,44 +152,41 @@ static void onParserSubtreeAdded( input_item_t *,
input_item_node_t *subtree,
namespace vlc {
namespace medialibrary {
-static bool request_metadata_sync( libvlc_int_t *libvlc, input_item_t *media,
- std::vector<InputItemPtr> *out_children )
+bool
+SDDirectory::requestMetadataSync(input_item_t *media,
+ std::vector<InputItemPtr> *out_children) const
{
metadata_request req;
req.children = out_children;
req.probe = false;
- auto deadline = vlc_tick_now() + VLC_TICK_FROM_SEC( 15 );
- static const input_item_parser_cbs_t cbs = {
+ vlc::threads::mutex_locker lock( req.lock );
+
+ static const struct vlc_preparser_cbs cbs = {
onParserEnded,
onParserSubtreeAdded,
nullptr,
};
+ int options = VLC_PREPARSER_TYPE_PARSE | VLC_PREPARSER_OPTION_SUBITEMS;
- const struct input_item_parser_cfg cfg= {
- .cbs = &cbs,
- .cbs_data = &req,
- .subitems = true,
- .interact = false,
- };
-
- auto inputParser = vlc::wrap_cptr( input_item_Parse( VLC_OBJECT( libvlc ),
media, &cfg ),
- &input_item_parser_id_Release );
-
- if ( inputParser == nullptr )
+ vlc_preparser_t *preparser = m_fs.getPreparser().instance();
+ if (preparser == nullptr) {
return false;
-
- vlc::threads::mutex_locker lock( req.lock );
- while ( req.probe == false )
- {
- auto res = req.cond.timedwait( req.lock, deadline );
- if ( res != 0 )
- {
+ }
+ vlc_preparser_req *preparser_req = vlc_preparser_Push(preparser,
+ media, options,
+ &cbs, &req);
+ if (preparser_req == nullptr) {
+ return false;
+ }
+ while (req.probe == false) {
+ req.cond.wait(req.lock);
+ }
+ if (req.status == VLC_ETIMEOUT) {
throw medialibrary::fs::errors::System(
ETIMEDOUT, "Failed to browse directory: Operation timed out" );
- }
}
- return req.success;
+ return req.status == VLC_SUCCESS;
}
void
@@ -208,7 +202,7 @@ SDDirectory::read() const
input_item_AddOption( media.get(), "show-hiddenfiles",
VLC_INPUT_OPTION_TRUSTED );
input_item_AddOption( media.get(), "ignore-filetypes=''",
VLC_INPUT_OPTION_TRUSTED );
input_item_AddOption( media.get(), "sub-autodetect-fuzzy=2",
VLC_INPUT_OPTION_TRUSTED );
- auto status = request_metadata_sync( m_fs.libvlc(), media.get(), &children
);
+ auto status = requestMetadataSync( media.get(), &children );
if ( status == false )
throw medialibrary::fs::errors::System(
=====================================
modules/misc/medialibrary/fs/directory.h
=====================================
@@ -31,6 +31,10 @@ namespace vlc {
using namespace ::medialibrary::fs;
+using InputItemPtr = ::vlc::vlc_shared_data_ptr<input_item_t,
+ &input_item_Hold,
+ &input_item_Release>;
+
class SDDirectory : public IDirectory
{
public:
@@ -43,6 +47,7 @@ public:
bool contains( const std::string& file ) const override;
private:
+ bool requestMetadataSync(input_item_t *media, std::vector<InputItemPtr>
*out_children) const;
void read() const;
void addFile( std::string mrl, fs::IFile::LinkedFileType, std::string
linkedWith ) const;
=====================================
modules/misc/medialibrary/fs/fs.cpp
=====================================
@@ -43,6 +43,12 @@ SDFileSystemFactory::SDFileSystemFactory(vlc_object_t
*parent,
: m_parent(parent)
, m_scheme(scheme)
, m_callbacks( nullptr )
+ , m_parser(parent, {
+ .types = VLC_PREPARSER_TYPE_PARSE,
+ .max_parser_threads = 1,
+ .max_thumbnailer_threads = 0,
+ .timeout = VLC_TICK_FROM_SEC(15),
+ })
{
m_isNetwork = strncasecmp( m_scheme.c_str(), "file://",
m_scheme.length() ) != 0;
@@ -133,6 +139,13 @@ SDFileSystemFactory::libvlc() const
return vlc_object_instance(m_parent);
}
+LazyPreparser&
+SDFileSystemFactory::getPreparser()
+{
+ vlc::threads::mutex_locker lock(m_mutex);
+ return m_parser;
+}
+
void SDFileSystemFactory::onDeviceMounted(const std::string& uuid,
const std::string& mountpoint,
bool removable)
=====================================
modules/misc/medialibrary/fs/fs.h
=====================================
@@ -25,10 +25,14 @@
#include <vector>
#include <vlc_common.h>
#include <vlc_threads.h>
+#include <vlc_preparser.h>
#include <vlc_cxx_helpers.hpp>
+
#include <medialibrary/filesystem/IFileSystemFactory.h>
#include <medialibrary/IDeviceLister.h>
+#include "../LazyPreparser.h"
+
struct libvlc_int_t;
namespace medialibrary {
@@ -92,6 +96,9 @@ public:
bool
waitForDevice(const std::string& mrl, uint32_t timeout) const override;
+ LazyPreparser&
+ getPreparser();
+
private:
std::shared_ptr<fs::IDevice>
deviceByUuid(const std::string& uuid);
@@ -110,6 +117,8 @@ private:
mutable vlc::threads::condition_variable m_cond;
std::vector<std::shared_ptr<IDevice>> m_devices;
std::shared_ptr<IDeviceLister> m_deviceLister;
+
+ LazyPreparser m_parser;
};
} /* namespace medialibrary */
=====================================
modules/misc/medialibrary/medialibrary.h
=====================================
@@ -39,6 +39,8 @@
#include <cstdarg>
#include <type_traits>
+#include "LazyPreparser.h"
+
struct vlc_event_t;
struct vlc_object_t;
struct vlc_preparser_t;
@@ -65,23 +67,16 @@ private:
struct ParseContext
{
ParseContext( MetadataExtractor* mde, medialibrary::parser::IItem&
item )
- : needsProbing( false )
- , success( false )
+ : status( medialibrary::parser::Status::Fatal )
+ , done( false )
, mde( mde )
, item( item )
- , inputItem( nullptr, &input_item_Release )
- , inputParser( nullptr, &input_item_parser_id_Release )
{
}
-
- bool needsProbing;
- bool success;
+ medialibrary::parser::Status status;
+ bool done;
MetadataExtractor* mde;
medialibrary::parser::IItem& item;
- std::unique_ptr<input_item_t, decltype(&input_item_Release)> inputItem;
- // Needs to be last to be destroyed first, otherwise a late callback
- // could use some already destroyed fields
- std::unique_ptr<input_item_parser_id_t,
decltype(&input_item_parser_id_Release)> inputParser;
};
public:
@@ -99,14 +94,12 @@ private:
void onRestarted() override;
void stop() override;
- void onParserEnded( ParseContext& ctx, int status );
- void addSubtree( ParseContext& ctx, input_item_node_t *root );
void populateItem( medialibrary::parser::IItem& item, input_item_t*
inputItem );
- static void onParserEnded( input_item_t *, int status, void *user_data );
- static void onParserSubtreeAdded( input_item_t *, input_item_node_t
*subtree,
+ static void onParserEnded(vlc_preparser_req *, int status, void
*user_data);
+ static void onParserSubtreeAdded( vlc_preparser_req *, input_item_node_t
*subtree,
void *user_data );
- static void onAttachmentsAdded( input_item_t *,
+ static void onAttachmentsAdded( vlc_preparser_req *,
input_attachment_t *const *array,
size_t count, void *data );
@@ -115,6 +108,7 @@ private:
vlc::threads::mutex m_mutex;
ParseContext* m_currentCtx;
vlc_object_t* m_obj;
+ LazyPreparser m_parser;
};
class Thumbnailer : public medialibrary::IThumbnailer
@@ -142,7 +136,7 @@ private:
vlc::threads::mutex m_mutex;
vlc::threads::condition_variable m_cond;
ThumbnailerCtx* m_currentContext;
- std::unique_ptr<vlc_preparser_t, void(*)(vlc_preparser_t*)> m_thumbnailer;
+ LazyPreparser m_thumbnailer;
};
class MediaLibrary : public medialibrary::IMediaLibraryCb
View it on GitLab:
https://code.videolan.org/videolan/vlc/-/compare/2ef92b2aefcab1b4aa14a8a54d7321b6e2ac60ac...8a18772682564dc9ae038ba6adb522ee4752c888
--
View it on GitLab:
https://code.videolan.org/videolan/vlc/-/compare/2ef92b2aefcab1b4aa14a8a54d7321b6e2ac60ac...8a18772682564dc9ae038ba6adb522ee4752c888
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance_______________________________________________
vlc-commits mailing list
[email protected]
https://mailman.videolan.org/listinfo/vlc-commits