diff --git a/include/medialibrary/filesystem/IFile.h b/include/medialibrary/filesystem/IFile.h index 1bd6511d6cb705e610a1c7dc3d386f1d4ca134ee..bf95e70f0480f5954d3617d5a575b46fc60b183a 100644 --- a/include/medialibrary/filesystem/IFile.h +++ b/include/medialibrary/filesystem/IFile.h @@ -24,6 +24,7 @@ #include #include +#include "medialibrary/IFile.h" namespace medialibrary { @@ -33,15 +34,6 @@ namespace fs class IFile { public: - enum class LinkedFileType : uint8_t - { - ///< This file is not a linked file - None, - ///< This is a linked subtitle file - Subtitles, - ///< This is a linked soundtrack file - SoundTrack, - }; virtual ~IFile() = default; /// Returns the URL encoded filename, including the extension virtual const std::string& name() const = 0; @@ -52,14 +44,20 @@ namespace fs virtual uint64_t size() const = 0; virtual bool isNetwork() const = 0; /** - * @brief type Returns the file type, or None if not linked with another file + * @brief type Returns the file type if it is known + * + * Otherwise this will default to medialibrary::IFile::Type::Unknown + */ + virtual medialibrary::IFile::Type type() const = 0; + /** + * @brief isLinked Return true if this file is linked with another */ - virtual LinkedFileType linkedType() const = 0; + virtual bool isLinked() const = 0; /** * @brief linkedWith Return the MRL this file is linked to, or an empty * string if it's not linked with anything * - * If type() is None, it's invalid to call this function + * If isLinked() is false, it's invalid to call this function */ virtual const std::string& linkedWith() const = 0; }; diff --git a/include/medialibrary/parser/IItem.h b/include/medialibrary/parser/IItem.h index 8d020b7441d2ce216dd3c35db90b3511a5465cd1..60c62801b8150d8acfd2fe15662dc60eff809bbe 100644 --- a/include/medialibrary/parser/IItem.h +++ b/include/medialibrary/parser/IItem.h @@ -147,6 +147,12 @@ public: */ virtual IFile::Type fileType() const = 0; + /** + * @brief setFileType Updates the analyzed file type + * @return true in case of success or no-op, false in case of failure + */ + virtual bool setFileType( IFile::Type fileType ) = 0; + /** * @return The number of linked items for this item. */ diff --git a/src/File.cpp b/src/File.cpp index 19b6e7d30c82960bd073f0c90139ea8e90ce2633..42a66925aa6b00777c7fb4492ad37a7d95a92dcb 100644 --- a/src/File.cpp +++ b/src/File.cpp @@ -210,7 +210,8 @@ bool File::isNetwork() const bool File::isMain() const { return m_type == Type::Main || - m_type == Type::Cache; + m_type == Type::Cache || + m_type == Type::Disc; } time_t File::insertionDate() const diff --git a/src/discoverer/FsDiscoverer.cpp b/src/discoverer/FsDiscoverer.cpp index 7b50b330c6865f8a95b7772897178a4d4cb259d9..832c7e69838628f6974f085f0ee1b48066b4fdb7 100644 --- a/src/discoverer/FsDiscoverer.cpp +++ b/src/discoverer/FsDiscoverer.cpp @@ -462,34 +462,33 @@ void FsDiscoverer::checkFiles( std::shared_ptr parentFolderFs, }); if ( it == end( files ) ) { - if ( fileFs->linkedType() == fs::IFile::LinkedFileType::None ) + if ( fileFs->isLinked() == false ) { - const auto ext = fileFs->extension(); - IFile::Type fileType = IFile::Type::Unknown; - if ( m_ml->isMediaExtensionSupported( ext.c_str() ) == true ) - fileType = IFile::Type::Main; - else if ( m_ml->isPlaylistExtensionSupported( ext.c_str() ) == true ) - fileType = IFile::Type::Playlist; + IFile::Type fileType = fileFs->type(); + /* + * If the file type is known, then use it. VLC's demuxers know + * better than we do based on the extension only. + * However at this point, most files are the subitems of a "media" + * and are not preparsed, so we usually only know if they are a + * folder or a file, at least for now + */ + if ( fileType == IFile::Type::Unknown ) + { + const auto ext = fileFs->extension(); + if ( m_ml->isMediaExtensionSupported( ext.c_str() ) == true ) + fileType = IFile::Type::Main; + else if ( m_ml->isPlaylistExtensionSupported( ext.c_str() ) == true ) + fileType = IFile::Type::Playlist; + } if ( fileType != IFile::Type::Unknown ) filesToAdd.emplace_back( fileFs, fileType ); } else { - auto fileType = IFile::Type::Unknown; - switch ( fileFs->linkedType() ) - { - case fs::IFile::LinkedFileType::None: - assert( !"The linked file type can't be none" ); - break; - case fs::IFile::LinkedFileType::Subtitles: - fileType = IFile::Type::Subtitles; - break; - case fs::IFile::LinkedFileType::SoundTrack: - fileType = IFile::Type::Soundtrack; - break; - } - if ( fileType != IFile::Type::Unknown ) - linkedFilesToAdd.emplace_back( fileFs, fileType ); + assert( fileFs->type() == IFile::Type::Subtitles || + fileFs->type() == IFile::Type::Soundtrack ); + auto linkedType = fileFs->type(); + linkedFilesToAdd.emplace_back( fileFs, linkedType ); } continue; } diff --git a/src/filesystem/common/CommonFile.cpp b/src/filesystem/common/CommonFile.cpp index 2df6fca61e19fb968be5c63d141114759e9f51a0..7fcac77b0bb9cf396f5812a3ccf122b777726e1e 100644 --- a/src/filesystem/common/CommonFile.cpp +++ b/src/filesystem/common/CommonFile.cpp @@ -35,23 +35,14 @@ namespace medialibrary namespace fs { -CommonFile::CommonFile( std::string mrl ) - : m_mrl( std::move( mrl ) ) - , m_name( utils::file::fileName( m_mrl ) ) - , m_extension( utils::file::extension( m_mrl ) ) - , m_linkedType( LinkedFileType::None ) -{ -} - -CommonFile::CommonFile( std::string mrl, IFile::LinkedFileType linkedType, +CommonFile::CommonFile( std::string mrl, medialibrary::IFile::Type type, std::string linkedFile ) : m_mrl( std::move( mrl ) ) , m_name( utils::file::fileName( m_mrl ) ) , m_extension( utils::file::extension( m_mrl ) ) + , m_type( type ) , m_linkedFile( std::move( linkedFile ) ) - , m_linkedType( linkedType ) { - } const std::string& CommonFile::name() const @@ -74,14 +65,19 @@ bool CommonFile::isNetwork() const return false; } -IFile::LinkedFileType CommonFile::linkedType() const +medialibrary::IFile::Type CommonFile::type() const +{ + return m_type; +} + +bool CommonFile::isLinked() const { - return m_linkedType; + return m_linkedFile.empty() == false; } const std::string& CommonFile::linkedWith() const { - assert( m_linkedType != LinkedFileType::None ); + assert( isLinked() == true ); return m_linkedFile; } diff --git a/src/filesystem/common/CommonFile.h b/src/filesystem/common/CommonFile.h index ba666e27429d27b947f3a2deb034d8f38bbe4b90..a147045519c68fd0b58977dd12cf8fadca635ab6 100644 --- a/src/filesystem/common/CommonFile.h +++ b/src/filesystem/common/CommonFile.h @@ -33,22 +33,22 @@ namespace fs class CommonFile : public IFile { public: - explicit CommonFile( std::string mrl ); - explicit CommonFile( std::string mrl, LinkedFileType linkedType, + explicit CommonFile( std::string mrl, medialibrary::IFile::Type linkedType, std::string linkedFile ); virtual const std::string& name() const override; virtual const std::string& extension() const override; virtual const std::string& mrl() const override; virtual bool isNetwork() const override; - virtual LinkedFileType linkedType() const override; + virtual medialibrary::IFile::Type type() const override; + virtual bool isLinked() const override; virtual const std::string& linkedWith() const override; protected: const std::string m_mrl; const std::string m_name; const std::string m_extension; + const medialibrary::IFile::Type m_type; const std::string m_linkedFile; - const LinkedFileType m_linkedType; }; } diff --git a/src/filesystem/libvlc/Directory.cpp b/src/filesystem/libvlc/Directory.cpp index 17594ab7b62c77ca19fa60c3254ce6d9a20bbf7d..1d8219a0ba5d94d6ec6fc00daa0f961d5a52fd26 100644 --- a/src/filesystem/libvlc/Directory.cpp +++ b/src/filesystem/libvlc/Directory.cpp @@ -116,6 +116,20 @@ void Directory::read() const if ( res == VLC::Media::ParsedStatus::Failed ) throw errors::System{ EIO, "Failed to browse network directory: Unknown error" }; + if ( media.type() == VLC::Media::Type::Disc ) + { + int64_t fileSize = 0; + int64_t fileMtime = 0; +#if LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0) + auto fileSizeTpl = media.fileStat( VLC::Media::FileStat::Size ); + auto fileMtimeTpl = media.fileStat( VLC::Media::FileStat::Mtime ); + fileSize = std::get<1>( fileSizeTpl ); + fileMtime = std::get<1>( fileMtimeTpl ); +#endif + addFile( media.mrl(), medialibrary::IFile::Type::Disc, {}, fileMtime, + fileSize ); + return; + } auto subItems = media.subitems(); for ( auto i = 0; i < subItems->count(); ++i ) { @@ -146,16 +160,29 @@ void Directory::read() const fileSize = std::get<1>( fileSizeTpl ); fileMtime = std::get<1>( fileMtimeTpl ); #endif - addFile( m->mrl(), IFile::LinkedFileType::None, {}, fileMtime, fileSize ); + medialibrary::IFile::Type fileType; + switch ( m->type() ) + { + case VLC::Media::Type::Playlist: + fileType = medialibrary::IFile::Type::Playlist; + break; + case VLC::Media::Type::Disc: + fileType = medialibrary::IFile::Type::Disc; + break; + default: + fileType = medialibrary::IFile::Type::Unknown; + break; + } + addFile( m->mrl(), fileType, {}, fileMtime, fileSize ); for ( const auto& am : m->slaves() ) { - IFile::LinkedFileType linkedType; + medialibrary::IFile::Type linkedType; if ( am.type() == VLC::MediaSlave::Type::Audio ) - linkedType = IFile::LinkedFileType::SoundTrack; + linkedType = medialibrary::IFile::Type::Soundtrack; else { assert( am.type() == VLC::MediaSlave::Type::Subtitle ); - linkedType = IFile::LinkedFileType::Subtitles; + linkedType = medialibrary::IFile::Type::Subtitles; } addFile( am.uri(), linkedType, m->mrl(), 0, 0 ); } @@ -163,7 +190,7 @@ void Directory::read() const } } -void Directory::addFile( std::string mrl, fs::IFile::LinkedFileType linkedType, +void Directory::addFile( std::string mrl, medialibrary::IFile::Type fileType, std::string linkedWith, time_t lastModificationDate, uint64_t fileSize ) const { @@ -214,13 +241,9 @@ void Directory::addFile( std::string mrl, fs::IFile::LinkedFileType linkedType, fileSize = s.st_size; #endif } - if ( linkedType == IFile::LinkedFileType::None ) - m_files.push_back( std::make_shared( std::move( mrl ), - m_fsFactory, lastModificationDate, fileSize ) ); - else - m_files.push_back( std::make_shared( std::move( mrl ), - m_fsFactory, lastModificationDate, fileSize, - linkedType, std::move( linkedWith ) ) ); + m_files.push_back( std::make_shared( std::move( mrl ), + m_fsFactory, fileType, lastModificationDate, fileSize, + std::move( linkedWith ) ) ); } } diff --git a/src/filesystem/libvlc/Directory.h b/src/filesystem/libvlc/Directory.h index da893cc076ef839ad78a8be37886ea33058afe5a..49ee08297a6d088dbaa04b1f8ed4c5782082974a 100644 --- a/src/filesystem/libvlc/Directory.h +++ b/src/filesystem/libvlc/Directory.h @@ -42,7 +42,7 @@ private: // These are unintuitively const, because the files/subfolders list is // lazily initialized when calling files() / dirs() virtual void read() const override; - void addFile( std::string mrl, IFile::LinkedFileType fileType, + void addFile( std::string mrl, medialibrary::IFile::Type fileType, std::string linkedWith, time_t lastModificationDate, uint64_t fileSize ) const; diff --git a/src/filesystem/libvlc/File.cpp b/src/filesystem/libvlc/File.cpp index c6c044fcf37e52835f43f569ed7379a44a687744..a8322b9efaaf936617268d15693a8b314c1e088f 100644 --- a/src/filesystem/libvlc/File.cpp +++ b/src/filesystem/libvlc/File.cpp @@ -40,20 +40,10 @@ namespace fs namespace libvlc { -File::File( std::string mrl, fs::IFileSystemFactory& fsFactory, - time_t lastModificationDate, uint64_t size ) - : CommonFile( std::move( mrl ) ) - , m_lastModificationDate( lastModificationDate ) - , m_size( size ) - , m_isNetwork( fsFactory.isNetworkFileSystem() ) -{ - -} - File::File( std::string mrl, IFileSystemFactory& fsFactory, - time_t lastModificationDate, uint64_t size, - IFile::LinkedFileType linkedType, std::string linkedFile ) - : CommonFile( std::move( mrl ), linkedType, std::move( linkedFile ) ) + medialibrary::IFile::Type type, time_t lastModificationDate, + uint64_t size, std::string linkedFile ) + : CommonFile( std::move( mrl ), type, std::move( linkedFile ) ) , m_lastModificationDate( lastModificationDate ) , m_size( size ) , m_isNetwork( fsFactory.isNetworkFileSystem() ) diff --git a/src/filesystem/libvlc/File.h b/src/filesystem/libvlc/File.h index 3fcda41ab3dff22ef1ba9ecbba72aaa462fc78b8..1bea4e005c07d8a141f2f7cdac9ff4a910f7b91c 100644 --- a/src/filesystem/libvlc/File.h +++ b/src/filesystem/libvlc/File.h @@ -38,10 +38,8 @@ class File : public CommonFile { public: File( std::string mrl, IFileSystemFactory& fsFactory, - time_t lastModificationDate, uint64_t size ); - File( std::string mrl, IFileSystemFactory& fsFactory, - time_t lastModificationDate, uint64_t size, - IFile::LinkedFileType linkedType, std::string linkedFile ); + medialibrary::IFile::Type linkedType, time_t lastModificationDate, + uint64_t size, std::string linkedFile ); virtual time_t lastModificationDate() const override; virtual uint64_t size() const override; virtual bool isNetwork() const override; diff --git a/src/metadata_services/MetadataParser.cpp b/src/metadata_services/MetadataParser.cpp index 0c9b336b269d137fa60f17cb60c7f982b1f8a13a..989b73d86851bad5dedfb0a5611d247483f4332f 100644 --- a/src/metadata_services/MetadataParser.cpp +++ b/src/metadata_services/MetadataParser.cpp @@ -624,6 +624,12 @@ bool MetadataAnalyzer::parseVideoFile( IItem& item ) const // with a video file, we might be refreshing that media, and it might already // have a title, so let's analyse the filename again instead. auto title = utils::title::sanitize( item.media()->fileName() ); + if ( title.empty() == true && item.fileType() == IFile::Type::Disc ) + { + title = utils::title::sanitize( + utils::file::directoryName( + utils::url::decode( item.mrl() ) ) ); + } auto showInfo = utils::title::analyze( title ); const auto& embeddedThumbnails = item.embeddedThumbnails(); @@ -674,6 +680,9 @@ bool MetadataAnalyzer::parseVideoFile( IItem& item ) const IMedia::Type MetadataAnalyzer::guessMediaType( const IItem &item ) const { + if ( item.fileType() == IFile::Type::Disc ) + return IMedia::Type::Video; + auto ext = utils::file::extension( item.mrl() ); std::transform(cbegin( ext ), cend( ext ), begin( ext ), [](unsigned char c){ return tolower(c); } ); @@ -765,11 +774,12 @@ Status MetadataAnalyzer::createFileAndMedia( IItem& item ) const auto deviceFs = item.parentFolderFs()->device(); if ( deviceFs == nullptr ) throw fs::errors::DeviceRemoved{}; - // For now, assume all media are made of a single file try { + assert( item.fileType() == IFile::Type::Main || + item.fileType() == IFile::Type::Disc ); file = m->addFile( *item.fileFs(), item.parentFolder()->id(), - deviceFs->isRemovable(), File::Type::Main ); + deviceFs->isRemovable(), item.fileType() ); if ( file == nullptr ) { LOG_ERROR( "Failed to add file ", mrl, " to media #", m->id() ); diff --git a/src/metadata_services/vlc/VLCMetadataService.cpp b/src/metadata_services/vlc/VLCMetadataService.cpp index de7c24b63f598fc66b7108596e0b285afef6cbd5..05f15f957435d3eee510c51360137c7d62ae1880 100644 --- a/src/metadata_services/vlc/VLCMetadataService.cpp +++ b/src/metadata_services/vlc/VLCMetadataService.cpp @@ -51,142 +51,173 @@ namespace parser bool VLCMetadataService::initialize( IMediaLibrary* ) { - return true; + return true; } #if defined(FORCE_ATTACHMENTS_API) void VLCMetadataService::onAttachedThumbnailsFound( const libvlc_event_t* e, void* data ) { - assert( e->type == libvlc_MediaAttachedThumbnailsFound ); - auto item = reinterpret_cast( data ); - auto pictureList = e->u.media_attached_thumbnails_found.thumbnails; - if ( libvlc_picture_list_count( pictureList ) == 0 ) - return; - item->addEmbeddedThumbnail( std::make_shared( - libvlc_picture_list_at( pictureList, 0 ) ) ); + assert( e->type == libvlc_MediaAttachedThumbnailsFound ); + auto item = reinterpret_cast( data ); + auto pictureList = e->u.media_attached_thumbnails_found.thumbnails; + if ( libvlc_picture_list_count( pictureList ) == 0 ) + return; + item->addEmbeddedThumbnail( std::make_shared( + libvlc_picture_list_at( pictureList, 0 ) ) ); } #endif Status VLCMetadataService::run( IItem& item ) { - auto mrl = item.mrl(); + auto mrl = item.mrl(); - // Having a valid media means we're re-executing this parser after the thumbnailer, - // which isn't expected, as we always mark this task as completed. + // Having a valid media means we're re-executing this parser after the thumbnailer, + // which isn't expected, as we always mark this task as completed. #if LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0) - VLC::Media vlcMedia{ mrl, VLC::Media::FromType::FromLocation }; + VLC::Media vlcMedia{ mrl, VLC::Media::FromType::FromLocation }; #else - VLC::Media vlcMedia{ VLCInstance::get(), mrl, VLC::Media::FromType::FromLocation }; + VLC::Media vlcMedia{ VLCInstance::get(), mrl, VLC::Media::FromType::FromLocation }; #endif - vlcMedia.addOption( ":no-lua" ); - - VLC::Media::ParsedStatus status; - bool done = false; - - { - /* - * Store a copy of the event manager. When the instance falls out of - * scope, all events registered through it will be unregistered - * automatically - */ - auto em = vlcMedia.eventManager(); - em.onParsedChanged( [this, &status, &done](VLC::Media::ParsedStatus s ) { - std::lock_guard lock( m_mutex ); - status = s; - done = true; - m_cond.notify_all(); - }); + vlcMedia.addOption( ":no-lua" ); + + VLC::Media::ParsedStatus status; + bool done = false; + + { + /* + * Store a copy of the event manager. When the instance falls out of + * scope, all events registered through it will be unregistered + * automatically + */ + auto em = vlcMedia.eventManager(); + em.onParsedChanged( [this, &status, &done](VLC::Media::ParsedStatus s ) { + std::lock_guard lock( m_mutex ); + status = s; + done = true; + m_cond.notify_all(); + }); #if LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0) - em.onAttachedThumbnailsFound( [&item]( const std::vector& pics ) { - if ( pics.empty() == false ) - { - item.addEmbeddedThumbnail( - std::make_shared( pics[0] ) ); - } - }); + em.onAttachedThumbnailsFound( [&item]( const std::vector& pics ) { + if ( pics.empty() == false ) + { + item.addEmbeddedThumbnail( + std::make_shared( pics[0] ) ); + } + }); #elif defined(FORCE_ATTACHMENTS_API) - /* - * We can force the use of the 4.0 attachment API if the patches have - * been backported to 3.0, however libvlcpp doesn't expose it so we have - * to fallback to the C API - */ - if ( libvlc_event_attach( em, libvlc_MediaAttachedThumbnailsFound, - &onAttachedThumbnailsFound, &item ) != 0 ) - { - LOG_ERROR( "Failed to attach to thumbnail found event" ); - return Status::Fatal; - } - auto unregister = utils::make_defer([&em, &item]() { - libvlc_event_detach( em, libvlc_MediaAttachedThumbnailsFound, - &onAttachedThumbnailsFound, &item ); - }); + /* + * We can force the use of the 4.0 attachment API if the patches have + * been backported to 3.0, however libvlcpp doesn't expose it so we have + * to fallback to the C API + */ + if ( libvlc_event_attach( em, libvlc_MediaAttachedThumbnailsFound, + &onAttachedThumbnailsFound, &item ) != 0 ) + { + LOG_ERROR( "Failed to attach to thumbnail found event" ); + return Status::Fatal; + } + auto unregister = utils::make_defer([&em, &item]() { + libvlc_event_detach( em, libvlc_MediaAttachedThumbnailsFound, + &onAttachedThumbnailsFound, &item ); + }); #endif - { - // We need m_currentMedia to be updated from a locked context - // but we also need parseWithOption to be called with the lock - // unlocked, to avoid a potential lock inversion with VLC's internal - // mutexes. - std::unique_lock lock( m_mutex ); - m_currentMedia = vlcMedia; - } + { + // We need m_currentMedia to be updated from a locked context + // but we also need parseWithOption to be called with the lock + // unlocked, to avoid a potential lock inversion with VLC's internal + // mutexes. + std::unique_lock lock( m_mutex ); + m_currentMedia = vlcMedia; + } #if LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0) - if ( vlcMedia.parseRequest( VLCInstance::get(), - VLC::Media::ParseFlags::Local | - VLC::Media::ParseFlags::Network, 5000 ) == false ) + if ( vlcMedia.parseRequest( VLCInstance::get(), + VLC::Media::ParseFlags::Local | + VLC::Media::ParseFlags::Network, 5000 ) == false ) #else - if ( vlcMedia.parseWithOptions( VLC::Media::ParseFlags::Local | - VLC::Media::ParseFlags::Network, 5000 ) == false ) + if ( vlcMedia.parseWithOptions( VLC::Media::ParseFlags::Local | + VLC::Media::ParseFlags::Network, 5000 ) == false ) #endif - { - std::unique_lock lock( m_mutex ); - m_currentMedia = VLC::Media{}; - return Status::Fatal; - } - std::unique_lock lock( m_mutex ); - m_cond.wait( lock, [&done]() { - return done == true; - }); - m_currentMedia = VLC::Media{}; - } - if ( status == VLC::Media::ParsedStatus::Failed || status == VLC::Media::ParsedStatus::Timeout ) - return Status::Fatal; - if ( ( item.fileType() == IFile::Type::Playlist || - item.fileType() == IFile::Type::Subscription ) && - vlcMedia.subitems()->count() == 0 ) - { - LOG_DEBUG( "Discarding ", - ( item.fileType() == IFile::Type::Playlist ? "playlist file" - : "subscription" ), - " with no subitem: ", mrl ); - return Status::Fatal; - } + { + std::unique_lock lock( m_mutex ); + m_currentMedia = VLC::Media{}; + return Status::Fatal; + } + std::unique_lock lock( m_mutex ); + m_cond.wait( lock, [&done]() { + return done == true; + }); + m_currentMedia = VLC::Media{}; + } + if ( status == VLC::Media::ParsedStatus::Failed || status == VLC::Media::ParsedStatus::Timeout ) + return Status::Fatal; + +#if LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0) + /* The type will always be "File" for any input when using VLC 3.x */ + auto fileType = mediaTypeToFileType( vlcMedia.type() ); + /* + * Most of the file type detection will happen when we really preparse + * the file using libvlc. We want to ensure to have that type stored + * in database later on since the Task filetype is usually the source + * of truth. + * However in the case of a linking task, a regular "Main" file can be + * forced to Soundtrack or another similar conversion. In which case, we + * want to preserve that information. + */ + if ( item.linkType() == IItem::LinkType::NoLink && + fileType != item.fileType() && + /* + * libvlc doesn't differenciate subscriptions & playlists so we need to + * avoid forcing a type change from subscription to playlist here + */ + ( fileType != IFile::Type::Playlist || + item.fileType() != IFile::Type::Subscription ) ) + { + LOG_DEBUG( "New filetype detected, converting ", mrl, " task from type ", + static_cast>( item.fileType() ), + " to type ", + static_cast>( fileType ) ); + if ( item.setFileType( fileType ) == false ) + return Status::Fatal; + } +#endif + + if ( ( item.fileType() == IFile::Type::Playlist || + item.fileType() == IFile::Type::Subscription ) && + vlcMedia.subitems()->count() == 0 ) + { + LOG_DEBUG( "Discarding ", + ( item.fileType() == IFile::Type::Playlist ? "playlist file" + : "subscription" ), + " with no subitem: ", mrl ); + return Status::Fatal; + } #if LIBVLC_VERSION_INT < LIBVLC_VERSION(4, 0, 0, 0) && !defined(FORCE_ATTACHMENTS_API) - auto artworkMrl = vlcMedia.meta( libvlc_meta_ArtworkURL ); - if ( artworkMrl.empty() == false ) - { - if ( utils::url::schemeIs( "attachment://", artworkMrl ) == true ) - { - LOG_WARN( "Artwork for ", mrl, " is an attachment. Falling back to playback" ); - VLC::MediaPlayer mp( vlcMedia ); - auto res = MetadataCommon::startPlayback( vlcMedia, mp, m_mutex, m_cond ); - if ( res == false ) - return Status::Fatal; - } - item.addEmbeddedThumbnail( std::make_shared( - utils::url::toLocalPath( vlcMedia.meta( libvlc_meta_ArtworkURL ) ) ) ); - } + auto artworkMrl = vlcMedia.meta( libvlc_meta_ArtworkURL ); + if ( artworkMrl.empty() == false ) + { + if ( utils::url::schemeIs( "attachment://", artworkMrl ) == true ) + { + LOG_WARN( "Artwork for ", mrl, " is an attachment. Falling back to playback" ); + VLC::MediaPlayer mp( vlcMedia ); + auto res = MetadataCommon::startPlayback( vlcMedia, mp, m_mutex, m_cond ); + if ( res == false ) + return Status::Fatal; + } + item.addEmbeddedThumbnail( std::make_shared( + utils::url::toLocalPath( vlcMedia.meta( libvlc_meta_ArtworkURL ) ) ) ); + } #endif - mediaToItem( vlcMedia, item ); - return Status::Success; + mediaToItem( vlcMedia, item ); + return Status::Success; } const char* VLCMetadataService::name() const { - return "VLC"; + return "VLC"; } void VLCMetadataService::onFlushing() @@ -199,233 +230,253 @@ void VLCMetadataService::onRestarted() Step VLCMetadataService::targetedStep() const { - return Step::MetadataExtraction; + return Step::MetadataExtraction; } void VLCMetadataService::stop() { - std::lock_guard lock{ m_mutex }; - if ( m_currentMedia.isValid() ) - { + std::lock_guard lock{ m_mutex }; + if ( m_currentMedia.isValid() ) + { #if LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0) - m_currentMedia.parseStop( VLCInstance::get() ); + m_currentMedia.parseStop( VLCInstance::get() ); #else - m_currentMedia.parseStop(); + m_currentMedia.parseStop(); #endif - } + } } void VLCMetadataService::mediaToItem( VLC::Media& media, IItem& item ) { - item.setMeta( IItem::Metadata::Title, - media.meta( libvlc_meta_Title ) ); - item.setMeta( IItem::Metadata::ArtworkUrl, - media.meta( libvlc_meta_ArtworkURL ) ); - item.setMeta( IItem::Metadata::ShowName, - media.meta( libvlc_meta_ShowName ) ); - item.setMeta( IItem::Metadata::Episode, - media.meta( libvlc_meta_Episode ) ); - item.setMeta( IItem::Metadata::Album, - media.meta( libvlc_meta_Album ) ); - item.setMeta( IItem::Metadata::Genre, - media.meta( libvlc_meta_Genre ) ); - item.setMeta( IItem::Metadata::Date, - media.meta( libvlc_meta_Date ) ); - item.setMeta( IItem::Metadata::AlbumArtist, - media.meta( libvlc_meta_AlbumArtist ) ); - item.setMeta( IItem::Metadata::Artist, - media.meta( libvlc_meta_Artist ) ); - item.setMeta( IItem::Metadata::TrackNumber, - media.meta( libvlc_meta_TrackNumber ) ); - item.setMeta( IItem::Metadata::DiscNumber, - media.meta( libvlc_meta_DiscNumber ) ); - item.setMeta( IItem::Metadata::DiscTotal, - media.meta( libvlc_meta_DiscTotal ) ); - item.setMeta( IItem::Metadata::Description, - media.meta( libvlc_meta_Description ) ); - item.setDuration( media.duration() ); + item.setMeta( IItem::Metadata::Title, + media.meta( libvlc_meta_Title ) ); + item.setMeta( IItem::Metadata::ArtworkUrl, + media.meta( libvlc_meta_ArtworkURL ) ); + item.setMeta( IItem::Metadata::ShowName, + media.meta( libvlc_meta_ShowName ) ); + item.setMeta( IItem::Metadata::Episode, + media.meta( libvlc_meta_Episode ) ); + item.setMeta( IItem::Metadata::Album, + media.meta( libvlc_meta_Album ) ); + item.setMeta( IItem::Metadata::Genre, + media.meta( libvlc_meta_Genre ) ); + item.setMeta( IItem::Metadata::Date, + media.meta( libvlc_meta_Date ) ); + item.setMeta( IItem::Metadata::AlbumArtist, + media.meta( libvlc_meta_AlbumArtist ) ); + item.setMeta( IItem::Metadata::Artist, + media.meta( libvlc_meta_Artist ) ); + item.setMeta( IItem::Metadata::TrackNumber, + media.meta( libvlc_meta_TrackNumber ) ); + item.setMeta( IItem::Metadata::DiscNumber, + media.meta( libvlc_meta_DiscNumber ) ); + item.setMeta( IItem::Metadata::DiscTotal, + media.meta( libvlc_meta_DiscTotal ) ); + item.setMeta( IItem::Metadata::Description, + media.meta( libvlc_meta_Description ) ); + item.setDuration( media.duration() ); #if LIBVLC_VERSION_INT < LIBVLC_VERSION(4, 0, 0, 0) - auto tracks = media.tracks(); + auto tracks = media.tracks(); #else - std::vector tracks; - auto trackList = media.tracks( VLC::MediaTrack::Type::Audio ); - std::move( begin( trackList ), end( trackList ), std::back_inserter( tracks ) ); - trackList = media.tracks( VLC::MediaTrack::Type::Video ); - std::move( begin( trackList ), end( trackList ), std::back_inserter( tracks ) ); - trackList = media.tracks( VLC::MediaTrack::Type::Subtitle ); - std::move( begin( trackList ), end( trackList ), std::back_inserter( tracks ) ); + std::vector tracks; + auto trackList = media.tracks( VLC::MediaTrack::Type::Audio ); + std::move( begin( trackList ), end( trackList ), std::back_inserter( tracks ) ); + trackList = media.tracks( VLC::MediaTrack::Type::Video ); + std::move( begin( trackList ), end( trackList ), std::back_inserter( tracks ) ); + trackList = media.tracks( VLC::MediaTrack::Type::Subtitle ); + std::move( begin( trackList ), end( trackList ), std::back_inserter( tracks ) ); #endif - for ( const auto& track : tracks ) - { - IItem::Track t; - - if ( track.type() == VLC::MediaTrack::Type::Audio ) - { - t.type = IItem::Track::Type::Audio; - t.u.a.nbChannels = track.channels(); - t.u.a.rate = track.rate(); - } - else if ( track.type() == VLC::MediaTrack::Type::Video ) - { - t.type = IItem::Track::Type::Video; - t.u.v.fpsNum = track.fpsNum(); - t.u.v.fpsDen = track.fpsDen(); - t.u.v.width = track.width(); - t.u.v.height = track.height(); - t.u.v.sarNum = track.sarNum(); - t.u.v.sarDen = track.sarDen(); - } - else if ( track.type() == VLC::MediaTrack::Type::Subtitle ) - { - t.type = IItem::Track::Type::Subtitle; - t.language = track.language(); - t.description = track.description(); - const auto& enc = track.encoding(); - strncpy( t.u.s.encoding, enc.c_str(), sizeof(t.u.s.encoding) - 1 ); - } - else - continue; - auto codec = track.codec(); - std::string fcc( reinterpret_cast( &codec ), 4 ); - t.codec = std::move( fcc ); - - t.bitrate = track.bitrate(); - t.language = track.language(); - t.description = track.description(); - - item.addTrack( std::move( t ) ); - } - - auto subItems = media.subitems(); - if ( subItems != nullptr ) - { - for ( auto i = 0; i < subItems->count(); ++i ) - { - auto vlcMedia = subItems->itemAtIndex( i ); - assert( vlcMedia != nullptr ); - auto mrl = vlcMedia->mrl(); - mrl = utils::url::encode( utils::url::decode( mrl ) ); - IItem& subItem = item.createLinkedItem( mrl, - IFile::Type::Main, i ); - mediaToItem( *vlcMedia, subItem ); - } - } + for ( const auto& track : tracks ) + { + IItem::Track t; + + if ( track.type() == VLC::MediaTrack::Type::Audio ) + { + t.type = IItem::Track::Type::Audio; + t.u.a.nbChannels = track.channels(); + t.u.a.rate = track.rate(); + } + else if ( track.type() == VLC::MediaTrack::Type::Video ) + { + t.type = IItem::Track::Type::Video; + t.u.v.fpsNum = track.fpsNum(); + t.u.v.fpsDen = track.fpsDen(); + t.u.v.width = track.width(); + t.u.v.height = track.height(); + t.u.v.sarNum = track.sarNum(); + t.u.v.sarDen = track.sarDen(); + } + else if ( track.type() == VLC::MediaTrack::Type::Subtitle ) + { + t.type = IItem::Track::Type::Subtitle; + t.language = track.language(); + t.description = track.description(); + const auto& enc = track.encoding(); + strncpy( t.u.s.encoding, enc.c_str(), sizeof(t.u.s.encoding) - 1 ); + } + else + continue; + auto codec = track.codec(); + std::string fcc( reinterpret_cast( &codec ), 4 ); + t.codec = std::move( fcc ); + + t.bitrate = track.bitrate(); + t.language = track.language(); + t.description = track.description(); + + item.addTrack( std::move( t ) ); + } + + auto subItems = media.subitems(); + if ( subItems != nullptr ) + { + for ( auto i = 0; i < subItems->count(); ++i ) + { + auto vlcMedia = subItems->itemAtIndex( i ); + assert( vlcMedia != nullptr ); + auto mrl = vlcMedia->mrl(); + mrl = utils::url::encode( utils::url::decode( mrl ) ); + IItem& subItem = item.createLinkedItem( mrl, + IFile::Type::Main, i ); + mediaToItem( *vlcMedia, subItem ); + } + } } #if LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0) VLCEmbeddedThumbnail4_0::VLCEmbeddedThumbnail4_0( VLC::Picture pic ) - : m_pic( std::move( pic ) ) + : m_pic( std::move( pic ) ) { } bool VLCEmbeddedThumbnail4_0::save( const std::string& path ) { - return m_pic.save( path ); + return m_pic.save( path ); } size_t VLCEmbeddedThumbnail4_0::size() const { - size_t size; - m_pic.buffer( &size ); - return size; + size_t size; + m_pic.buffer( &size ); + return size; } std::string VLCEmbeddedThumbnail4_0::hash() const { - size_t size; - auto buff = m_pic.buffer( &size ); - return utils::hash::toString( utils::hash::xxFromBuff( buff, size ) ); + size_t size; + auto buff = m_pic.buffer( &size ); + return utils::hash::toString( utils::hash::xxFromBuff( buff, size ) ); } std::string VLCEmbeddedThumbnail4_0::extension() const { - switch ( m_pic.type() ) - { - case VLC::Picture::Type::Argb: - return "bmp"; - case VLC::Picture::Type::Jpg: - return "jpg"; - case VLC::Picture::Type::Png: - return "png"; - } - assert( !"Invalid thumbnail type" ); - return {}; + switch ( m_pic.type() ) + { + case VLC::Picture::Type::Argb: + return "bmp"; + case VLC::Picture::Type::Jpg: + return "jpg"; + case VLC::Picture::Type::Png: + return "png"; + } + assert( !"Invalid thumbnail type" ); + return {}; +} + +IFile::Type VLCMetadataService::mediaTypeToFileType( VLC::Media::Type type ) +{ + switch ( type ) + { + case VLC::Media::Type::Unknown: + return IFile::Type::Unknown; + case VLC::Media::Type::File: + return IFile::Type::Main; + case VLC::Media::Type::Disc: + return IFile::Type::Disc; + case VLC::Media::Type::Playlist: + return IFile::Type::Playlist; + case VLC::Media::Type::Stream: + case VLC::Media::Type::Directory: + assert( !"Unexpected VLC media type" ); + break; + } + return IFile::Type::Unknown; } #elif defined(FORCE_ATTACHMENTS_API) VLCEmbeddedThumbnailForced::VLCEmbeddedThumbnailForced( libvlc_picture_t *pic ) - : m_pic( pic ) + : m_pic( pic ) { - libvlc_picture_retain( m_pic ); + libvlc_picture_retain( m_pic ); } VLCEmbeddedThumbnailForced::~VLCEmbeddedThumbnailForced() { - libvlc_picture_release( m_pic ); + libvlc_picture_release( m_pic ); } bool VLCEmbeddedThumbnailForced::save( const std::string& path ) { - return libvlc_picture_save( m_pic, path.c_str() ) == 0; + return libvlc_picture_save( m_pic, path.c_str() ) == 0; } size_t VLCEmbeddedThumbnailForced::size() const { - size_t size; - libvlc_picture_get_buffer( m_pic, &size ); - return size; + size_t size; + libvlc_picture_get_buffer( m_pic, &size ); + return size; } std::string VLCEmbeddedThumbnailForced::hash() const { - size_t size; - auto buff = libvlc_picture_get_buffer( m_pic, &size ); - return utils::hash::toString( utils::hash::xxFromBuff( buff, size ) ); + size_t size; + auto buff = libvlc_picture_get_buffer( m_pic, &size ); + return utils::hash::toString( utils::hash::xxFromBuff( buff, size ) ); } std::string VLCEmbeddedThumbnailForced::extension() const { - switch ( libvlc_picture_type( m_pic ) ) - { - case libvlc_picture_Argb: - return "bmp"; - case libvlc_picture_Jpg: - return "jpg"; - case libvlc_picture_Png: - return "png"; - } - assert( !"Invalid thumbnail type" ); - return nullptr; + switch ( libvlc_picture_type( m_pic ) ) + { + case libvlc_picture_Argb: + return "bmp"; + case libvlc_picture_Jpg: + return "jpg"; + case libvlc_picture_Png: + return "png"; + } + assert( !"Invalid thumbnail type" ); + return nullptr; } #else VLCEmbeddedThumbnail3_0::VLCEmbeddedThumbnail3_0( std::string path ) - : m_thumbnailPath( std::move( path ) ) + : m_thumbnailPath( std::move( path ) ) { } bool VLCEmbeddedThumbnail3_0::save( const std::string& path ) { - return utils::fs::copy( m_thumbnailPath, path ); + return utils::fs::copy( m_thumbnailPath, path ); } size_t VLCEmbeddedThumbnail3_0::size() const { - return utils::fs::fileSize( m_thumbnailPath ); + return utils::fs::fileSize( m_thumbnailPath ); } std::string VLCEmbeddedThumbnail3_0::hash() const { - return utils::hash::toString( utils::hash::xxFromFile( m_thumbnailPath ) ); + return utils::hash::toString( utils::hash::xxFromFile( m_thumbnailPath ) ); } std::string VLCEmbeddedThumbnail3_0::extension() const { - return utils::file::extension( m_thumbnailPath ); + return utils::file::extension( m_thumbnailPath ); } #endif diff --git a/src/metadata_services/vlc/VLCMetadataService.h b/src/metadata_services/vlc/VLCMetadataService.h index f30a50f34a22fdde5a692b45b073ecff194341a8..205284bd252bd4c938bc008dd2e8ad6113777272 100644 --- a/src/metadata_services/vlc/VLCMetadataService.h +++ b/src/metadata_services/vlc/VLCMetadataService.h @@ -95,6 +95,7 @@ private: virtual void stop() override; void mediaToItem( VLC::Media& media, parser::IItem& item ); + IFile::Type mediaTypeToFileType( VLC::Media::Type type ); #if defined(FORCE_ATTACHMENTS_API) static void onAttachedThumbnailsFound( const libvlc_event_t* e, void* data ); diff --git a/src/parser/Task.cpp b/src/parser/Task.cpp index 92d0e792abc0a54c781f5942997ca28ea4180ca8..6e56b679b1407a2adb06572b982099d3740caaad 100644 --- a/src/parser/Task.cpp +++ b/src/parser/Task.cpp @@ -971,6 +971,18 @@ IFile::Type Task::fileType() const return m_fileType; } +bool Task::setFileType( IFile::Type fileType ) +{ + if ( m_fileType == fileType ) + return true; + const std::string req = "UPDATE " + Table::Name + " SET file_type = ? " + "WHERE id_task = ?"; + if ( sqlite::Tools::executeUpdate( m_ml->getConn(), req, fileType, m_id ) == false ) + return false; + m_fileType = fileType; + return true; +} + size_t Task::nbLinkedItems() const { return m_linkedItems.size(); diff --git a/src/parser/Task.h b/src/parser/Task.h index 2a62245c72519bec2b34b5930500516dfac061d5..a1b1e2556aba15afdbf4be8a8c1f90c7cc91aa33 100644 --- a/src/parser/Task.h +++ b/src/parser/Task.h @@ -284,6 +284,7 @@ public: virtual const std::string& mrl() const override; virtual IFile::Type fileType() const override; + virtual bool setFileType( IFile::Type fileType ) override; virtual size_t nbLinkedItems() const override; virtual const IItem& linkedItem( unsigned int index ) const override; diff --git a/test/samples/Tester.cpp b/test/samples/Tester.cpp index 83bfadf6a2ca4c2cf251aadf17334e7dcca8e4fd..26ef1eacbe7fb2474a98a2e1ddceaf261c163242 100644 --- a/test/samples/Tester.cpp +++ b/test/samples/Tester.cpp @@ -584,6 +584,20 @@ void Tests::checkMedias(const rapidjson::Value& expectedMedias) { checkMediaFiles( media.get(), expectedMedia["files"] ); } + if ( expectedMedia.HasMember( "isDisc" ) ) + { + auto files = media->files(); + auto isDisc = false; + for ( const auto& f : files ) + { + if ( f->type() == IFile::Type::Disc ) + { + isDisc = true; + break; + } + } + ASSERT_EQ( isDisc, expectedMedia["isDisc"].GetBool() ); + } } } diff --git a/test/samples/meson.build b/test/samples/meson.build index 88cda98ff7fc34db90b6eef4b92b5a81e0ae7fb9..c5115a5b7501aeba00adfddd301aa13f3a45b16c 100644 --- a/test/samples/meson.build +++ b/test/samples/meson.build @@ -41,6 +41,8 @@ test_list = [ 'attached_subs', 'attached_audio', 'various', + 'parse_dvd', + 'parse_iso', ] if libvlc_dep.version().version_compare('>=4.0') diff --git a/test/samples/testcases/parse_dvd.json b/test/samples/testcases/parse_dvd.json new file mode 100644 index 0000000000000000000000000000000000000000..7c86e6da146904fcfa712349882f1cf16a46e07a --- /dev/null +++ b/test/samples/testcases/parse_dvd.json @@ -0,0 +1,14 @@ +{ + "input": [ + "discs/dvd_folder/" + ], + "expected": { + "nbVideos": 1, + "nbAudios": 0, + "media": [{ + "title": "dvd_folder", + "isDisc": true, + "snapshotExpected": true + }] + } +} diff --git a/test/samples/testcases/parse_iso.json b/test/samples/testcases/parse_iso.json new file mode 100644 index 0000000000000000000000000000000000000000..22d970baeaef380a56679dda5ce45d76dc1fc86e --- /dev/null +++ b/test/samples/testcases/parse_iso.json @@ -0,0 +1,14 @@ +{ + "input": [ + "discs/dvd_iso/" + ], + "expected": { + "nbVideos": 1, + "nbAudios": 0, + "media": [{ + "title": "super_dvd_iso", + "isDisc": true, + "snapshotExpected": true + }] + } +} diff --git a/test/unittest/mocks/FileSystem.h b/test/unittest/mocks/FileSystem.h index 5067050e4d5f4dc512c2af2a28865f2aadc6cdaa..58119a667f53b9867f0ca683ac07d554a080678c 100644 --- a/test/unittest/mocks/FileSystem.h +++ b/test/unittest/mocks/FileSystem.h @@ -350,9 +350,14 @@ public: return m_size; } - LinkedFileType linkedType() const + medialibrary::IFile::Type type() const { - return LinkedFileType::None; + return medialibrary::IFile::Type::Unknown; + } + + bool isLinked() const + { + return m_linkedWith.empty() == false; } const std::string& linkedWith() const diff --git a/test/unittest/mocks/filesystem/MockFile.cpp b/test/unittest/mocks/filesystem/MockFile.cpp index 6607e1d5743d6bdc82fb8106bb7bda345711ede6..06f8e6119ff8648bc6d622f1d364d21af9d9e955 100644 --- a/test/unittest/mocks/filesystem/MockFile.cpp +++ b/test/unittest/mocks/filesystem/MockFile.cpp @@ -64,9 +64,9 @@ bool File::isNetwork() const return false; } -fs::IFile::LinkedFileType File::linkedType() const +bool File::isLinked() const { - return LinkedFileType::None; + return m_linkedWith.empty() == false; } const std::string &File::linkedWith() const @@ -84,4 +84,9 @@ uint64_t File::size() const return 0; } +IFile::Type File::type() const +{ + return medialibrary::IFile::Type::Unknown; +} + } diff --git a/test/unittest/mocks/filesystem/MockFile.h b/test/unittest/mocks/filesystem/MockFile.h index 4ba913491c709bc3b603c9e4b04bdef17386f103..4b75d5018efe6a8fa86a9344b93e90568e70d351 100644 --- a/test/unittest/mocks/filesystem/MockFile.h +++ b/test/unittest/mocks/filesystem/MockFile.h @@ -41,10 +41,11 @@ public: virtual const std::string& extension() const override; virtual time_t lastModificationDate() const override; virtual uint64_t size() const override; + virtual medialibrary::IFile::Type type() const override; void markAsModified(); virtual const std::string& mrl() const override; virtual bool isNetwork() const override; - virtual LinkedFileType linkedType() const override; + virtual bool isLinked() const override; virtual const std::string& linkedWith() const override; private: