Merge branch 'develop' for release 3.9.0
This commit is contained in:
+4
-4
@@ -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
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Executable
+233
@@ -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()
|
||||||
@@ -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
@@ -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 \
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ target_link_libraries(lmsav PUBLIC
|
|||||||
lmsutils
|
lmsutils
|
||||||
avformat
|
avformat
|
||||||
avutil
|
avutil
|
||||||
|
std::filesystem
|
||||||
wt
|
wt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -154,12 +154,118 @@ getAlbum(const TagLib::PropertyMap& properties)
|
|||||||
return Album {std::move(albumName.front()), albumMBID.front()};
|
return Album {std::move(albumName.front()), albumMBID.front()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug)
|
||||||
|
{
|
||||||
|
|
||||||
|
// TODO validate MBID format
|
||||||
|
if (debug)
|
||||||
|
{
|
||||||
|
std::vector<std::string> strs;
|
||||||
|
std::transform(values.begin(), values.end(), std::back_inserter(strs), [](const auto& value) { return value.to8Bit(true); });
|
||||||
|
|
||||||
|
std::cout << "[" << tag << "] = " << StringUtils::joinStrings(strs, "*SEP*") << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag.empty() || values.isEmpty() || values.front().isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string value {StringUtils::stringTrim(values.front().to8Bit(true))};
|
||||||
|
|
||||||
|
if (tag == "TITLE")
|
||||||
|
track.title = value;
|
||||||
|
else if (tag == "MUSICBRAINZ_RELEASETRACKID"
|
||||||
|
|| tag == "MUSICBRAINZ RELEASE TRACK ID")
|
||||||
|
{
|
||||||
|
track.musicBrainzTrackID = UUID::fromString(value);
|
||||||
|
}
|
||||||
|
else if (tag == "MUSICBRAINZ_TRACKID"
|
||||||
|
|| tag == "MUSICBRAINZ TRACK ID")
|
||||||
|
track.musicBrainzRecordID = UUID::fromString(value);
|
||||||
|
else if (tag == "ACOUSTID_ID")
|
||||||
|
track.acoustID = UUID::fromString(value);
|
||||||
|
else if (tag == "TRACKTOTAL")
|
||||||
|
{
|
||||||
|
auto totalTrack = StringUtils::readAs<std::size_t>(value);
|
||||||
|
if (totalTrack)
|
||||||
|
track.totalTrack = totalTrack;
|
||||||
|
}
|
||||||
|
else if (tag == "TRACKNUMBER")
|
||||||
|
{
|
||||||
|
// Expecting 'Number/Total'
|
||||||
|
std::vector<std::string> strings {splitAndTrimString(value, "/")};
|
||||||
|
|
||||||
|
if (!strings.empty())
|
||||||
|
{
|
||||||
|
track.trackNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
||||||
|
|
||||||
|
// Lower priority than TRACKTOTAL
|
||||||
|
if (strings.size() > 1 && !track.totalTrack)
|
||||||
|
track.totalTrack = StringUtils::readAs<std::size_t>(strings[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tag == "DISCTOTAL")
|
||||||
|
{
|
||||||
|
auto totalDisc = StringUtils::readAs<std::size_t>(value);
|
||||||
|
if (totalDisc)
|
||||||
|
track.totalDisc = totalDisc;
|
||||||
|
}
|
||||||
|
else if (tag == "DISCNUMBER")
|
||||||
|
{
|
||||||
|
// Expecting 'Number/Total'
|
||||||
|
std::vector<std::string> strings {StringUtils::splitString(value, "/")};
|
||||||
|
|
||||||
|
if (!strings.empty())
|
||||||
|
{
|
||||||
|
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
||||||
|
|
||||||
|
// Lower priority than DISCTOTAL
|
||||||
|
if (strings.size() > 1 && !track.totalDisc)
|
||||||
|
track.totalDisc = StringUtils::readAs<std::size_t>(strings[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tag == "DATE")
|
||||||
|
track.year = StringUtils::readAs<int>(value);
|
||||||
|
else if (tag == "ORIGINALDATE" && !track.originalYear)
|
||||||
|
{
|
||||||
|
// Lower priority than ORIGINALYEAR
|
||||||
|
track.originalYear = StringUtils::readAs<int>(value);
|
||||||
|
}
|
||||||
|
else if (tag == "ORIGINALYEAR")
|
||||||
|
{
|
||||||
|
// Higher priority than ORIGINALDATE
|
||||||
|
auto originalYear = StringUtils::readAs<int>(value);
|
||||||
|
if (originalYear)
|
||||||
|
track.originalYear = originalYear;
|
||||||
|
}
|
||||||
|
else if (tag == "METADATA_BLOCK_PICTURE")
|
||||||
|
track.hasCover = true;
|
||||||
|
else if (tag == "COPYRIGHT")
|
||||||
|
track.copyright = value;
|
||||||
|
else if (tag == "COPYRIGHTURL")
|
||||||
|
track.copyrightURL = value;
|
||||||
|
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||||
|
{
|
||||||
|
std::set<std::string> clusterNames;
|
||||||
|
for (const auto& valueList : values)
|
||||||
|
{
|
||||||
|
auto values = splitAndTrimString(valueList.to8Bit(true), "/,;");
|
||||||
|
|
||||||
|
for (const auto& value : values)
|
||||||
|
clusterNames.insert(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clusterNames.empty())
|
||||||
|
track.clusters[tag] = clusterNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<Track>
|
std::optional<Track>
|
||||||
TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
||||||
{
|
{
|
||||||
TagLib::FileRef f {p.string().c_str(),
|
TagLib::FileRef f {p.string().c_str(),
|
||||||
true, // read audio properties
|
true, // read audio properties
|
||||||
TagLib::AudioProperties::Fast};
|
TagLib::AudioProperties::Fast};
|
||||||
|
|
||||||
if (f.isNull())
|
if (f.isNull())
|
||||||
{
|
{
|
||||||
@@ -184,14 +290,40 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
|||||||
track.audioStreams = {std::move(audioStream)};
|
track.audioStreams = {std::move(audioStream)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TagLib::PropertyMap properties {f.file()->properties()};
|
||||||
|
|
||||||
// Not that good embedded pictures handling
|
// Not that good embedded pictures handling
|
||||||
|
|
||||||
// WMA
|
// WMA
|
||||||
if (TagLib::ASF::File* asfFile {dynamic_cast<TagLib::ASF::File*>(f.file())})
|
if (TagLib::ASF::File* asfFile {dynamic_cast<TagLib::ASF::File*>(f.file())})
|
||||||
{
|
{
|
||||||
const TagLib::ASF::Tag* tag {asfFile->tag()};
|
const TagLib::ASF::Tag* tag {asfFile->tag()};
|
||||||
if (tag && tag->attributeListMap().contains("WM/Picture"))
|
if (tag)
|
||||||
track.hasCover = true;
|
{
|
||||||
|
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
|
// MP3
|
||||||
else if (TagLib::MPEG::File* mp3File {dynamic_cast<TagLib::MPEG::File*>(f.file())})
|
else if (TagLib::MPEG::File* mp3File {dynamic_cast<TagLib::MPEG::File*>(f.file())})
|
||||||
@@ -209,124 +341,18 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
|||||||
track.hasCover = true;
|
track.hasCover = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f.tag())
|
for(const auto& property : properties)
|
||||||
{
|
{
|
||||||
MetaData::Clusters clusters;
|
const std::string tag {property.first.upper().to8Bit(true)};
|
||||||
const TagLib::PropertyMap& properties {f.file()->properties()};
|
const TagLib::StringList& values {property.second};
|
||||||
|
|
||||||
for(const auto& property : properties)
|
|
||||||
{
|
|
||||||
const std::string tag {property.first.upper().to8Bit(true)};
|
|
||||||
const TagLib::StringList& values {property.second};
|
|
||||||
|
|
||||||
// TODO validate MBID format
|
|
||||||
if (debug)
|
|
||||||
{
|
|
||||||
std::vector<std::string> strs;
|
|
||||||
std::transform(values.begin(), values.end(), std::back_inserter(strs), [](const auto& value) { return value.to8Bit(true); });
|
|
||||||
|
|
||||||
std::cout << "[" << tag << "] = " << StringUtils::joinStrings(strs, "*SEP*") << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag.empty() || values.isEmpty() || values.front().isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::string value {StringUtils::stringTrim(values.front().to8Bit(true))};
|
|
||||||
|
|
||||||
if (tag == "TITLE")
|
|
||||||
track.title = value;
|
|
||||||
else if (tag == "MUSICBRAINZ_RELEASETRACKID"
|
|
||||||
|| tag == "MUSICBRAINZ RELEASE TRACK ID")
|
|
||||||
{
|
|
||||||
track.musicBrainzTrackID = UUID::fromString(value);
|
|
||||||
}
|
|
||||||
else if (tag == "MUSICBRAINZ_TRACKID"
|
|
||||||
|| tag == "MUSICBRAINZ TRACK ID")
|
|
||||||
track.musicBrainzRecordID = UUID::fromString(value);
|
|
||||||
else if (tag == "ACOUSTID_ID")
|
|
||||||
track.acoustID = UUID::fromString(value);
|
|
||||||
else if (tag == "TRACKTOTAL")
|
|
||||||
{
|
|
||||||
auto totalTrack = StringUtils::readAs<std::size_t>(value);
|
|
||||||
if (totalTrack)
|
|
||||||
track.totalTrack = totalTrack;
|
|
||||||
}
|
|
||||||
else if (tag == "TRACKNUMBER")
|
|
||||||
{
|
|
||||||
// Expecting 'Number/Total'
|
|
||||||
std::vector<std::string> strings {splitAndTrimString(value, "/")};
|
|
||||||
|
|
||||||
if (!strings.empty())
|
|
||||||
{
|
|
||||||
track.trackNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
|
||||||
|
|
||||||
// Lower priority than TRACKTOTAL
|
|
||||||
if (strings.size() > 1 && !track.totalTrack)
|
|
||||||
track.totalTrack = StringUtils::readAs<std::size_t>(strings[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (tag == "DISCTOTAL")
|
|
||||||
{
|
|
||||||
auto totalDisc = StringUtils::readAs<std::size_t>(value);
|
|
||||||
if (totalDisc)
|
|
||||||
track.totalDisc = totalDisc;
|
|
||||||
}
|
|
||||||
else if (tag == "DISCNUMBER")
|
|
||||||
{
|
|
||||||
// Expecting 'Number/Total'
|
|
||||||
std::vector<std::string> strings {StringUtils::splitString(value, "/")};
|
|
||||||
|
|
||||||
if (!strings.empty())
|
|
||||||
{
|
|
||||||
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
|
||||||
|
|
||||||
// Lower priority than DISCTOTAL
|
|
||||||
if (strings.size() > 1 && !track.totalDisc)
|
|
||||||
track.totalDisc = StringUtils::readAs<std::size_t>(strings[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (tag == "DATE")
|
|
||||||
track.year = StringUtils::readAs<int>(value);
|
|
||||||
else if (tag == "ORIGINALDATE" && !track.originalYear)
|
|
||||||
{
|
|
||||||
// Lower priority than ORIGINALYEAR
|
|
||||||
track.originalYear = StringUtils::readAs<int>(value);
|
|
||||||
}
|
|
||||||
else if (tag == "ORIGINALYEAR")
|
|
||||||
{
|
|
||||||
// Higher priority than ORIGINALDATE
|
|
||||||
auto originalYear = StringUtils::readAs<int>(value);
|
|
||||||
if (originalYear)
|
|
||||||
track.originalYear = originalYear;
|
|
||||||
}
|
|
||||||
else if (tag == "METADATA_BLOCK_PICTURE")
|
|
||||||
track.hasCover = true;
|
|
||||||
else if (tag == "COPYRIGHT")
|
|
||||||
track.copyright = value;
|
|
||||||
else if (tag == "COPYRIGHTURL")
|
|
||||||
track.copyrightURL = value;
|
|
||||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
|
||||||
{
|
|
||||||
std::set<std::string> clusterNames;
|
|
||||||
for (const auto& valueList : values)
|
|
||||||
{
|
|
||||||
auto values = splitAndTrimString(valueList.to8Bit(true), "/,;");
|
|
||||||
|
|
||||||
for (const auto& value : values)
|
|
||||||
clusterNames.insert(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!clusterNames.empty())
|
|
||||||
track.clusters[tag] = clusterNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
track.artists = getArtists(properties);
|
|
||||||
track.albumArtists = getAlbumArtists(properties);
|
|
||||||
track.album = getAlbum(properties);
|
|
||||||
|
|
||||||
|
processTag(track, tag, values, debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
track.artists = getArtists(properties);
|
||||||
|
track.albumArtists = getAlbumArtists(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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ target_link_libraries(lmsscanner PRIVATE
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(lmsscanner PUBLIC
|
target_link_libraries(lmsscanner PUBLIC
|
||||||
|
std::filesystem
|
||||||
wt
|
wt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user