Merge branch 'develop' for release 3.9.0

This commit is contained in:
emeric
2020-03-30 22:44:03 +02:00
33 changed files with 506 additions and 210 deletions
+4 -4
View File
@@ -9,17 +9,17 @@ matrix:
sources: sources:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
packages: packages:
- g++-8 - g++-9
env: env:
- MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" - MATRIX_EVAL="CC=gcc-9 && CXX=g++-9"
compiler: compiler:
- gcc - gcc
before_install: before_install:
- eval "${MATRIX_EVAL}" - eval "${MATRIX_EVAL}"
- sudo apt-get install build-essential cmake libboost-all-dev libconfig++-dev libavcodec-dev libavutil-dev libavformat-dev ffmpeg libmagick++-dev libpstreams-dev libconfig++-dev libpstreams-dev libtag1-dev - sudo apt-get install build-essential cmake libboost-all-dev libconfig++-dev libavcodec-dev libavutil-dev libavformat-dev ffmpeg libgraphicsmagick++1-dev libpstreams-dev libconfig++-dev libpstreams-dev libtag1-dev
- git clone https://github.com/emweb/wt.git wt - git clone https://github.com/emweb/wt.git wt
- pushd wt; - pushd wt;
- git checkout 4.1.0 - git checkout 4.2.1
- cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr && sudo make install - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr && sudo make install
- popd - popd
script: cmake -DCMAKE_BUILD_TYPE=Release . && make && make test script: cmake -DCMAKE_BUILD_TYPE=Release . && make && make test
+6 -2
View File
@@ -2,12 +2,16 @@ cmake_minimum_required(VERSION 3.10)
project(lms) project(lms)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED True)
include(CTest) include(CTest)
find_package(PkgConfig) find_package(PkgConfig REQUIRED)
pkg_check_modules(IMAGEMAGICKXX REQUIRED ImageMagick++) find_package(Filesystem REQUIRED)
pkg_check_modules(GRAPHICSMAGICKXX REQUIRED GraphicsMagick++)
add_subdirectory(src) add_subdirectory(src)
+3 -3
View File
@@ -85,7 +85,7 @@ __Notes__:
* a C++17 compiler is needed * a C++17 compiler is needed
* ffmpeg version 4 minimum is required * ffmpeg version 4 minimum is required
```sh ```sh
apt-get install g++ cmake libboost-system-dev libavutil-dev libavformat-dev libmagick++-dev libconfig++-dev libpstreams-dev ffmpeg libtag1-dev apt-get install g++ cmake libboost-system-dev libavutil-dev libavformat-dev libgraphicsmagick++1-dev libconfig++-dev libpstreams-dev ffmpeg libtag1-dev
``` ```
You also need _Wt4_, which is not packaged yet on _Debian_. See [installation instructions](https://www.webtoolkit.eu/wt/doc/reference/html/InstallationUnix.html).</br> You also need _Wt4_, which is not packaged yet on _Debian_. See [installation instructions](https://www.webtoolkit.eu/wt/doc/reference/html/InstallationUnix.html).</br>
@@ -157,7 +157,7 @@ systemctl restart lms
__Note__: don't forget to give the _lms_ user read access to the music directory you want to scan. __Note__: don't forget to give the _lms_ user read access to the music directory you want to scan.
### Configuration ### Configuration
_LMS_ uses a configuration file, installed by default in `/etc/lms.conf`. It is recommended to edit this file and change relevant settings (listen address, listen port, working directory, Subsonic API activation, ...). _LMS_ uses a configuration file, installed by default in `/etc/lms.conf`. It is recommended to edit this file and change relevant settings (listen address, listen port, working directory, Subsonic API activation, deployment path, ...).
All other settings are set using the web interface (user management, scan settings, transcode settings, ...). All other settings are set using the web interface (user management, scan settings, transcode settings, ...).
@@ -208,6 +208,6 @@ To connect to _LMS_, just open your favorite browser and go to http://localhost:
* Wt (http://www.webtoolkit.eu/) * Wt (http://www.webtoolkit.eu/)
* bootstrap3 (http://getbootstrap.com/) * bootstrap3 (http://getbootstrap.com/)
* ffmpeg project (https://ffmpeg.org/) * ffmpeg project (https://ffmpeg.org/)
* Magick++ (http://www.imagemagick.org/Magick++/) * GraphicsMagick++ (http://www.graphicsmagick.org/)
* MetaBrainz (https://metabrainz.org/) * MetaBrainz (https://metabrainz.org/)
* Bootstrap Notify: https://github.com/mouse0270/bootstrap-notify * Bootstrap Notify: https://github.com/mouse0270/bootstrap-notify
+233
View File
@@ -0,0 +1,233 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindFilesystem
##############
This module supports the C++17 standard library's filesystem utilities. Use the
:imp-target:`std::filesystem` imported target to
Options
*******
The ``COMPONENTS`` argument to this module supports the following values:
.. find-component:: Experimental
:name: fs.Experimental
Allows the module to find the "experimental" Filesystem TS version of the
Filesystem library. This is the library that should be used with the
``std::experimental::filesystem`` namespace.
.. find-component:: Final
:name: fs.Final
Finds the final C++17 standard version of the filesystem library.
If no components are provided, behaves as if the
:find-component:`fs.Final` component was specified.
If both :find-component:`fs.Experimental` and :find-component:`fs.Final` are
provided, first looks for ``Final``, and falls back to ``Experimental`` in case
of failure. If ``Final`` is found, :imp-target:`std::filesystem` and all
:ref:`variables <fs.variables>` will refer to the ``Final`` version.
Imported Targets
****************
.. imp-target:: std::filesystem
The ``std::filesystem`` imported target is defined when any requested
version of the C++ filesystem library has been found, whether it is
*Experimental* or *Final*.
If no version of the filesystem library is available, this target will not
be defined.
.. note::
This target has ``cxx_std_17`` as an ``INTERFACE``
:ref:`compile language standard feature <req-lang-standards>`. Linking
to this target will automatically enable C++17 if no later standard
version is already required on the linking target.
.. _fs.variables:
Variables
*********
.. variable:: CXX_FILESYSTEM_IS_EXPERIMENTAL
Set to ``TRUE`` when the :find-component:`fs.Experimental` version of C++
filesystem library was found, otherwise ``FALSE``.
.. variable:: CXX_FILESYSTEM_HAVE_FS
Set to ``TRUE`` when a filesystem header was found.
.. variable:: CXX_FILESYSTEM_HEADER
Set to either ``filesystem`` or ``experimental/filesystem`` depending on
whether :find-component:`fs.Final` or :find-component:`fs.Experimental` was
found.
.. variable:: CXX_FILESYSTEM_NAMESPACE
Set to either ``std::filesystem`` or ``std::experimental::filesystem``
depending on whether :find-component:`fs.Final` or
:find-component:`fs.Experimental` was found.
Examples
********
Using `find_package(Filesystem)` with no component arguments:
.. code-block:: cmake
find_package(Filesystem REQUIRED)
add_executable(my-program main.cpp)
target_link_libraries(my-program PRIVATE std::filesystem)
#]=======================================================================]
if(TARGET std::filesystem)
# This module has already been processed. Don't do it again.
return()
endif()
include(CMakePushCheckState)
include(CheckIncludeFileCXX)
include(CheckCXXSourceCompiles)
cmake_push_check_state()
set(CMAKE_REQUIRED_QUIET ${Filesystem_FIND_QUIETLY})
# All of our tests required C++17 or later
set(CMAKE_CXX_STANDARD 17)
# Normalize and check the component list we were given
set(want_components ${Filesystem_FIND_COMPONENTS})
if(Filesystem_FIND_COMPONENTS STREQUAL "")
set(want_components Final)
endif()
# Warn on any unrecognized components
set(extra_components ${want_components})
list(REMOVE_ITEM extra_components Final Experimental)
foreach(component IN LISTS extra_components)
message(WARNING "Extraneous find_package component for Filesystem: ${component}")
endforeach()
# Detect which of Experimental and Final we should look for
set(find_experimental TRUE)
set(find_final TRUE)
if(NOT "Final" IN_LIST want_components)
set(find_final FALSE)
endif()
if(NOT "Experimental" IN_LIST want_components)
set(find_experimental FALSE)
endif()
if(find_final)
check_include_file_cxx("filesystem" _CXX_FILESYSTEM_HAVE_HEADER)
mark_as_advanced(_CXX_FILESYSTEM_HAVE_HEADER)
if(_CXX_FILESYSTEM_HAVE_HEADER)
# We found the non-experimental header. Don't bother looking for the
# experimental one.
set(find_experimental FALSE)
endif()
else()
set(_CXX_FILESYSTEM_HAVE_HEADER FALSE)
endif()
if(find_experimental)
check_include_file_cxx("experimental/filesystem" _CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
mark_as_advanced(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
else()
set(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER FALSE)
endif()
if(_CXX_FILESYSTEM_HAVE_HEADER)
set(_have_fs TRUE)
set(_fs_header filesystem)
set(_fs_namespace std::filesystem)
elseif(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER)
set(_have_fs TRUE)
set(_fs_header experimental/filesystem)
set(_fs_namespace std::experimental::filesystem)
else()
set(_have_fs FALSE)
endif()
set(CXX_FILESYSTEM_HAVE_FS ${_have_fs} CACHE BOOL "TRUE if we have the C++ filesystem headers")
set(CXX_FILESYSTEM_HEADER ${_fs_header} CACHE STRING "The header that should be included to obtain the filesystem APIs")
set(CXX_FILESYSTEM_NAMESPACE ${_fs_namespace} CACHE STRING "The C++ namespace that contains the filesystem APIs")
set(_found FALSE)
if(CXX_FILESYSTEM_HAVE_FS)
# We have some filesystem library available. Do link checks
string(CONFIGURE [[
#include <@CXX_FILESYSTEM_HEADER@>
int main() {
auto cwd = @CXX_FILESYSTEM_NAMESPACE@::current_path();
return static_cast<int>(cwd.string().size());
}
]] code @ONLY)
# Try to compile a simple filesystem program without any linker flags
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_NO_LINK_NEEDED)
set(can_link ${CXX_FILESYSTEM_NO_LINK_NEEDED})
if(NOT CXX_FILESYSTEM_NO_LINK_NEEDED)
set(prev_libraries ${CMAKE_REQUIRED_LIBRARIES})
# Try to compile a simple filesystem program with the libstdc++ flag
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lstdc++fs)
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_STDCPPFS_NEEDED)
set(can_link ${CXX_FILESYSTEM_STDCPPFS_NEEDED})
if(NOT CXX_FILESYSTEM_STDCPPFS_NEEDED)
# Try to compile a simple filesystem program with the libc++ flag
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lc++fs)
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_CPPFS_NEEDED)
set(can_link ${CXX_FILESYSTEM_CPPFS_NEEDED})
if(NOT CXX_FILESYSTEM_CPPFS_NEEDED)
# Try to compile a simple filesystem program without any linker flags
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_NO_LINK_NEEDED)
set(can_link ${CXX_FILESYSTEM_NO_LINK_NEEDED})
endif()
endif()
endif()
if(can_link)
add_library(std::filesystem INTERFACE IMPORTED)
target_compile_features(std::filesystem INTERFACE cxx_std_17)
set(_found TRUE)
if(CXX_FILESYSTEM_NO_LINK_NEEDED)
# Nothing to add...
elseif(CXX_FILESYSTEM_STDCPPFS_NEEDED)
target_link_libraries(std::filesystem INTERFACE -lstdc++fs)
elseif(CXX_FILESYSTEM_CPPFS_NEEDED)
target_link_libraries(std::filesystem INTERFACE -lc++fs)
endif()
endif()
endif()
cmake_pop_check_state()
set(Filesystem_FOUND ${_found} CACHE BOOL "TRUE if we can compile and link a program using std::filesystem" FORCE)
if(Filesystem_FIND_REQUIRED AND NOT Filesystem_FOUND)
message(FATAL_ERROR "Cannot Compile simple program using std::filesystem")
endif()
+3
View File
@@ -18,6 +18,9 @@ listen-port = 5082;
listen-addr = "0.0.0.0"; listen-addr = "0.0.0.0";
behind-reverse-proxy = false; behind-reverse-proxy = false;
# Location for deployment
deploy-path = "/";
# If enabled, these files have to exist and have correct permissions # If enabled, these files have to exist and have correct permissions
tls-enable = false; tls-enable = false;
tls-cert = "/var/lms/cert.pem"; tls-cert = "/var/lms/cert.pem";
+37 -41
View File
@@ -5,7 +5,7 @@ WORKDIR /tmp/workdir
ARG MAKEFLAGS="-j2" ARG MAKEFLAGS="-j2"
ARG FFMPEG_VERSION=4.1.4 ARG FFMPEG_VERSION=4.1.4
ARG WT_VERSION=4.2.0 ARG WT_VERSION=4.2.0
ARG IMAGEMAGICK6_VERSION=6.9.10-71 ARG GRAPHICSMAGICK_VERSION=1.3.35
ARG PSTREAMS_VERSION=1.0.1 ARG PSTREAMS_VERSION=1.0.1
ARG LMS_VERSION=v3.6.3 ARG LMS_VERSION=v3.6.3
@@ -24,6 +24,7 @@ ARG BUILD_PACKAGES=" \
make \ make \
libjpeg-turbo-dev \ libjpeg-turbo-dev \
openjpeg-dev \ openjpeg-dev \
jasper-dev \
libpng-dev \ libpng-dev \
tiff-dev \ tiff-dev \
nasm \ nasm \
@@ -95,50 +96,42 @@ RUN \
make && \ make && \
make install make install
# ImageMagick # GraphicsMagick++
RUN \ RUN \
DIR=/tmp/imagemagick && mkdir -p ${DIR} && cd ${DIR} && \ DIR=/tmp/graphicsmagick && mkdir -p ${DIR} && cd ${DIR} && \
curl -sLO https://github.com/ImageMagick/ImageMagick6/archive/${IMAGEMAGICK6_VERSION}.tar.gz && \ curl -sL ftp://ftp.graphicsmagick.org/pub/GraphicsMagick/1.3/GraphicsMagick-${GRAPHICSMAGICK_VERSION}.tar.gz -o ${GRAPHICSMAGICK_VERSION}.tar.gz && \
tar -x --strip-components=1 -f ${IMAGEMAGICK6_VERSION}.tar.gz tar -x --strip-components=1 -f ${GRAPHICSMAGICK_VERSION}.tar.gz
RUN \ RUN \
DIR=/tmp/imagemagick && mkdir -p ${DIR} && cd ${DIR} && \ DIR=/tmp/graphicsmagick && mkdir -p ${DIR} && cd ${DIR} && \
./configure \ ./configure \
--prefix=${PREFIX} \ --prefix=${PREFIX} \
--enable-shared=yes \ --enable-shared=yes \
--disable-static \ --enable-static=no \
--disable-docs \ --with-quantum-depth=8 \
--with-magick-plus-plus \ --with-modules=no \
--without-zstd \ --with-frozenpaths=no \
--without-dps \ --with-magick-plus-plus=yes \
--without-autotrace \ --with-perl=no \
--without-dps \ --with-bzlib=no \
--without-fftw \ --with-dps=no \
--without-flif \ --with-fpx=no \
--without-fpx \ --with-jbig=no \
--without-djvu \ --with-webp=no \
--without-fontconfig \ --with-jp2=yes \
--without-freetype \ --with-jpeg=yes \
--without-raqm \ --with-lcms2=no \
--without-gslib \ --with-lzma=no \
--without-gvc \ --with-png=yes \
--without-heic \ --with-tiff=yes \
--without-jbig \ --with-trio=no \
--with-jpeg \ --with-ttf=no \
--without-jxl \ --with-tcmalloc=no \
--without-lcms \ --with-wmf=no \
--with-openjp2 \ --with-xml=no \
--without-lqr \ --with-zlib=yes \
--with-lzma \ --with-zstd=no \
--without-openexr \ --with-x=no && \
--without-pango \
--with-png \
--without-raw \
--without-rsvg \
--with-tiff \
--without-webp \
--without-wmf \
--without-xml && \
make && \ make && \
make install && \ make install && \
make distclean make distclean
@@ -158,8 +151,9 @@ RUN \
RUN \ RUN \
DIR=/tmp/lms && mkdir -p ${DIR} && cd ${DIR} && \ DIR=/tmp/lms && mkdir -p ${DIR} && cd ${DIR} && \
PKG_CONFIG_PATH=/tmp/install/lib/pkgconfig CXXFLAGS="-I${PREFIX}/include -L${PREFIX}/lib -Wl,--rpath-link=${PREFIX}/lib" cmake . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${PREFIX} && \ PKG_CONFIG_PATH=/tmp/install/lib/pkgconfig CXXFLAGS="-I${PREFIX}/include" LDFLAGS="-L${PREFIX}/lib -Wl,--rpath-link=${PREFIX}/lib" cmake . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${PREFIX} && \
make && \ make && \
LD_LIBRARY_PATH=${PREFIX}/lib make test && \
make install && \ make install && \
mkdir -p ${PREFIX}/etc/ && \ mkdir -p ${PREFIX}/etc/ && \
cp conf/lms.conf ${PREFIX}/etc cp conf/lms.conf ${PREFIX}/etc
@@ -178,7 +172,8 @@ RUN \
done && \ done && \
cp -r ${PREFIX}/lib /tmp/fakeroot/lib && \ cp -r ${PREFIX}/lib /tmp/fakeroot/lib && \
cp -r ${PREFIX}/share /tmp/fakeroot/share && \ cp -r ${PREFIX}/share /tmp/fakeroot/share && \
LD_LIBRARY_PATH=/tmp/fakeroot/lib /tmp/fakeroot/bin/ffmpeg -buildconf rm -rf /tmp/fakeroot/share/doc && \
rm -rf /tmp/fakeroot/share/man
## Release Stage ## Release Stage
FROM alpine:3.10 AS release FROM alpine:3.10 AS release
@@ -188,6 +183,7 @@ ARG RUNTIME_PACKAGES=" \
openssl \ openssl \
libjpeg-turbo \ libjpeg-turbo \
openjpeg \ openjpeg \
jasper-libs \
libpng \ libpng \
tiff \ tiff \
libogg \ libogg \
+1
View File
@@ -19,6 +19,7 @@ target_link_libraries(lmsav PUBLIC
lmsutils lmsutils
avformat avformat
avutil avutil
std::filesystem
wt wt
) )
+7 -3
View File
@@ -10,17 +10,21 @@ target_include_directories(lmscover INTERFACE
target_include_directories(lmscover PRIVATE target_include_directories(lmscover PRIVATE
include include
${IMAGEMAGICKXX_INCLUDE_DIRS} ${GRAPHICSMAGICKXX_INCLUDE_DIRS}
) )
target_compile_options(lmscover PRIVATE target_compile_options(lmscover PRIVATE
${IMAGEMAGICKXX_CFLAGS_OTHER} ${GRAPHICSMAGICKXX_CFLAGS_OTHER}
) )
target_link_libraries(lmscover PRIVATE target_link_libraries(lmscover PRIVATE
lmsav lmsav
${GRAPHICSMAGICKXX_LIBRARIES}
)
target_link_libraries(lmscover PUBLIC
lmsdatabase lmsdatabase
${IMAGEMAGICKXX_LIBRARIES} std::filesystem
) )
install(TARGETS lmscover DESTINATION lib) install(TARGETS lmscover DESTINATION lib)
+6 -8
View File
@@ -49,15 +49,13 @@ Grabber::Grabber(const std::filesystem::path& execPath)
init(execPath); init(execPath);
} }
Grabber::~Grabber()
{
deinit();
}
void void
Grabber::setDefaultCover(const std::filesystem::path& p) Grabber::setDefaultCover(const std::filesystem::path& p)
{ {
if (!_defaultCover.load(p)) std::unique_lock lock {_mutex};
_defaultCover = std::make_unique<Image>();
if (!_defaultCover->load(p))
throw LmsException("Cannot read default cover file '" + p.string() + "'"); throw LmsException("Cannot read default cover file '" + p.string() + "'");
} }
@@ -65,12 +63,12 @@ Image
Grabber::getDefaultCover(std::size_t size) Grabber::getDefaultCover(std::size_t size)
{ {
LMS_LOG(COVER, DEBUG) << "Getting a default cover using size = " << size; LMS_LOG(COVER, DEBUG) << "Getting a default cover using size = " << size;
std::unique_lock<std::mutex> lock(_mutex); std::unique_lock lock {_mutex};
auto it = _defaultCovers.find(size); auto it = _defaultCovers.find(size);
if (it == _defaultCovers.end()) if (it == _defaultCovers.end())
{ {
Image cover = _defaultCover; Image cover = *_defaultCover;
LMS_LOG(COVER, DEBUG) << "default cover size = " << cover.getSize().width << " x " << cover.getSize().height; LMS_LOG(COVER, DEBUG) << "default cover size = " << cover.getSize().width << " x " << cover.getSize().height;
+1 -3
View File
@@ -41,7 +41,6 @@ namespace CoverArt
{ {
public: public:
Grabber(const std::filesystem::path& execPath); Grabber(const std::filesystem::path& execPath);
~Grabber();
Grabber(const Grabber&) = delete; Grabber(const Grabber&) = delete;
Grabber& operator=(const Grabber&) = delete; Grabber& operator=(const Grabber&) = delete;
@@ -63,9 +62,8 @@ namespace CoverArt
std::optional<Image> getFromDirectory(const std::filesystem::path& path) const; std::optional<Image> getFromDirectory(const std::filesystem::path& path) const;
Image getDefaultCover(std::size_t size); Image getDefaultCover(std::size_t size);
Image _defaultCover;
std::mutex _mutex; std::mutex _mutex;
std::unique_ptr<Image> _defaultCover;
std::map<std::size_t /* size */, Image> _defaultCovers; std::map<std::size_t /* size */, Image> _defaultCovers;
static inline const std::vector<std::filesystem::path> _fileExtensions {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize static inline const std::vector<std::filesystem::path> _fileExtensions {".jpg", ".jpeg", ".png", ".bmp"}; // TODO parametrize
-6
View File
@@ -29,12 +29,6 @@ init(const std::filesystem::path& path)
Magick::InitializeMagick(path.string().c_str()); Magick::InitializeMagick(path.string().c_str());
} }
void
deinit()
{
MagickCore::MagickCoreTerminus();
}
static static
std::string std::string
formatToMagick(Format format) formatToMagick(Format format)
-1
View File
@@ -30,7 +30,6 @@ namespace CoverArt
{ {
void init(const std::filesystem::path& path); void init(const std::filesystem::path& path);
void deinit();
class Image class Image
{ {
+1
View File
@@ -30,6 +30,7 @@ target_link_libraries(lmsdatabase PRIVATE
target_link_libraries(lmsdatabase PUBLIC target_link_libraries(lmsdatabase PUBLIC
lmsutils lmsutils
std::filesystem
wtdbo wtdbo
) )
+8 -2
View File
@@ -40,7 +40,7 @@
namespace Database { namespace Database {
#define LMS_DATABASE_VERSION 13 #define LMS_DATABASE_VERSION 14
using Version = std::size_t; using Version = std::size_t;
@@ -153,7 +153,13 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
} }
else if (version == 12) else if (version == 12)
{ {
// Artist and release that have a baddly parsed name but a MBID had no chance to updat the name // Artist and release that have a badly parsed name but a MBID had no chance to updat the name
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion();
}
else if (version == 13)
{
// Always store UUID in lower case + better WMA parsing
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion(); ScanSettings::get(*this).modify()->incScanVersion();
} }
+1
View File
@@ -19,6 +19,7 @@ target_link_libraries(lmsmetadata PRIVATE
target_link_libraries(lmsmetadata PUBLIC target_link_libraries(lmsmetadata PUBLIC
lmsutils lmsutils
std::filesystem
) )
install(TARGETS lmsmetadata DESTINATION lib) install(TARGETS lmsmetadata DESTINATION lib)
+92 -66
View File
@@ -154,70 +154,9 @@ getAlbum(const TagLib::PropertyMap& properties)
return Album {std::move(albumName.front()), albumMBID.front()}; return Album {std::move(albumName.front()), albumMBID.front()};
} }
std::optional<Track> void
TagLibParser::parse(const std::filesystem::path& p, bool debug) TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug)
{ {
TagLib::FileRef f {p.string().c_str(),
true, // read audio properties
TagLib::AudioProperties::Fast};
if (f.isNull())
{
LMS_LOG(METADATA, ERROR) << "File '" << p.string() << "': parsing failed";
return std::nullopt;
}
if (!f.audioProperties())
{
LMS_LOG(METADATA, INFO) << "File '" << p.string() << "': no audio properties";
return std::nullopt;
}
Track track;
{
const TagLib::AudioProperties *properties {f.audioProperties() };
track.duration = std::chrono::milliseconds {properties->length() * 1000};
MetaData::AudioStream audioStream {static_cast<unsigned>(properties->bitrate() * 1000)};
track.audioStreams = {std::move(audioStream)};
}
// Not that good embedded pictures handling
// WMA
if (TagLib::ASF::File* asfFile {dynamic_cast<TagLib::ASF::File*>(f.file())})
{
const TagLib::ASF::Tag* tag {asfFile->tag()};
if (tag && tag->attributeListMap().contains("WM/Picture"))
track.hasCover = true;
}
// MP3
else if (TagLib::MPEG::File* mp3File {dynamic_cast<TagLib::MPEG::File*>(f.file())})
{
if (mp3File->ID3v2Tag())
{
if (!mp3File->ID3v2Tag()->frameListMap()["APIC"].isEmpty())
track.hasCover = true;
}
}
// FLAC
else if (TagLib::FLAC::File* flacFile {dynamic_cast<TagLib::FLAC::File*>(f.file())})
{
if (!flacFile->pictureList().isEmpty())
track.hasCover = true;
}
if (f.tag())
{
MetaData::Clusters clusters;
const TagLib::PropertyMap& properties {f.file()->properties()};
for(const auto& property : properties)
{
const std::string tag {property.first.upper().to8Bit(true)};
const TagLib::StringList& values {property.second};
// TODO validate MBID format // TODO validate MBID format
if (debug) if (debug)
@@ -229,7 +168,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
} }
if (tag.empty() || values.isEmpty() || values.front().isEmpty()) if (tag.empty() || values.isEmpty() || values.front().isEmpty())
continue; return;
std::string value {StringUtils::stringTrim(values.front().to8Bit(true))}; std::string value {StringUtils::stringTrim(values.front().to8Bit(true))};
@@ -321,12 +260,99 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
} }
} }
std::optional<Track>
TagLibParser::parse(const std::filesystem::path& p, bool debug)
{
TagLib::FileRef f {p.string().c_str(),
true, // read audio properties
TagLib::AudioProperties::Fast};
if (f.isNull())
{
LMS_LOG(METADATA, ERROR) << "File '" << p.string() << "': parsing failed";
return std::nullopt;
}
if (!f.audioProperties())
{
LMS_LOG(METADATA, INFO) << "File '" << p.string() << "': no audio properties";
return std::nullopt;
}
Track track;
{
const TagLib::AudioProperties *properties {f.audioProperties() };
track.duration = std::chrono::milliseconds {properties->length() * 1000};
MetaData::AudioStream audioStream {static_cast<unsigned>(properties->bitrate() * 1000)};
track.audioStreams = {std::move(audioStream)};
}
TagLib::PropertyMap properties {f.file()->properties()};
// Not that good embedded pictures handling
// WMA
if (TagLib::ASF::File* asfFile {dynamic_cast<TagLib::ASF::File*>(f.file())})
{
const TagLib::ASF::Tag* tag {asfFile->tag()};
if (tag)
{
if (tag->attributeListMap().contains("WM/Picture"))
track.hasCover = true;
for (const auto& [name, attributeList] : tag->attributeListMap())
{
if (name.to8Bit().find("WM/") == 0 || properties.contains(name))
continue;
TagLib::StringList stringAttributeList;
for (const auto& attribute : attributeList)
{
if (attribute.type() == TagLib::ASF::Attribute::AttributeTypes::UnicodeType)
stringAttributeList.append(attribute.toString());
}
if (!stringAttributeList.isEmpty())
{
if (debug)
std::cout << "Property: '" << name << "'" << std::endl;
properties.insert(name, stringAttributeList);
}
}
}
}
// MP3
else if (TagLib::MPEG::File* mp3File {dynamic_cast<TagLib::MPEG::File*>(f.file())})
{
if (mp3File->ID3v2Tag())
{
if (!mp3File->ID3v2Tag()->frameListMap()["APIC"].isEmpty())
track.hasCover = true;
}
}
// FLAC
else if (TagLib::FLAC::File* flacFile {dynamic_cast<TagLib::FLAC::File*>(f.file())})
{
if (!flacFile->pictureList().isEmpty())
track.hasCover = true;
}
for(const auto& property : properties)
{
const std::string tag {property.first.upper().to8Bit(true)};
const TagLib::StringList& values {property.second};
processTag(track, tag, values, debug);
}
track.artists = getArtists(properties); track.artists = getArtists(properties);
track.albumArtists = getAlbumArtists(properties); track.albumArtists = getAlbumArtists(properties);
track.album = getAlbum(properties); track.album = getAlbum(properties);
}
return track; return track;
} }
@@ -21,14 +21,21 @@
#include "metadata/IParser.hpp" #include "metadata/IParser.hpp"
namespace TagLib
{
class StringList;
}
namespace MetaData namespace MetaData
{ {
// Parse that makes use of AvFormat // Parse that makes use of AvFormat
class TagLibParser : public IParser class TagLibParser : public IParser
{ {
public: private:
std::optional<Track> parse(const std::filesystem::path& p, bool debug = false) override; std::optional<Track> parse(const std::filesystem::path& p, bool debug = false) override;
void processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug);
}; };
} // namespace MetaData } // namespace MetaData
+1
View File
@@ -18,6 +18,7 @@ target_include_directories(lmsrecommendation PRIVATE
target_link_libraries(lmsrecommendation PRIVATE target_link_libraries(lmsrecommendation PRIVATE
lmsdatabase lmsdatabase
lmssom lmssom
std::filesystem
wt wt
) )
@@ -36,7 +36,7 @@ namespace Recommendation
private: private:
std::string_view getName() const { return "Clusters"; } std::string_view getName() const override { return "Clusters"; }
bool init(Database::Session&, bool) override {return true;} bool init(Database::Session&, bool) override {return true;}
void requestCancelInit() override {} void requestCancelInit() override {}
@@ -55,15 +55,15 @@ class FeaturesClassifier : public IClassifier
static const FeatureSettingsMap& getDefaultTrainFeatureSettings(); static const FeatureSettingsMap& getDefaultTrainFeatureSettings();
private: private:
std::string_view getName() const { return "Features"; } std::string_view getName() const override { return "Features"; }
bool init(Database::Session& session, bool databaseChanged) override; bool init(Database::Session& session, bool databaseChanged) override;
void requestCancelInit() override; void requestCancelInit() override;
std::vector<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override; std::vector<Database::IdType> getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) const override;
std::vector<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override; std::vector<Database::IdType> getSimilarTracks(Database::Session& session, const std::unordered_set<Database::IdType>& tracksId, std::size_t maxCount) const override;
std::vector<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const; std::vector<Database::IdType> getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount) const override;
std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) const; std::vector<Database::IdType> getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount) const override;
bool initFromCache(Database::Session& session, const FeaturesClassifierCache& cache); bool initFromCache(Database::Session& session, const FeaturesClassifierCache& cache);
+1
View File
@@ -20,6 +20,7 @@ target_link_libraries(lmsscanner PRIVATE
) )
target_link_libraries(lmsscanner PUBLIC target_link_libraries(lmsscanner PUBLIC
std::filesystem
wt wt
) )
+1
View File
@@ -21,6 +21,7 @@ target_link_libraries(lmssubsonic PRIVATE
lmscover lmscover
lmsrecommendation lmsrecommendation
lmsutils lmsutils
std::filesystem
) )
target_link_libraries(lmssubsonic PUBLIC target_link_libraries(lmssubsonic PUBLIC
+2 -2
View File
@@ -23,14 +23,14 @@ namespace StringUtils
{ {
template<> template<>
std::optional<API::Subsonic::Id> std::optional<API::Subsonic::Id>
StringUtils::readAs(const std::string& str) readAs(const std::string& str)
{ {
return API::Subsonic::IdFromString(str); return API::Subsonic::IdFromString(str);
} }
template<> template<>
std::optional<bool> std::optional<bool>
StringUtils::readAs(const std::string& str) readAs(const std::string& str)
{ {
if (str == "true") if (str == "true")
return true; return true;
+2 -2
View File
@@ -90,10 +90,10 @@ namespace StringUtils
{ {
template<> template<>
std::optional<API::Subsonic::Id> std::optional<API::Subsonic::Id>
StringUtils::readAs(const std::string& str); readAs(const std::string& str);
template<> template<>
std::optional<bool> std::optional<bool>
StringUtils::readAs(const std::string& str); readAs(const std::string& str);
} }
+2 -1
View File
@@ -19,6 +19,7 @@
#include "subsonic/SubsonicResource.hpp" #include "subsonic/SubsonicResource.hpp"
#include <atomic> #include <atomic>
#include <unordered_map>
#include <Wt/WLocalDateTime.h> #include <Wt/WLocalDateTime.h>
@@ -65,7 +66,7 @@ namespace StringUtils
{ {
template<> template<>
std::optional<API::Subsonic::ClientVersion> std::optional<API::Subsonic::ClientVersion>
StringUtils::readAs(const std::string& str) readAs(const std::string& str)
{ {
// Expects "X.Y.Z" // Expects "X.Y.Z"
const auto numbers {StringUtils::splitString(str, ".")}; const auto numbers {StringUtils::splitString(str, ".")};
@@ -36,7 +36,7 @@ class SubsonicResource final : public Wt::WResource
public: public:
SubsonicResource(Database::Db& db); SubsonicResource(Database::Db& db);
static std::string getPath() { return "/rest/"; } static std::string getPath() { return "rest/"; }
private: private:
void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override; void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override;
+1 -1
View File
@@ -26,7 +26,7 @@ target_link_libraries(lmsutils PRIVATE
target_link_libraries(lmsutils PUBLIC target_link_libraries(lmsutils PUBLIC
boost_system boost_system
stdc++fs std::filesystem
wt wt
) )
+14 -3
View File
@@ -20,6 +20,7 @@
#include "utils/String.hpp" #include "utils/String.hpp"
#include <iomanip> #include <iomanip>
#include <unordered_map>
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
@@ -98,15 +99,25 @@ stringTrimEnd(const std::string& str, const std::string& whitespace)
} }
std::string std::string
stringToLower(const std::string& str) stringToLower(std::string_view str)
{ {
return boost::algorithm::to_lower_copy(str); std::string res;
res.reserve(str.size());
std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](char c) { return std::tolower(c);});
return res;
} }
std::string std::string
stringToUpper(const std::string& str) stringToUpper(const std::string& str)
{ {
return boost::to_upper_copy<std::string>(str); std::string res;
res.reserve(str.size());
std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](char c) { return std::toupper(c);});
return res;
} }
std::string std::string
+6
View File
@@ -21,6 +21,8 @@
#include <regex> #include <regex>
#include "utils/String.hpp"
namespace StringUtils namespace StringUtils
{ {
template <> template <>
@@ -40,6 +42,10 @@ stringIsUUID(std::string_view str)
return std::regex_match(std::cbegin(str), std::cend(str), re); return std::regex_match(std::cbegin(str), std::cend(str), re);
} }
UUID::UUID(std::string_view str)
: _value {StringUtils::stringToLower(str)}
{
}
std::optional<UUID> std::optional<UUID>
UUID::fromString(std::string_view str) UUID::fromString(std::string_view str)
+2 -1
View File
@@ -21,6 +21,7 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
@@ -40,7 +41,7 @@ std::string
stringTrimEnd(const std::string& str, const std::string& whitespaces = " \t"); stringTrimEnd(const std::string& str, const std::string& whitespaces = " \t");
std::string std::string
stringToLower(const std::string& str); stringToLower(std::string_view str);
std::string std::string
stringToUpper(const std::string& str); stringToUpper(const std::string& str);
+1 -1
View File
@@ -34,7 +34,7 @@ class UUID
std::string_view getAsString() const { return _value; } std::string_view getAsString() const { return _value; }
private: private:
UUID(std::string_view value) : _value {value} {} UUID(std::string_view value);
std::string _value; std::string _value;
}; };
+3
View File
@@ -48,6 +48,7 @@ std::vector<std::string> generateWtConfig(std::string execPath)
args.push_back("--config=" + wtConfigPath.string()); args.push_back("--config=" + wtConfigPath.string());
args.push_back("--docroot=" + ServiceProvider<IConfig>::get()->getString("docroot")); args.push_back("--docroot=" + ServiceProvider<IConfig>::get()->getString("docroot"));
args.push_back("--approot=" + ServiceProvider<IConfig>::get()->getString("approot")); args.push_back("--approot=" + ServiceProvider<IConfig>::get()->getString("approot"));
args.push_back("--deploy-path=" + ServiceProvider<IConfig>::get()->getString("deploy-path", "/"));
args.push_back("--resources-dir=" + ServiceProvider<IConfig>::get()->getString("wt-resources")); args.push_back("--resources-dir=" + ServiceProvider<IConfig>::get()->getString("wt-resources"));
if (ServiceProvider<IConfig>::get()->getBool("tls-enable", false)) if (ServiceProvider<IConfig>::get()->getBool("tls-enable", false))
@@ -197,6 +198,8 @@ int main(int argc, char* argv[])
LMS_LOG(MAIN, INFO) << "Stopping recommendation engine..."; LMS_LOG(MAIN, INFO) << "Stopping recommendation engine...";
recommendationEngine.stop(); recommendationEngine.stop();
ServiceProvider<CoverArt::IGrabber>::clear();
LMS_LOG(MAIN, INFO) << "Clean stop!"; LMS_LOG(MAIN, INFO) << "Clean stop!";
res = EXIT_SUCCESS; res = EXIT_SUCCESS;
} }
@@ -88,7 +88,7 @@ dumpRecommendation(Database::Session session, Recommendation::IEngine& engine)
}; };
std::cout << "Processing release '" << releaseToString(releaseId) << "'" << std::endl; std::cout << "Processing release '" << releaseToString(releaseId) << "'" << std::endl;
for (Database::IdType similarReleaseId : engine.getSimilarReleases(session, {releaseId}, 3)) for (Database::IdType similarReleaseId : engine.getSimilarReleases(session, releaseId, 3))
std::cout << "\t- Similar release '" << releaseToString(similarReleaseId) << "'" << std::endl; std::cout << "\t- Similar release '" << releaseToString(similarReleaseId) << "'" << std::endl;
} }
@@ -110,7 +110,7 @@ dumpRecommendation(Database::Session session, Recommendation::IEngine& engine)
}; };
std::cout << "Processing artist '" << artistToString(artistId) << "'" << std::endl; std::cout << "Processing artist '" << artistToString(artistId) << "'" << std::endl;
for (Database::IdType similarArtistId : engine.getSimilarArtists(session, {artistId}, 3)) for (Database::IdType similarArtistId : engine.getSimilarArtists(session, artistId, 3))
std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl; std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl;
} }