Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33ae53a90f | ||
|
|
01c28867b7 | ||
|
|
08fb2ae331 | ||
|
|
99970f9e52 | ||
|
|
bc206f43b4 | ||
|
|
018448159c | ||
|
|
81fe90bdef | ||
|
|
319558c535 | ||
|
|
9095b0d6b2 | ||
|
|
558eae1ca1 | ||
|
|
2c64f05cea | ||
|
|
c80e7071a1 | ||
|
|
038e679000 | ||
|
|
72447fecfb | ||
|
|
c7830f6f05 | ||
|
|
af525e42b6 | ||
|
|
eb83f23125 | ||
|
|
7527d2ea9a | ||
|
|
69d38879d2 | ||
|
|
cbce9f7191 | ||
|
|
f938129d81 | ||
|
|
415a40ea04 | ||
|
|
6e7aaed4ee | ||
|
|
7afae70bb0 | ||
|
|
be8097919b | ||
|
|
1990a42e1d | ||
|
|
8fcee4511d | ||
|
|
24af1be666 | ||
|
|
8302a95bc1 | ||
|
|
36a8ab49a0 | ||
|
|
5c64dc9c4d | ||
|
|
c271743208 | ||
|
|
ee49b1ddc8 | ||
|
|
bf98633f16 | ||
|
|
e2a928f2dc | ||
|
|
47d3312a6b | ||
|
|
4f97325953 | ||
|
|
91e8fe0943 | ||
|
|
dc5894b38a | ||
|
|
52ee50a2a4 | ||
|
|
f545b028ee | ||
|
|
a96627c5a9 | ||
|
|
a13fc31f83 | ||
|
|
9ee5c8dc17 | ||
|
|
82cd425ece | ||
|
|
3b02d364ba | ||
|
|
73e7947487 | ||
|
|
cfc9a43b88 | ||
|
|
969500023a | ||
|
|
53c72d4f8e | ||
|
|
f971c92f32 | ||
|
|
82156e8a13 | ||
|
|
6a6285861e | ||
|
|
dae8c8730b | ||
|
|
20b47eae8f | ||
|
|
2ce8220d88 | ||
|
|
35b0b5df57 | ||
|
|
63631d6b0c | ||
|
|
21ab2ef1a7 | ||
|
|
a3f96d2b85 | ||
|
|
e2c1cb0116 | ||
|
|
07e295776b | ||
|
|
8604a39d94 | ||
|
|
315240fec7 | ||
|
|
fbc6d326f8 | ||
|
|
c082377e7a | ||
|
|
448445a38a | ||
|
|
18f835d7e5 | ||
|
|
fd427dac29 | ||
|
|
e1afe03d51 | ||
|
|
1b49653974 | ||
|
|
d66126f998 | ||
|
|
0fff5f672a | ||
|
|
2726f01fb3 | ||
|
|
9a74fce53d | ||
|
|
b3be8387f1 | ||
|
|
d396cb515d | ||
|
|
2548b4648e |
105
.github/workflows/build.yml
vendored
105
.github/workflows/build.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
|
|
||||||
build-opensuse:
|
build-opensuse:
|
||||||
name: Build openSUSE
|
name: Build openSUSE
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -102,14 +102,14 @@ jobs:
|
|||||||
- name: Build RPM (Tumbleweed)
|
- name: Build RPM (Tumbleweed)
|
||||||
if: matrix.opensuse_version == 'tumbleweed'
|
if: matrix.opensuse_version == 'tumbleweed'
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
run: rpmbuild -ba strawberry.spec
|
||||||
- name: Build RPM (Leap)
|
- name: Build RPM (Leap)
|
||||||
if: matrix.opensuse_version != 'tumbleweed'
|
if: matrix.opensuse_version != 'tumbleweed'
|
||||||
working-directory: build
|
working-directory: build
|
||||||
env:
|
env:
|
||||||
CC: gcc-13
|
CC: gcc-13
|
||||||
CXX: g++-13
|
CXX: g++-13
|
||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
run: rpmbuild -ba strawberry.spec
|
||||||
- name: Set subdir
|
- name: Set subdir
|
||||||
id: set-subdir
|
id: set-subdir
|
||||||
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
|
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
|
|
||||||
build-fedora:
|
build-fedora:
|
||||||
name: Build Fedora
|
name: Build Fedora
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
dnf -y install
|
dnf -y install
|
||||||
@development-tools
|
@development-tools
|
||||||
redhat-lsb-core
|
lsb_release
|
||||||
which
|
which
|
||||||
git
|
git
|
||||||
glibc
|
glibc
|
||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RPM_BUILD_NCPUS: "2"
|
RPM_BUILD_NCPUS: "2"
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
run: rpmbuild -ba strawberry.spec
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -221,7 +221,7 @@ jobs:
|
|||||||
|
|
||||||
build-openmandriva:
|
build-openmandriva:
|
||||||
name: Build OpenMandriva
|
name: Build OpenMandriva
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && false
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master' && false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -302,7 +302,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RPM_BUILD_NCPUS: "2"
|
RPM_BUILD_NCPUS: "2"
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
run: rpmbuild -ba strawberry.spec
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: matrix.openmandriva_version != 'cooker'
|
if: matrix.openmandriva_version != 'cooker'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -315,7 +315,7 @@ jobs:
|
|||||||
|
|
||||||
build-mageia:
|
build-mageia:
|
||||||
name: Build Mageia
|
name: Build Mageia
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -398,7 +398,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RPM_BUILD_NCPUS: "2"
|
RPM_BUILD_NCPUS: "2"
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
run: rpmbuild -ba strawberry.spec
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -410,7 +410,7 @@ jobs:
|
|||||||
|
|
||||||
build-debian:
|
build-debian:
|
||||||
name: Build Debian
|
name: Build Debian
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -471,21 +471,24 @@ jobs:
|
|||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
|
||||||
|
- name: Delete build directory
|
||||||
|
run: rm -rf build
|
||||||
- name: make deb
|
- name: make deb
|
||||||
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
run: dpkg-buildpackage -b -d -uc -us -nc -j4
|
||||||
- name: Copy deb
|
- name: Copy deb
|
||||||
run: cp ../*.deb .
|
run: cp ../*.deb .
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: debian-${{matrix.debian_version}}
|
name: debian-${{matrix.debian_version}}
|
||||||
path: "*.deb"
|
path: |
|
||||||
|
*.deb
|
||||||
|
|
||||||
|
|
||||||
build-ubuntu:
|
build-ubuntu:
|
||||||
name: Build Ubuntu
|
name: Build Ubuntu
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -549,9 +552,11 @@ jobs:
|
|||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
|
||||||
|
- name: Delete build directory
|
||||||
|
run: rm -rf build
|
||||||
- name: make deb
|
- name: make deb
|
||||||
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
run: dpkg-buildpackage -b -d -uc -us -nc -j4
|
||||||
- name: Copy deb
|
- name: Copy deb
|
||||||
run: cp ../*.deb ../*.ddeb .
|
run: cp ../*.deb ../*.ddeb .
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
@@ -565,7 +570,7 @@ jobs:
|
|||||||
|
|
||||||
upload-ubuntu-ppa:
|
upload-ubuntu-ppa:
|
||||||
name: Upload Ubuntu PPA
|
name: Upload Ubuntu PPA
|
||||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/1.1'))) && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/1.1')))
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -634,7 +639,7 @@ jobs:
|
|||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
|
||||||
- name: Delete build directory
|
- name: Delete build directory
|
||||||
run: rm -rf build
|
run: rm -rf build
|
||||||
- name: Import Ubuntu PPA GPG private key
|
- name: Import Ubuntu PPA GPG private key
|
||||||
@@ -651,9 +656,60 @@ jobs:
|
|||||||
run: dput ppa:jonaski/strawberry ../*_source.changes
|
run: dput ppa:jonaski/strawberry ../*_source.changes
|
||||||
|
|
||||||
|
|
||||||
|
build-freebsd:
|
||||||
|
name: Build FreeBSD
|
||||||
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: recursive
|
||||||
|
- name: Build FreeBSD
|
||||||
|
id: build-freebsd
|
||||||
|
uses: vmactions/freebsd-vm@v1.1.8
|
||||||
|
with:
|
||||||
|
usesh: true
|
||||||
|
mem: 4096
|
||||||
|
prepare: pkg install -y git cmake pkgconf boost-libs alsa-lib glib qt6-base qt6-tools sqlite gstreamer1 gstreamer1-plugins chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 icu kdsingleapplication googletest pulseaudio
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
cmake -E make_directory build
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE="Debug"
|
||||||
|
cmake --build build --config Debug --parallel 4
|
||||||
|
|
||||||
|
|
||||||
|
build-openbsd:
|
||||||
|
name: Build OpenBSD
|
||||||
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: recursive
|
||||||
|
- name: Build OpenBSD
|
||||||
|
id: build-openbsd
|
||||||
|
uses: vmactions/openbsd-vm@v1.1.5
|
||||||
|
with:
|
||||||
|
usesh: true
|
||||||
|
mem: 4096
|
||||||
|
prepare: pkg_add git cmake pkgconf boost glib2 qt6-qtbase qt6-qttools sqlite gstreamer1 gstreamer1-plugins-base chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf libgpod fftw3 icu4c kdsingleapplication pulseaudio
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
export LDFLAGS="-L/usr/local/lib"
|
||||||
|
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
cmake -E make_directory build
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE="Debug"
|
||||||
|
cmake --build build --config Debug --parallel 4
|
||||||
|
|
||||||
|
|
||||||
build-macos-public:
|
build-macos-public:
|
||||||
name: Build macOS Public
|
name: Build macOS Public
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -931,7 +987,7 @@ jobs:
|
|||||||
|
|
||||||
build-windows-mingw:
|
build-windows-mingw:
|
||||||
name: Build Windows MinGW
|
name: Build Windows MinGW
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -1120,7 +1176,7 @@ jobs:
|
|||||||
|
|
||||||
build-windows-msvc:
|
build-windows-msvc:
|
||||||
name: Build Windows MSVC
|
name: Build Windows MSVC
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -1396,6 +1452,11 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Download MSVC runtime
|
||||||
|
shell: bash
|
||||||
|
working-directory: build
|
||||||
|
run: curl -f -O -L https://aka.ms/vs/17/release/vc_redist.$(test "${{matrix.arch}}" = "x86_64" && echo "x64" || echo "${{matrix.arch}}").exe
|
||||||
|
|
||||||
- name: Create nsis installer
|
- name: Create nsis installer
|
||||||
shell: cmd
|
shell: cmd
|
||||||
working-directory: build
|
working-directory: build
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,6 +11,4 @@
|
|||||||
/out
|
/out
|
||||||
/CMakeSettings.json
|
/CMakeSettings.json
|
||||||
/dist/scripts/maketarball.sh
|
/dist/scripts/maketarball.sh
|
||||||
/dist/unix/strawberry.spec
|
|
||||||
/debian/changelog
|
/debian/changelog
|
||||||
/dist/macos/Info.plist
|
|
||||||
|
|||||||
@@ -25,17 +25,12 @@ include(cmake/ParseArguments.cmake)
|
|||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
set(LINUX ON)
|
set(LINUX ON)
|
||||||
endif()
|
endif()
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
|
||||||
set(FREEBSD ON)
|
|
||||||
endif()
|
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
|
||||||
set(OPENBSD ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(LINUX)
|
if(LINUX)
|
||||||
include(cmake/Rpm.cmake)
|
include(cmake/Rpm.cmake)
|
||||||
include(cmake/Deb.cmake)
|
include(cmake/Deb.cmake)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
include(cmake/Dmg.cmake)
|
include(cmake/Dmg.cmake)
|
||||||
endif()
|
endif()
|
||||||
@@ -392,7 +387,7 @@ add_executable(strawberry)
|
|||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE TRUE)
|
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE TRUE)
|
||||||
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/dist/macos/Info.plist")
|
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/dist/macos/Info.plist")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 AND NOT ENABLE_WIN32_CONSOLE)
|
if(WIN32 AND NOT ENABLE_WIN32_CONSOLE)
|
||||||
@@ -443,6 +438,7 @@ set(SOURCES
|
|||||||
src/core/enginemetadata.cpp
|
src/core/enginemetadata.cpp
|
||||||
src/core/songmimedata.cpp
|
src/core/songmimedata.cpp
|
||||||
src/core/platforminterface.cpp
|
src/core/platforminterface.cpp
|
||||||
|
src/core/standardpaths.cpp
|
||||||
|
|
||||||
src/utilities/strutils.cpp
|
src/utilities/strutils.cpp
|
||||||
src/utilities/envutils.cpp
|
src/utilities/envutils.cpp
|
||||||
@@ -1491,6 +1487,7 @@ endif()
|
|||||||
|
|
||||||
target_link_libraries(strawberry_lib PUBLIC
|
target_link_libraries(strawberry_lib PUBLIC
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
$<$<BOOL:${HAVE_BACKTRACE}>:${Backtrace_LIBRARIES}>
|
||||||
PkgConfig::GLIB
|
PkgConfig::GLIB
|
||||||
PkgConfig::GOBJECT
|
PkgConfig::GOBJECT
|
||||||
PkgConfig::SQLITE
|
PkgConfig::SQLITE
|
||||||
@@ -1522,7 +1519,6 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
$<$<BOOL:${HAVE_MTP}>:PkgConfig::LIBMTP>
|
$<$<BOOL:${HAVE_MTP}>:PkgConfig::LIBMTP>
|
||||||
$<$<BOOL:${HAVE_GPOD}>:PkgConfig::LIBGPOD PkgConfig::GDK_PIXBUF>
|
$<$<BOOL:${HAVE_GPOD}>:PkgConfig::LIBGPOD PkgConfig::GDK_PIXBUF>
|
||||||
$<$<BOOL:${HAVE_QTSPARKLE}>:PkgConfig::QTSPARKLE>
|
$<$<BOOL:${HAVE_QTSPARKLE}>:PkgConfig::QTSPARKLE>
|
||||||
$<$<BOOL:${FREEBSD}>:execinfo>
|
|
||||||
$<$<BOOL:${WIN32}>:dsound dwmapi getopt-win::getopt>
|
$<$<BOOL:${WIN32}>:dsound dwmapi getopt-win::getopt>
|
||||||
$<$<BOOL:${MSVC}>:WindowsApp>
|
$<$<BOOL:${MSVC}>:WindowsApp>
|
||||||
${SINGLEAPPLICATION_LIBRARIES}
|
${SINGLEAPPLICATION_LIBRARIES}
|
||||||
|
|||||||
25
Changelog
25
Changelog
@@ -2,6 +2,31 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
Version 1.2.4 (2025.01.10):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed Spotify songs not being available for scrobbling.
|
||||||
|
* Fixed leading "A" and "The" articles being skipped for album sort text.
|
||||||
|
* Fixed thread safety issue when validating playlist songs on startup.
|
||||||
|
* Fixed filter search not ignoring space after colon when using column based search.
|
||||||
|
* Fixed KGlobalAccel to use capitalized application name.
|
||||||
|
* Fixed slash not properly handled when saving a playlist (#1624).
|
||||||
|
* (Unix) Fixed collection scanner so it ignores special filesystem paths (/sys, /proc, /run, etc) (#1615).
|
||||||
|
* (Windows) Fixed smart playlist wizard not respecting dark mode with Windows 11 style (#1639).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Use XSPF "title" as playlist name when loading and saving playlists (#1624).
|
||||||
|
* Added support for using album ID when receving album covers for Subsonic songs (#1636).
|
||||||
|
* Added option for preserving directory structure when trascoding songs (#1637).
|
||||||
|
* (Windows) Always run MSVC runtime installer to possible fix issues when there is an older runtime installed.
|
||||||
|
|
||||||
|
Version 1.2.3 (2024.12.08):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed libcdio NULL related compilation error on FreeBSD (#1610).
|
||||||
|
* Fixed missing seek when starting playback of a CUE song (#1568).
|
||||||
|
* Fixed "QDBusObjectPath: invalid path" error.
|
||||||
|
|
||||||
Version 1.2.2 (2024.11.23):
|
Version 1.2.2 (2024.11.23):
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ find_program(LSB_RELEASE_EXEC lsb_release)
|
|||||||
find_program(DPKG_BUILDPACKAGE dpkg-buildpackage)
|
find_program(DPKG_BUILDPACKAGE dpkg-buildpackage)
|
||||||
|
|
||||||
if (LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
|
if (LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
|
||||||
add_custom_target(deb WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us)
|
add_custom_target(deb WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us -nc -j4)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ if(MACDEPLOYQT_EXECUTABLE)
|
|||||||
|
|
||||||
add_custom_target(deploy
|
add_custom_target(deploy
|
||||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/{Frameworks,Resources}
|
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/{Frameworks,Resources}
|
||||||
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
|
COMMAND cp -v ${CMAKE_BINARY_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
|
||||||
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/strawberry.icns ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources/
|
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/strawberry.icns ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources/
|
||||||
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh ${CMAKE_BINARY_DIR}/strawberry.app
|
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh ${CMAKE_BINARY_DIR}/strawberry.app
|
||||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}
|
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
|
|||||||
add_custom_target(rpm
|
add_custom_target(rpm
|
||||||
COMMAND ${CMAKE_SOURCE_DIR}/dist/scripts/maketarball.sh
|
COMMAND ${CMAKE_SOURCE_DIR}/dist/scripts/maketarball.sh
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy strawberry-${STRAWBERRY_VERSION_PACKAGE}.tar.xz ${RPMBUILD_DIR}/SOURCES/
|
COMMAND ${CMAKE_COMMAND} -E copy strawberry-${STRAWBERRY_VERSION_PACKAGE}.tar.xz ${RPMBUILD_DIR}/SOURCES/
|
||||||
COMMAND ${RPMBUILD_EXEC} -ba ${CMAKE_SOURCE_DIR}/dist/unix/strawberry.spec
|
COMMAND ${RPMBUILD_EXEC} -ba ${CMAKE_BINARY_DIR}/strawberry.spec
|
||||||
)
|
)
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||||
set(STRAWBERRY_VERSION_MINOR 2)
|
set(STRAWBERRY_VERSION_MINOR 2)
|
||||||
set(STRAWBERRY_VERSION_PATCH 2)
|
set(STRAWBERRY_VERSION_PATCH 4)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
set(INCLUDE_GIT_REVISION OFF)
|
set(INCLUDE_GIT_REVISION OFF)
|
||||||
|
|||||||
3
debian/clean
vendored
Normal file
3
debian/clean
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
dist/scripts/maketarball.sh
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles/
|
||||||
1
debian/compat
vendored
1
debian/compat
vendored
@@ -1 +0,0 @@
|
|||||||
11
|
|
||||||
4
debian/control
vendored
4
debian/control
vendored
@@ -2,7 +2,7 @@ Source: strawberry
|
|||||||
Section: sound
|
Section: sound
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Jonas Kvinge <jonas@jkvinge.net>
|
Maintainer: Jonas Kvinge <jonas@jkvinge.net>
|
||||||
Build-Depends: debhelper (>= 11),
|
Build-Depends: debhelper-compat (= 12),
|
||||||
git,
|
git,
|
||||||
make,
|
make,
|
||||||
cmake,
|
cmake,
|
||||||
@@ -28,7 +28,7 @@ Build-Depends: debhelper (>= 11),
|
|||||||
libchromaprint-dev,
|
libchromaprint-dev,
|
||||||
libfftw3-dev,
|
libfftw3-dev,
|
||||||
libebur128-dev
|
libebur128-dev
|
||||||
Standards-Version: 4.6.1
|
Standards-Version: 4.7.0
|
||||||
|
|
||||||
Package: strawberry
|
Package: strawberry
|
||||||
Architecture: any
|
Architecture: any
|
||||||
|
|||||||
15
debian/rules
vendored
15
debian/rules
vendored
@@ -1,17 +1,10 @@
|
|||||||
#!/usr/bin/make -f
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
%:
|
export DH_VERBOSE=1
|
||||||
dh $@ --buildsystem=cmake -builddirectory=build
|
export DEB_BUILD_MAINT_OPTIONS=hardening=+all
|
||||||
|
|
||||||
override_dh_auto_clean:
|
|
||||||
rm -f dist/macos/Info.plist
|
|
||||||
rm -f dist/unix/strawberry.spec
|
|
||||||
rm -f dist/scripts/maketarball.sh
|
|
||||||
rm -f dist/windows/strawberry.nsi
|
|
||||||
rm -f src/translations/translations.pot
|
|
||||||
dh_auto_clean
|
|
||||||
|
|
||||||
override_dh_installchangelogs:
|
override_dh_installchangelogs:
|
||||||
dh_installchangelogs Changelog
|
dh_installchangelogs Changelog
|
||||||
|
|
||||||
override_dh_auto_test:
|
%:
|
||||||
|
dh $@
|
||||||
|
|||||||
14
dist/CMakeLists.txt
vendored
14
dist/CMakeLists.txt
vendored
@@ -1,7 +1,7 @@
|
|||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh.in ${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh.in ${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh @ONLY)
|
||||||
if(RPM_DISTRO AND RPM_DATE)
|
if(RPM_DISTRO AND RPM_DATE)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec.in ${CMAKE_BINARY_DIR}/strawberry.spec @ONLY)
|
||||||
endif(RPM_DISTRO AND RPM_DATE)
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET})
|
if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET})
|
||||||
@@ -9,13 +9,13 @@ if(APPLE)
|
|||||||
else()
|
else()
|
||||||
set(LSMinimumSystemVersion 12.0)
|
set(LSMinimumSystemVersion 12.0)
|
||||||
endif()
|
endif()
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist)
|
||||||
endif(APPLE)
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/windres.rc.in ${CMAKE_BINARY_DIR}/windres.rc)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/windres.rc.in ${CMAKE_BINARY_DIR}/windres.rc)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_BINARY_DIR}/strawberry.nsi @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_BINARY_DIR}/strawberry.nsi @ONLY)
|
||||||
endif(WIN32)
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
install(FILES ../data/icons/48x48/strawberry.png DESTINATION share/icons/hicolor/48x48/apps/)
|
install(FILES ../data/icons/48x48/strawberry.png DESTINATION share/icons/hicolor/48x48/apps/)
|
||||||
@@ -24,9 +24,9 @@ if(UNIX AND NOT APPLE)
|
|||||||
install(FILES unix/org.strawberrymusicplayer.strawberry.desktop DESTINATION share/applications)
|
install(FILES unix/org.strawberrymusicplayer.strawberry.desktop DESTINATION share/applications)
|
||||||
install(FILES unix/org.strawberrymusicplayer.strawberry.appdata.xml DESTINATION share/metainfo)
|
install(FILES unix/org.strawberrymusicplayer.strawberry.appdata.xml DESTINATION share/metainfo)
|
||||||
install(FILES unix/strawberry.1 DESTINATION share/man/man1)
|
install(FILES unix/strawberry.1 DESTINATION share/man/man1)
|
||||||
endif(UNIX AND NOT APPLE)
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents")
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents")
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/strawberry.icns" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources")
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/strawberry.icns" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -51,6 +51,8 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="1.2.4" date="2025-01-10"/>
|
||||||
|
<release version="1.2.3" date="2024-12-08"/>
|
||||||
<release version="1.2.2" date="2024-11-23"/>
|
<release version="1.2.2" date="2024-11-23"/>
|
||||||
<release version="1.2.1" date="2024-11-21"/>
|
<release version="1.2.1" date="2024-11-21"/>
|
||||||
<release version="1.1.3" date="2024-09-21"/>
|
<release version="1.1.3" date="2024-09-21"/>
|
||||||
|
|||||||
10
dist/windows/strawberry.nsi.in
vendored
10
dist/windows/strawberry.nsi.in
vendored
@@ -208,14 +208,16 @@ FunctionEnd
|
|||||||
!ifdef msvc
|
!ifdef msvc
|
||||||
!define vc_redist_file "vc_redist.${arch}.exe"
|
!define vc_redist_file "vc_redist.${arch}.exe"
|
||||||
Function InstallMSVCRuntime
|
Function InstallMSVCRuntime
|
||||||
${registry::Read} "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${arch}" "Version" $R0 $R1
|
SetOutPath "$TEMP"
|
||||||
${If} $R0 == ""
|
File "${vc_redist_file}"
|
||||||
|
; ${registry::Read} "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${arch}" "Version" $R0 $R1
|
||||||
|
; ${If} $R0 == ""
|
||||||
SetDetailsView hide
|
SetDetailsView hide
|
||||||
inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
|
; inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
|
||||||
ExecWait '"$TEMP\${vc_redist_file}" /install /passive'
|
ExecWait '"$TEMP\${vc_redist_file}" /install /passive'
|
||||||
Delete "$TEMP\${vc_redist_file}"
|
Delete "$TEMP\${vc_redist_file}"
|
||||||
SetDetailsView show
|
SetDetailsView show
|
||||||
${EndIf}
|
; ${EndIf}
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
|
|||||||
@@ -50,14 +50,14 @@
|
|||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
#include <QNetworkDiskCache>
|
#include <QNetworkDiskCache>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "includes/scoped_ptr.h"
|
#include "includes/scoped_ptr.h"
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "core/standardpaths.h"
|
||||||
#include "core/database.h"
|
#include "core/database.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/logging.h"
|
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "core/songmimedata.h"
|
#include "core/songmimedata.h"
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
@@ -114,7 +114,7 @@ CollectionModel::CollectionModel(const SharedPtr<CollectionBackend> backend, con
|
|||||||
pixmap_no_cover_ = nocover.pixmap(nocover_sizes.last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
pixmap_no_cover_ = nocover.pixmap(nocover_sizes.last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
icon_disk_cache_->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/' + QLatin1String(kPixmapDiskCacheDir) + u'-' + Song::TextForSource(backend_->source()));
|
icon_disk_cache_->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u'/' + QLatin1String(kPixmapDiskCacheDir) + u'-' + Song::TextForSource(backend_->source()));
|
||||||
|
|
||||||
QObject::connect(&*backend_, &CollectionBackend::SongsAdded, this, &CollectionModel::AddReAddOrUpdate);
|
QObject::connect(&*backend_, &CollectionBackend::SongsAdded, this, &CollectionModel::AddReAddOrUpdate);
|
||||||
QObject::connect(&*backend_, &CollectionBackend::SongsChanged, this, &CollectionModel::AddReAddOrUpdate);
|
QObject::connect(&*backend_, &CollectionBackend::SongsChanged, this, &CollectionModel::AddReAddOrUpdate);
|
||||||
@@ -1095,7 +1095,7 @@ QString CollectionModel::SortText(const GroupBy group_by, const Song &song, cons
|
|||||||
case GroupBy::Artist:
|
case GroupBy::Artist:
|
||||||
return SortTextForArtist(song.artist(), sort_skips_articles);
|
return SortTextForArtist(song.artist(), sort_skips_articles);
|
||||||
case GroupBy::Album:
|
case GroupBy::Album:
|
||||||
return SortTextForArtist(song.album(), sort_skips_articles);
|
return SortText(song.album());
|
||||||
case GroupBy::AlbumDisc:
|
case GroupBy::AlbumDisc:
|
||||||
return song.album() + SortTextForNumber(std::max(0, song.disc()));
|
return song.album() + SortTextForNumber(std::max(0, song.disc()));
|
||||||
case GroupBy::YearAlbum:
|
case GroupBy::YearAlbum:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
|
#include <QStorageInfo>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "utilities/imageutils.h"
|
#include "utilities/imageutils.h"
|
||||||
#include "constants/timeconstants.h"
|
#include "constants/timeconstants.h"
|
||||||
|
#include "constants/filesystemconstants.h"
|
||||||
#include "tagreader/tagreaderclient.h"
|
#include "tagreader/tagreaderclient.h"
|
||||||
#include "collectiondirectory.h"
|
#include "collectiondirectory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
@@ -464,6 +466,24 @@ CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
|||||||
|
|
||||||
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
||||||
|
|
||||||
|
{
|
||||||
|
const QFileInfo path_info(dir.path);
|
||||||
|
if (path_info.isSymbolicLink()) {
|
||||||
|
const QStorageInfo storage_info(path_info.symLinkTarget());
|
||||||
|
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||||
|
qLog(Warning) << "Ignoring collection directory path" << dir.path << "which is a symbolic link to path" << path_info.symLinkTarget() << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const QStorageInfo storage_info(dir.path);
|
||||||
|
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||||
|
qLog(Warning) << "Ignoring collection directory path" << dir.path << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CancelStop();
|
CancelStop();
|
||||||
|
|
||||||
watched_dirs_[dir.id] = dir;
|
watched_dirs_[dir.id] = dir;
|
||||||
@@ -506,17 +526,29 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
|
|||||||
|
|
||||||
void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
|
void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
|
||||||
|
|
||||||
QFileInfo path_info(path);
|
const QFileInfo path_info(path);
|
||||||
|
|
||||||
// Do not scan symlinked dirs that are already in collection
|
|
||||||
if (path_info.isSymLink()) {
|
if (path_info.isSymLink()) {
|
||||||
QString real_path = path_info.symLinkTarget();
|
const QString real_path = path_info.symLinkTarget();
|
||||||
|
const QStorageInfo storage_info(real_path);
|
||||||
|
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||||
|
qLog(Warning) << "Ignoring symbolic link" << path << "which links to" << real_path << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Do not scan symlinked dirs that are already in collection
|
||||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
if (real_path.startsWith(dir.path)) {
|
if (real_path.startsWith(dir.path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
const QStorageInfo storage_info(path);
|
||||||
|
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||||
|
qLog(Warning) << "Ignoring path" << path << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool songs_missing_fingerprint = false;
|
bool songs_missing_fingerprint = false;
|
||||||
bool songs_missing_loudness_characteristics = false;
|
bool songs_missing_loudness_characteristics = false;
|
||||||
@@ -556,32 +588,40 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
|
|
||||||
if (stop_or_abort_requested()) return;
|
if (stop_or_abort_requested()) return;
|
||||||
|
|
||||||
QString child(it.next());
|
const QString child_filepath = it.next();
|
||||||
QFileInfo child_info(child);
|
const QFileInfo child_fileinfo(child_filepath);
|
||||||
|
|
||||||
if (child_info.isDir()) {
|
if (child_fileinfo.isSymLink()) {
|
||||||
if (!t->HasSeenSubdir(child)) {
|
QStorageInfo storage_info(child_fileinfo.symLinkTarget());
|
||||||
|
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||||
|
qLog(Warning) << "Ignoring symbolic link" << child_filepath << "which links to" << child_fileinfo.symLinkTarget() << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child_fileinfo.isDir()) {
|
||||||
|
if (!t->HasSeenSubdir(child_filepath)) {
|
||||||
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
|
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
|
||||||
CollectionSubdirectory new_subdir;
|
CollectionSubdirectory new_subdir;
|
||||||
new_subdir.directory_id = -1;
|
new_subdir.directory_id = -1;
|
||||||
new_subdir.path = child;
|
new_subdir.path = child_filepath;
|
||||||
new_subdir.mtime = child_info.lastModified().toSecsSinceEpoch();
|
new_subdir.mtime = child_fileinfo.lastModified().toSecsSinceEpoch();
|
||||||
my_new_subdirs << new_subdir;
|
my_new_subdirs << new_subdir;
|
||||||
}
|
}
|
||||||
t->AddToProgress(1);
|
t->AddToProgress(1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QString ext_part(ExtensionPart(child));
|
QString ext_part(ExtensionPart(child_filepath));
|
||||||
QString dir_part(DirectoryPart(child));
|
QString dir_part(DirectoryPart(child_filepath));
|
||||||
if (Song::kRejectedExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == "qt_temp"_L1) {
|
if (Song::kRejectedExtensions.contains(child_fileinfo.suffix(), Qt::CaseInsensitive) || child_fileinfo.baseName() == "qt_temp"_L1) {
|
||||||
t->AddToProgress(1);
|
t->AddToProgress(1);
|
||||||
}
|
}
|
||||||
else if (sValidImages.contains(ext_part)) {
|
else if (sValidImages.contains(ext_part)) {
|
||||||
album_art[dir_part] << child;
|
album_art[dir_part] << child_filepath;
|
||||||
t->AddToProgress(1);
|
t->AddToProgress(1);
|
||||||
}
|
}
|
||||||
else if (tagreader_client_->IsMediaFileBlocking(child)) {
|
else if (tagreader_client_->IsMediaFileBlocking(child_filepath)) {
|
||||||
files_on_disk << child;
|
files_on_disk << child_filepath;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
t->AddToProgress(1);
|
t->AddToProgress(1);
|
||||||
@@ -828,7 +868,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
|||||||
qLog(Error) << "Could not open CUE file" << matching_cue << "for reading:" << cue_file.errorString();
|
qLog(Error) << "Could not open CUE file" << matching_cue << "for reading:" << cue_file.errorString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const SongList songs = cue_parser_->Load(&cue_file, matching_cue, path, false);
|
const SongList songs = cue_parser_->Load(&cue_file, matching_cue, path, false).songs;
|
||||||
cue_file.close();
|
cue_file.close();
|
||||||
|
|
||||||
// Update every song that's in the CUE and collection
|
// Update every song that's in the CUE and collection
|
||||||
@@ -915,7 +955,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
|||||||
// Also, watch out for incorrect media files.
|
// Also, watch out for incorrect media files.
|
||||||
// Playlist parser for CUEs considers every entry in sheet valid, and we don't want invalid media getting into collection!
|
// Playlist parser for CUEs considers every entry in sheet valid, and we don't want invalid media getting into collection!
|
||||||
QString file_nfd = file.normalized(QString::NormalizationForm_D);
|
QString file_nfd = file.normalized(QString::NormalizationForm_D);
|
||||||
SongList cue_songs = cue_parser_->Load(&cue_file, matching_cue, path, false);
|
SongList cue_songs = cue_parser_->Load(&cue_file, matching_cue, path, false).songs;
|
||||||
cue_file.close();
|
cue_file.close();
|
||||||
songs.reserve(cue_songs.count());
|
songs.reserve(cue_songs.count());
|
||||||
for (Song &cue_song : cue_songs) {
|
for (Song &cue_song : cue_songs) {
|
||||||
@@ -1153,7 +1193,7 @@ void CollectionWatcher::RescanPathsNow() {
|
|||||||
|
|
||||||
QMap<QString, quint64> subdir_files_count;
|
QMap<QString, quint64> subdir_files_count;
|
||||||
for (const QString &path : paths) {
|
for (const QString &path : paths) {
|
||||||
quint64 files_count = FilesCountForPath(&transaction, path);
|
const quint64 files_count = FilesCountForPath(&transaction, path);
|
||||||
subdir_files_count[path] = files_count;
|
subdir_files_count[path] = files_count;
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
}
|
}
|
||||||
@@ -1316,18 +1356,45 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
|||||||
|
|
||||||
quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &path) {
|
quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &path) {
|
||||||
|
|
||||||
|
const QFileInfo path_info(path);
|
||||||
|
if (path_info.isSymLink()) {
|
||||||
|
const QString real_path = path_info.symLinkTarget();
|
||||||
|
const QStorageInfo storage_info(real_path);
|
||||||
|
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
|
if (real_path.startsWith(dir.path)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const QStorageInfo storage_info(path);
|
||||||
|
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
quint64 i = 0;
|
quint64 i = 0;
|
||||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
|
||||||
if (stop_or_abort_requested()) break;
|
if (stop_or_abort_requested()) break;
|
||||||
|
|
||||||
QString child = it.next();
|
const QString child_filepath = it.next();
|
||||||
QFileInfo path_info(child);
|
const QFileInfo child_fileinfo(child_filepath);
|
||||||
|
|
||||||
|
if (child_fileinfo.isDir()) {
|
||||||
|
if (child_fileinfo.isSymLink()) {
|
||||||
|
|
||||||
|
const QString real_path = child_fileinfo.symLinkTarget();
|
||||||
|
|
||||||
|
QStorageInfo storage_info(real_path);
|
||||||
|
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (path_info.isDir()) {
|
|
||||||
if (path_info.isSymLink()) {
|
|
||||||
QString real_path = path_info.symLinkTarget();
|
|
||||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
if (real_path.startsWith(dir.path)) {
|
if (real_path.startsWith(dir.path)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1335,9 +1402,9 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!t->HasSeenSubdir(child) && !path_info.isHidden()) {
|
if (!t->HasSeenSubdir(child_filepath) && !child_fileinfo.isHidden()) {
|
||||||
// We haven't seen this subdirectory before, so we need to include the file count for this directory too.
|
// We haven't seen this subdirectory before, so we need to include the file count for this directory too.
|
||||||
i += FilesCountForPath(t, child);
|
i += FilesCountForPath(t, child_filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1385,7 +1452,7 @@ void CollectionWatcher::RescanSongs(const SongList &songs) {
|
|||||||
if (stop_or_abort_requested()) break;
|
if (stop_or_abort_requested()) break;
|
||||||
if (subdir.path != song_path) continue;
|
if (subdir.path != song_path) continue;
|
||||||
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
|
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
|
||||||
quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
const quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
||||||
ScanSubdirectory(song_path, subdir, files_count, &transaction);
|
ScanSubdirectory(song_path, subdir, files_count, &transaction);
|
||||||
scanned_paths << subdir.path;
|
scanned_paths << subdir.path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
31
src/constants/filesystemconstants.h
Normal file
31
src/constants/filesystemconstants.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILESYSTEMCONSTANTS_H
|
||||||
|
#define FILESYSTEMCONSTANTS_H
|
||||||
|
|
||||||
|
#include <QByteArrayList>
|
||||||
|
|
||||||
|
const QByteArrayList kRejectedFileSystems = QByteArrayList() << "proc"
|
||||||
|
<< "sysfs"
|
||||||
|
<< "tmpfs"
|
||||||
|
<< "devtmpfs";
|
||||||
|
|
||||||
|
|
||||||
|
#endif // FILESYSTEMCONSTANTS_H
|
||||||
@@ -37,6 +37,7 @@ constexpr char kPassword[] = "password";
|
|||||||
constexpr char kHTTP2[] = "http2";
|
constexpr char kHTTP2[] = "http2";
|
||||||
constexpr char kVerifyCertificate[] = "verifycertificate";
|
constexpr char kVerifyCertificate[] = "verifycertificate";
|
||||||
constexpr char kDownloadAlbumCovers[] = "downloadalbumcovers";
|
constexpr char kDownloadAlbumCovers[] = "downloadalbumcovers";
|
||||||
|
constexpr char kUseAlbumIdForAlbumCovers[] = "usealbumidforalbumcovers";
|
||||||
constexpr char kServerSideScrobbling[] = "serversidescrobbling";
|
constexpr char kServerSideScrobbling[] = "serversidescrobbling";
|
||||||
constexpr char kAuthMethod[] = "authmethod";
|
constexpr char kAuthMethod[] = "authmethod";
|
||||||
|
|
||||||
|
|||||||
@@ -39,10 +39,10 @@
|
|||||||
#include <QSqlDriver>
|
#include <QSqlDriver>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QScopeGuard>
|
#include <QScopeGuard>
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "logging.h"
|
||||||
|
#include "standardpaths.h"
|
||||||
#include "taskmanager.h"
|
#include "taskmanager.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "sqlquery.h"
|
#include "sqlquery.h"
|
||||||
@@ -78,7 +78,7 @@ Database::Database(SharedPtr<TaskManager> task_manager, QObject *parent, const Q
|
|||||||
connection_id_ = sNextConnectionId++;
|
connection_id_ = sNextConnectionId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
directory_ = QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
|
directory_ = QDir::toNativeSeparators(StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation)).replace(u"Strawberry"_s, u"strawberry"_s);
|
||||||
|
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_);
|
||||||
Connect();
|
Connect();
|
||||||
|
|||||||
@@ -27,10 +27,10 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
#include "standardpaths.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "includes/iconmapper.h"
|
#include "includes/iconmapper.h"
|
||||||
#include "iconloader.h"
|
#include "iconloader.h"
|
||||||
@@ -51,7 +51,7 @@ void IconLoader::Init() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QDir dir;
|
QDir dir;
|
||||||
if (dir.exists(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/icons"_s)) {
|
if (dir.exists(StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/icons"_s)) {
|
||||||
custom_icons_ = true;
|
custom_icons_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (custom_icons_) {
|
if (custom_icons_) {
|
||||||
QString custom_icon_path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/icons/%1x%2/%3.png"_s;
|
QString custom_icon_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/icons/%1x%2/%3.png"_s;
|
||||||
for (int s : std::as_const(sizes)) {
|
for (int s : std::as_const(sizes)) {
|
||||||
QString filename(custom_icon_path.arg(s).arg(s).arg(name));
|
QString filename(custom_icon_path.arg(s).arg(s).arg(name));
|
||||||
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));
|
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,13 +18,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QVariant>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
Settings::Settings(QObject *parent)
|
Settings::Settings(QObject *parent)
|
||||||
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||||
|
: QSettings(QCoreApplication::organizationName().toLower(), QCoreApplication::applicationName().toLower(), parent) {}
|
||||||
|
#else
|
||||||
: QSettings(parent) {}
|
: QSettings(parent) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Settings::Settings(const QSettings::Scope scope, QObject *parent)
|
||||||
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||||
|
: QSettings(scope, QCoreApplication::organizationName().toLower(), QCoreApplication::applicationName().toLower(), parent) {}
|
||||||
|
#else
|
||||||
|
: QSettings(scope, parent) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
Settings::Settings(const QString &filename, const Format format, QObject *parent)
|
Settings::Settings(const QString &filename, const Format format, QObject *parent)
|
||||||
: QSettings(filename, format, parent) {}
|
: QSettings(filename, format, parent) {}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,14 +21,14 @@
|
|||||||
#define SETTINGS_H
|
#define SETTINGS_H
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QObject>
|
#include <QString>
|
||||||
#include <QVariant>
|
|
||||||
|
|
||||||
class Settings : public QSettings {
|
class Settings : public QSettings {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Settings(QObject *parent = nullptr);
|
explicit Settings(QObject *parent = nullptr);
|
||||||
|
explicit Settings(const QSettings::Scope scope, QObject *parent = nullptr);
|
||||||
explicit Settings(const QString &filename, const Format format, QObject *parent = nullptr);
|
explicit Settings(const QString &filename, const Format format, QObject *parent = nullptr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,11 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QSqlRecord>
|
#include <QSqlRecord>
|
||||||
|
|
||||||
#include <taglib/tstring.h>
|
#include <taglib/tstring.h>
|
||||||
|
|
||||||
|
#include "core/standardpaths.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/enginemetadata.h"
|
#include "core/enginemetadata.h"
|
||||||
#include "utilities/strutils.h"
|
#include "utilities/strutils.h"
|
||||||
@@ -249,7 +249,8 @@ const QStringList Song::kRejectedExtensions = QStringList() << u"tmp"_s
|
|||||||
<< u"z"_s
|
<< u"z"_s
|
||||||
<< u"zip"_s
|
<< u"zip"_s
|
||||||
<< u"rar"_s
|
<< u"rar"_s
|
||||||
<< u"wvc"_s;
|
<< u"wvc"_s
|
||||||
|
<< u"zst"_s;
|
||||||
|
|
||||||
struct Song::Private : public QSharedData {
|
struct Song::Private : public QSharedData {
|
||||||
|
|
||||||
@@ -1334,24 +1335,24 @@ QString Song::ImageCacheDir(const Source source) {
|
|||||||
|
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case Source::Collection:
|
case Source::Collection:
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/collectionalbumcovers"_s;
|
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/collectionalbumcovers"_s;
|
||||||
case Source::Subsonic:
|
case Source::Subsonic:
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/subsonicalbumcovers"_s;
|
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/subsonicalbumcovers"_s;
|
||||||
case Source::Tidal:
|
case Source::Tidal:
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/tidalalbumcovers"_s;
|
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/tidalalbumcovers"_s;
|
||||||
case Source::Spotify:
|
case Source::Spotify:
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/spotifyalbumcovers"_s;
|
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/spotifyalbumcovers"_s;
|
||||||
case Source::Qobuz:
|
case Source::Qobuz:
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/qobuzalbumcovers"_s;
|
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/qobuzalbumcovers"_s;
|
||||||
case Source::Device:
|
case Source::Device:
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/devicealbumcovers"_s;
|
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/devicealbumcovers"_s;
|
||||||
case Source::LocalFile:
|
case Source::LocalFile:
|
||||||
case Source::CDDA:
|
case Source::CDDA:
|
||||||
case Source::Stream:
|
case Source::Stream:
|
||||||
case Source::SomaFM:
|
case Source::SomaFM:
|
||||||
case Source::RadioParadise:
|
case Source::RadioParadise:
|
||||||
case Source::Unknown:
|
case Source::Unknown:
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/albumcovers"_s;
|
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/albumcovers"_s;
|
||||||
}
|
}
|
||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
|
|||||||
// It's a CUE - create virtual tracks
|
// It's a CUE - create virtual tracks
|
||||||
QFile cue(matching_cue);
|
QFile cue(matching_cue);
|
||||||
if (cue.open(QIODevice::ReadOnly)) {
|
if (cue.open(QIODevice::ReadOnly)) {
|
||||||
const SongList songs = cue_parser_->Load(&cue, matching_cue, QDir(filename.section(u'/', 0, -2)));
|
const SongList songs = cue_parser_->Load(&cue, matching_cue, QDir(filename.section(u'/', 0, -2))).songs;
|
||||||
cue.close();
|
cue.close();
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
if (song.is_valid()) songs_ << song;
|
if (song.is_valid()) songs_ << song;
|
||||||
@@ -348,7 +348,9 @@ void SongLoader::LoadPlaylist(ParserBase *parser, const QString &filename) {
|
|||||||
|
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
songs_ = parser->Load(&file, filename, QFileInfo(filename).path());
|
const ParserBase::LoadResult result = parser->Load(&file, filename, QFileInfo(filename).path());
|
||||||
|
songs_ = result.songs;
|
||||||
|
playlist_name_ = result.playlist_name;
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -424,7 +426,9 @@ void SongLoader::StopTypefind() {
|
|||||||
// Parse the playlist
|
// Parse the playlist
|
||||||
QBuffer buf(&buffer_);
|
QBuffer buf(&buffer_);
|
||||||
if (buf.open(QIODevice::ReadOnly)) {
|
if (buf.open(QIODevice::ReadOnly)) {
|
||||||
songs_ = parser_->Load(&buf);
|
const ParserBase::LoadResult result = parser_->Load(&buf);
|
||||||
|
songs_ = result.songs;
|
||||||
|
playlist_name_ = result.playlist_name;
|
||||||
buf.close();
|
buf.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class SongLoader : public QObject {
|
|||||||
|
|
||||||
const QUrl &url() const { return url_; }
|
const QUrl &url() const { return url_; }
|
||||||
const SongList &songs() const { return songs_; }
|
const SongList &songs() const { return songs_; }
|
||||||
|
const QString &playlist_name() const { return playlist_name_; }
|
||||||
|
|
||||||
int timeout() const { return timeout_; }
|
int timeout() const { return timeout_; }
|
||||||
void set_timeout(int msec) { timeout_ = msec; }
|
void set_timeout(int msec) { timeout_ = msec; }
|
||||||
@@ -141,6 +142,7 @@ class SongLoader : public QObject {
|
|||||||
|
|
||||||
QUrl url_;
|
QUrl url_;
|
||||||
SongList songs_;
|
SongList songs_;
|
||||||
|
QString playlist_name_;
|
||||||
|
|
||||||
const SharedPtr<UrlHandlers> url_handlers_;
|
const SharedPtr<UrlHandlers> url_handlers_;
|
||||||
const SharedPtr<CollectionBackendInterface> collection_backend_;
|
const SharedPtr<CollectionBackendInterface> collection_backend_;
|
||||||
|
|||||||
82
src/core/standardpaths.cpp
Normal file
82
src/core/standardpaths.cpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QString>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
#include "standardpaths.h"
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
void StandardPaths::AppendOrganizationAndApplication(QString &path) {
|
||||||
|
|
||||||
|
const QString organization_name = QCoreApplication::organizationName().toLower();
|
||||||
|
if (!organization_name.isEmpty()) {
|
||||||
|
path += u'/' + organization_name;
|
||||||
|
}
|
||||||
|
const QString application_name = QCoreApplication::applicationName().toLower();
|
||||||
|
if (!application_name.isEmpty()) {
|
||||||
|
path += u'/' + application_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString StandardPaths::WritableLocation(const StandardLocation type) {
|
||||||
|
|
||||||
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||||
|
switch (type) {
|
||||||
|
case StandardLocation::CacheLocation:
|
||||||
|
case StandardLocation::GenericCacheLocation:{
|
||||||
|
QString cache_location = qEnvironmentVariable("XDG_CACHE_HOME");
|
||||||
|
if (!cache_location.startsWith(u'/')) {
|
||||||
|
cache_location.clear();
|
||||||
|
}
|
||||||
|
if (cache_location.isEmpty()) {
|
||||||
|
cache_location = QDir::homePath() + "/.cache"_L1;
|
||||||
|
}
|
||||||
|
if (type == QStandardPaths::CacheLocation) {
|
||||||
|
AppendOrganizationAndApplication(cache_location);
|
||||||
|
}
|
||||||
|
return cache_location;
|
||||||
|
}
|
||||||
|
case StandardLocation::AppDataLocation:
|
||||||
|
case StandardLocation::AppLocalDataLocation:
|
||||||
|
case StandardLocation::GenericDataLocation:{
|
||||||
|
QString data_location = qEnvironmentVariable("XDG_DATA_HOME");
|
||||||
|
if (!data_location.startsWith(u'/')) {
|
||||||
|
data_location.clear();
|
||||||
|
}
|
||||||
|
if (data_location.isEmpty()) {
|
||||||
|
data_location = QDir::homePath() + "/.local/share"_L1;
|
||||||
|
}
|
||||||
|
if (type == StandardLocation::AppDataLocation || type == StandardLocation::AppLocalDataLocation) {
|
||||||
|
AppendOrganizationAndApplication(data_location);
|
||||||
|
}
|
||||||
|
return data_location;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return QStandardPaths::writableLocation(type);
|
||||||
|
|
||||||
|
}
|
||||||
36
src/core/standardpaths.h
Normal file
36
src/core/standardpaths.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STANDARDPATHS_H
|
||||||
|
#define STANDARDPATHS_H
|
||||||
|
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
class StandardPaths {
|
||||||
|
Q_GADGET
|
||||||
|
|
||||||
|
public:
|
||||||
|
using StandardLocation = QStandardPaths::StandardLocation;
|
||||||
|
static QString WritableLocation(const StandardLocation type);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void AppendOrganizationAndApplication(QString &path);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STANDARDPATHS_H
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QNetworkDiskCache>
|
#include <QNetworkDiskCache>
|
||||||
@@ -32,6 +31,7 @@
|
|||||||
#include <QAbstractNetworkCache>
|
#include <QAbstractNetworkCache>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "standardpaths.h"
|
||||||
#include "threadsafenetworkdiskcache.h"
|
#include "threadsafenetworkdiskcache.h"
|
||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
@@ -48,9 +48,9 @@ ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent) : QAbstr
|
|||||||
if (!sCache) {
|
if (!sCache) {
|
||||||
sCache = new QNetworkDiskCache;
|
sCache = new QNetworkDiskCache;
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + u"/strawberry/networkcache"_s);
|
sCache->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation) + u"/strawberry/networkcache"_s);
|
||||||
#else
|
#else
|
||||||
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/networkcache"_s);
|
sCache->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/networkcache"_s);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QStandardPaths>
|
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/standardpaths.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/temporaryfile.h"
|
#include "core/temporaryfile.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
@@ -42,7 +42,7 @@ using namespace Qt::Literals::StringLiterals;
|
|||||||
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(const SharedPtr<AlbumCoverLoader> albumcover_loader, QObject *parent)
|
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(const SharedPtr<AlbumCoverLoader> albumcover_loader, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
albumcover_loader_(albumcover_loader),
|
albumcover_loader_(albumcover_loader),
|
||||||
temp_file_pattern_(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + u"/strawberry-cover-XXXXXX.jpg"_s),
|
temp_file_pattern_(StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation) + u"/strawberry-cover-XXXXXX.jpg"_s),
|
||||||
id_(0) {
|
id_(0) {
|
||||||
|
|
||||||
setObjectName(QLatin1String(metaObject()->className()));
|
setObjectName(QLatin1String(metaObject()->className()));
|
||||||
|
|||||||
@@ -52,10 +52,12 @@
|
|||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
using std::make_shared;
|
using std::make_shared;
|
||||||
|
|
||||||
const char *DiscogsCoverProvider::kUrlSearch = "https://api.discogs.com/database/search";
|
namespace {
|
||||||
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
|
constexpr char kUrlSearch[] = "https://api.discogs.com/database/search";
|
||||||
const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
|
constexpr char kAccessKeyB64[] = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
|
||||||
const int DiscogsCoverProvider::kRequestsDelay = 1000;
|
constexpr char kSecretKeyB64[] = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
|
||||||
|
constexpr int kRequestsDelay = 1000;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
DiscogsCoverProvider::DiscogsCoverProvider(const SharedPtr<NetworkAccessManager> network, QObject *parent)
|
DiscogsCoverProvider::DiscogsCoverProvider(const SharedPtr<NetworkAccessManager> network, QObject *parent)
|
||||||
: JsonCoverProvider(u"Discogs"_s, false, false, 0.0, false, false, network, parent),
|
: JsonCoverProvider(u"Discogs"_s, false, false, 0.0, false, false, network, parent),
|
||||||
|
|||||||
@@ -89,11 +89,6 @@ class DiscogsCoverProvider : public JsonCoverProvider {
|
|||||||
void HandleReleaseReply(QNetworkReply *reply, const int search_id, const quint64 release_id);
|
void HandleReleaseReply(QNetworkReply *reply, const int search_id, const quint64 release_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const char *kUrlSearch;
|
|
||||||
static const char *kAccessKeyB64;
|
|
||||||
static const char *kSecretKeyB64;
|
|
||||||
static const int kRequestsDelay;
|
|
||||||
|
|
||||||
QTimer *timer_flush_requests_;
|
QTimer *timer_flush_requests_;
|
||||||
QQueue<SharedPtr<DiscogsCoverSearchContext>> queue_search_requests_;
|
QQueue<SharedPtr<DiscogsCoverSearchContext>> queue_search_requests_;
|
||||||
QQueue<DiscogsCoverReleaseContext> queue_release_requests_;
|
QQueue<DiscogsCoverReleaseContext> queue_release_requests_;
|
||||||
|
|||||||
@@ -24,9 +24,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <cdio/cdio.h>
|
|
||||||
#include <gst/audio/gstaudiocdsrc.h>
|
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <cdio/cdio.h>
|
||||||
|
#include <cdio/device.h>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -30,10 +35,6 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
// This must come after Qt includes
|
|
||||||
#include <cdio/cdio.h>
|
|
||||||
#include <cdio/device.h>
|
|
||||||
|
|
||||||
#include "cddalister.h"
|
#include "cddalister.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,16 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gtypes.h>
|
#include <glib/gtypes.h>
|
||||||
#include <glib-object.h>
|
#include <glib-object.h>
|
||||||
|
|
||||||
|
#include <cdio/cdio.h>
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <gst/tag/tag.h>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
@@ -35,10 +41,6 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include <cdio/cdio.h>
|
|
||||||
#include <gst/gst.h>
|
|
||||||
#include <gst/tag/tag.h>
|
|
||||||
|
|
||||||
#include "cddasongloader.h"
|
#include "cddasongloader.h"
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
|||||||
@@ -24,17 +24,19 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <cdio/types.h>
|
||||||
|
#include <cdio/cdio.h>
|
||||||
|
|
||||||
|
#include <gst/gstelement.h>
|
||||||
|
#include <gst/audio/gstaudiocdsrc.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
// These must come after Qt includes
|
|
||||||
#include <cdio/types.h>
|
|
||||||
#include <cdio/cdio.h>
|
|
||||||
#include <gst/gstelement.h>
|
|
||||||
#include <gst/audio/gstaudiocdsrc.h>
|
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#ifdef HAVE_MUSICBRAINZ
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
|
|||||||
@@ -36,10 +36,10 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QStandardPaths>
|
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/standardpaths.h"
|
||||||
#include "core/temporaryfile.h"
|
#include "core/temporaryfile.h"
|
||||||
#include "core/taskmanager.h"
|
#include "core/taskmanager.h"
|
||||||
#include "core/database.h"
|
#include "core/database.h"
|
||||||
@@ -211,9 +211,9 @@ bool GPodDevice::CopyToStorage(const CopyJob &job, QString &error_text) {
|
|||||||
bool result = false;
|
bool result = false;
|
||||||
if (!job.cover_image_.isNull()) {
|
if (!job.cover_image_.isNull()) {
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
QString temp_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/organize"_s;
|
QString temp_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/organize"_s;
|
||||||
#else
|
#else
|
||||||
QString temp_path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
QString temp_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation);
|
||||||
#endif
|
#endif
|
||||||
if (!QDir(temp_path).exists()) QDir().mkpath(temp_path);
|
if (!QDir(temp_path).exists()) QDir().mkpath(temp_path);
|
||||||
SharedPtr<TemporaryFile> cover_file = make_shared<TemporaryFile>(temp_path + u"/track-albumcover-XXXXXX.jpg"_s);
|
SharedPtr<TemporaryFile> cover_file = make_shared<TemporaryFile>(temp_path + u"/track-albumcover-XXXXXX.jpg"_s);
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ EngineBase::EngineBase(QObject *parent)
|
|||||||
exclusive_mode_(false),
|
exclusive_mode_(false),
|
||||||
volume_control_(true),
|
volume_control_(true),
|
||||||
volume_(100),
|
volume_(100),
|
||||||
beginning_nanosec_(0),
|
beginning_offset_nanosec_(0),
|
||||||
end_nanosec_(0),
|
end_offset_nanosec_(0),
|
||||||
ebur128_loudness_normalizing_gain_db_(0.0),
|
ebur128_loudness_normalizing_gain_db_(0.0),
|
||||||
scope_(kScopeSize),
|
scope_(kScopeSize),
|
||||||
buffering_(false),
|
buffering_(false),
|
||||||
@@ -84,15 +84,15 @@ EngineBase::EngineBase(QObject *parent)
|
|||||||
|
|
||||||
EngineBase::~EngineBase() = default;
|
EngineBase::~EngineBase() = default;
|
||||||
|
|
||||||
bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags track_change_flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags track_change_flags, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||||
|
|
||||||
Q_UNUSED(track_change_flags)
|
Q_UNUSED(track_change_flags)
|
||||||
Q_UNUSED(force_stop_at_end);
|
Q_UNUSED(force_stop_at_end);
|
||||||
|
|
||||||
media_url_ = media_url;
|
media_url_ = media_url;
|
||||||
stream_url_ = stream_url;
|
stream_url_ = stream_url;
|
||||||
beginning_nanosec_ = beginning_nanosec;
|
beginning_offset_nanosec_ = beginning_offset_nanosec;
|
||||||
end_nanosec_ = end_nanosec;
|
end_offset_nanosec_ = end_offset_nanosec;
|
||||||
|
|
||||||
ebur128_loudness_normalizing_gain_db_ = 0.0;
|
ebur128_loudness_normalizing_gain_db_ = 0.0;
|
||||||
if (ebur128_loudness_normalization_ && ebur128_integrated_loudness_lufs) {
|
if (ebur128_loudness_normalization_ && ebur128_integrated_loudness_lufs) {
|
||||||
@@ -112,9 +112,9 @@ bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const Track
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EngineBase::Play(const QUrl &media_url, const QUrl &stream_url, const bool pause, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
bool EngineBase::Play(const QUrl &media_url, const QUrl &stream_url, const bool pause, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||||
|
|
||||||
if (!Load(media_url, stream_url, flags, force_stop_at_end, beginning_nanosec, end_nanosec, ebur128_integrated_loudness_lufs)) {
|
if (!Load(media_url, stream_url, flags, force_stop_at_end, beginning_offset_nanosec, end_offset_nanosec, ebur128_integrated_loudness_lufs)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class EngineBase : public QObject {
|
|||||||
virtual bool Init() = 0;
|
virtual bool Init() = 0;
|
||||||
virtual State state() const = 0;
|
virtual State state() const = 0;
|
||||||
virtual void StartPreloading(const QUrl&, const QUrl&, const bool, const qint64, const qint64) {}
|
virtual void StartPreloading(const QUrl&, const QUrl&, const bool, const qint64, const qint64) {}
|
||||||
virtual bool Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags track_change_flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
|
virtual bool Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags track_change_flags, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
|
||||||
virtual bool Play(const bool pause, const quint64 offset_nanosec) = 0;
|
virtual bool Play(const bool pause, const quint64 offset_nanosec) = 0;
|
||||||
virtual void Stop(const bool stop_after = false) = 0;
|
virtual void Stop(const bool stop_after = false) = 0;
|
||||||
virtual void Pause() = 0;
|
virtual void Pause() = 0;
|
||||||
@@ -106,9 +106,9 @@ class EngineBase : public QObject {
|
|||||||
|
|
||||||
// Sets new values for the beginning and end markers of the currently playing song.
|
// Sets new values for the beginning and end markers of the currently playing song.
|
||||||
// This doesn't change the state of engine or the stream's current position.
|
// This doesn't change the state of engine or the stream's current position.
|
||||||
virtual void RefreshMarkers(const quint64 beginning_nanosec, const qint64 end_nanosec) {
|
virtual void RefreshMarkers(const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec) {
|
||||||
beginning_nanosec_ = beginning_nanosec;
|
beginning_offset_nanosec_ = beginning_offset_nanosec;
|
||||||
end_nanosec_ = end_nanosec;
|
end_offset_nanosec_ = end_offset_nanosec;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual OutputDetailsList GetOutputsList() const = 0;
|
virtual OutputDetailsList GetOutputsList() const = 0;
|
||||||
@@ -179,8 +179,8 @@ class EngineBase : public QObject {
|
|||||||
bool exclusive_mode_;
|
bool exclusive_mode_;
|
||||||
bool volume_control_;
|
bool volume_control_;
|
||||||
uint volume_;
|
uint volume_;
|
||||||
quint64 beginning_nanosec_;
|
quint64 beginning_offset_nanosec_;
|
||||||
qint64 end_nanosec_;
|
qint64 end_offset_nanosec_;
|
||||||
QUrl media_url_;
|
QUrl media_url_;
|
||||||
QUrl stream_url_;
|
QUrl stream_url_;
|
||||||
double ebur128_loudness_normalizing_gain_db_;
|
double ebur128_loudness_normalizing_gain_db_;
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
#include "gstbufferconsumer.h"
|
#include "gstbufferconsumer.h"
|
||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
using std::make_shared;
|
|
||||||
|
|
||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
# pragma clang diagnostic push
|
# pragma clang diagnostic push
|
||||||
@@ -177,13 +176,13 @@ EngineBase::State GstEngine::state() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) {
|
void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec) {
|
||||||
|
|
||||||
const QByteArray gst_url = FixupUrl(stream_url);
|
const QByteArray gst_url = FixupUrl(stream_url);
|
||||||
|
|
||||||
// No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully)
|
// No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully)
|
||||||
if (current_pipeline_) {
|
if (current_pipeline_) {
|
||||||
current_pipeline_->PrepareNextUrl(media_url, stream_url, gst_url, beginning_nanosec, force_stop_at_end ? end_nanosec : 0);
|
current_pipeline_->PrepareNextUrl(media_url, stream_url, gst_url, beginning_offset_nanosec, force_stop_at_end ? end_offset_nanosec : 0);
|
||||||
// Add request to discover the stream
|
// Add request to discover the stream
|
||||||
if (discoverer_ && media_url.scheme() != u"spotify"_s) {
|
if (discoverer_ && media_url.scheme() != u"spotify"_s) {
|
||||||
if (!gst_discoverer_discover_uri_async(discoverer_, gst_url.constData())) {
|
if (!gst_discoverer_discover_uri_async(discoverer_, gst_url.constData())) {
|
||||||
@@ -194,9 +193,9 @@ void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &stream_url, c
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||||
|
|
||||||
EngineBase::Load(media_url, stream_url, change, force_stop_at_end, beginning_nanosec, end_nanosec, ebur128_integrated_loudness_lufs);
|
EngineBase::Load(media_url, stream_url, change, force_stop_at_end, beginning_offset_nanosec, end_offset_nanosec, ebur128_integrated_loudness_lufs);
|
||||||
|
|
||||||
const QByteArray gst_url = FixupUrl(stream_url);
|
const QByteArray gst_url = FixupUrl(stream_url);
|
||||||
|
|
||||||
@@ -215,7 +214,7 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GstEnginePipelinePtr pipeline = CreatePipeline(media_url, stream_url, gst_url, force_stop_at_end ? end_nanosec : 0, ebur128_loudness_normalizing_gain_db_);
|
GstEnginePipelinePtr pipeline = CreatePipeline(media_url, stream_url, gst_url, static_cast<qint64>(beginning_offset_nanosec), force_stop_at_end ? end_offset_nanosec : 0, ebur128_loudness_normalizing_gain_db_);
|
||||||
if (!pipeline) return false;
|
if (!pipeline) return false;
|
||||||
|
|
||||||
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
||||||
@@ -264,7 +263,17 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
|
|||||||
|
|
||||||
bool GstEngine::Play(const bool pause, const quint64 offset_nanosec) {
|
bool GstEngine::Play(const bool pause, const quint64 offset_nanosec) {
|
||||||
|
|
||||||
if (!current_pipeline_ || current_pipeline_->is_buffering() || current_pipeline_->state() == GstState::GST_STATE_PLAYING) return false;
|
if (!current_pipeline_ || current_pipeline_->is_buffering()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_pipeline_->state() == GstState::GST_STATE_PLAYING) {
|
||||||
|
if (offset_nanosec != 0 || beginning_offset_nanosec_ != 0) {
|
||||||
|
Seek(offset_nanosec);
|
||||||
|
PlayDone(GST_STATE_CHANGE_SUCCESS, false, offset_nanosec, current_pipeline_->id());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (OldExclusivePipelineActive()) {
|
if (OldExclusivePipelineActive()) {
|
||||||
qLog(Debug) << "Delaying play because a exclusive pipeline is already active...";
|
qLog(Debug) << "Delaying play because a exclusive pipeline is already active...";
|
||||||
@@ -289,7 +298,7 @@ bool GstEngine::Play(const bool pause, const quint64 offset_nanosec) {
|
|||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
PlayDone(ret, pause, offset_nanosec, pipeline_id);
|
PlayDone(ret, pause, offset_nanosec, pipeline_id);
|
||||||
});
|
});
|
||||||
QFuture<GstStateChangeReturn> future = current_pipeline_->Play(pause, beginning_nanosec_ + offset_nanosec);
|
QFuture<GstStateChangeReturn> future = current_pipeline_->Play(pause, beginning_offset_nanosec_ + offset_nanosec);
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -306,7 +315,7 @@ void GstEngine::Stop(const bool stop_after) {
|
|||||||
|
|
||||||
media_url_.clear();
|
media_url_.clear();
|
||||||
stream_url_.clear(); // To ensure we return Empty from state()
|
stream_url_.clear(); // To ensure we return Empty from state()
|
||||||
beginning_nanosec_ = end_nanosec_ = 0;
|
beginning_offset_nanosec_ = end_offset_nanosec_ = 0;
|
||||||
|
|
||||||
// Check if we started a fade out. If it isn't finished yet and the user pressed stop, we cancel the fader and just stop the playback.
|
// Check if we started a fade out. If it isn't finished yet and the user pressed stop, we cancel the fader and just stop the playback.
|
||||||
if (fadeout_pause_pipeline_) {
|
if (fadeout_pause_pipeline_) {
|
||||||
@@ -384,7 +393,7 @@ void GstEngine::Seek(const quint64 offset_nanosec) {
|
|||||||
|
|
||||||
if (!current_pipeline_) return;
|
if (!current_pipeline_) return;
|
||||||
|
|
||||||
seek_pos_ = beginning_nanosec_ + offset_nanosec;
|
seek_pos_ = beginning_offset_nanosec_ + offset_nanosec;
|
||||||
waiting_to_seek_ = true;
|
waiting_to_seek_ = true;
|
||||||
|
|
||||||
if (!seek_timer_->isActive()) {
|
if (!seek_timer_->isActive()) {
|
||||||
@@ -402,7 +411,7 @@ qint64 GstEngine::position_nanosec() const {
|
|||||||
|
|
||||||
if (!current_pipeline_) return 0;
|
if (!current_pipeline_) return 0;
|
||||||
|
|
||||||
const qint64 result = current_pipeline_->position() - static_cast<qint64>(beginning_nanosec_);
|
const qint64 result = current_pipeline_->position() - static_cast<qint64>(beginning_offset_nanosec_);
|
||||||
return std::max(0LL, result);
|
return std::max(0LL, result);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -411,7 +420,7 @@ qint64 GstEngine::length_nanosec() const {
|
|||||||
|
|
||||||
if (!current_pipeline_) return 0;
|
if (!current_pipeline_) return 0;
|
||||||
|
|
||||||
const qint64 result = end_nanosec_ - static_cast<qint64>(beginning_nanosec_);
|
const qint64 result = end_offset_nanosec_ - static_cast<qint64>(beginning_offset_nanosec_);
|
||||||
|
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
return result;
|
return result;
|
||||||
@@ -742,7 +751,7 @@ void GstEngine::PlayDone(const GstStateChangeReturn ret, const bool pause, const
|
|||||||
stream_url = old_pipeline->stream_url();
|
stream_url = old_pipeline->stream_url();
|
||||||
stream_url.detach();
|
stream_url.detach();
|
||||||
}
|
}
|
||||||
current_pipeline_ = CreatePipeline(media_url, stream_url, redirect_url, end_nanosec_, old_pipeline->ebur128_loudness_normalizing_gain_db());
|
current_pipeline_ = CreatePipeline(media_url, stream_url, redirect_url, beginning_offset_nanosec_, end_offset_nanosec_, old_pipeline->ebur128_loudness_normalizing_gain_db());
|
||||||
FinishPipeline(old_pipeline);
|
FinishPipeline(old_pipeline);
|
||||||
Play(pause, offset_nanosec);
|
Play(pause, offset_nanosec);
|
||||||
return;
|
return;
|
||||||
@@ -761,7 +770,7 @@ void GstEngine::PlayDone(const GstStateChangeReturn ret, const bool pause, const
|
|||||||
|
|
||||||
Q_EMIT StateChanged(pause ? State::Paused : State::Playing);
|
Q_EMIT StateChanged(pause ? State::Paused : State::Playing);
|
||||||
|
|
||||||
// We've successfully started playing a media stream with this url
|
// We've successfully started playing a media stream with this URL
|
||||||
Q_EMIT ValidSongRequested(stream_url_);
|
Q_EMIT ValidSongRequested(stream_url_);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -887,7 +896,7 @@ void GstEngine::StopTimers() {
|
|||||||
|
|
||||||
GstEnginePipelinePtr GstEngine::CreatePipeline() {
|
GstEnginePipelinePtr GstEngine::CreatePipeline() {
|
||||||
|
|
||||||
GstEnginePipelinePtr pipeline = make_shared<GstEnginePipeline>();
|
GstEnginePipelinePtr pipeline = GstEnginePipelinePtr(new GstEnginePipeline);
|
||||||
pipeline->set_output_device(output_, device_);
|
pipeline->set_output_device(output_, device_);
|
||||||
pipeline->set_exclusive_mode(exclusive_mode_);
|
pipeline->set_exclusive_mode(exclusive_mode_);
|
||||||
pipeline->set_volume_enabled(volume_control_);
|
pipeline->set_volume_enabled(volume_control_);
|
||||||
@@ -926,11 +935,11 @@ GstEnginePipelinePtr GstEngine::CreatePipeline() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GstEnginePipelinePtr GstEngine::CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db) {
|
GstEnginePipelinePtr GstEngine::CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const double ebur128_loudness_normalizing_gain_db) {
|
||||||
|
|
||||||
GstEnginePipelinePtr ret = CreatePipeline();
|
GstEnginePipelinePtr ret = CreatePipeline();
|
||||||
QString error;
|
QString error;
|
||||||
if (!ret->InitFromUrl(media_url, stream_url, gst_url, end_nanosec, ebur128_loudness_normalizing_gain_db, error)) {
|
if (!ret->InitFromUrl(media_url, stream_url, gst_url, beginning_offset_nanosec, end_offset_nanosec, ebur128_loudness_normalizing_gain_db, error)) {
|
||||||
ret.reset();
|
ret.reset();
|
||||||
Q_EMIT Error(error);
|
Q_EMIT Error(error);
|
||||||
Q_EMIT StateChanged(State::Error);
|
Q_EMIT StateChanged(State::Error);
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
|
|||||||
|
|
||||||
bool Init() override;
|
bool Init() override;
|
||||||
State state() const override;
|
State state() const override;
|
||||||
void StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) override;
|
void StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec) override;
|
||||||
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override;
|
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override;
|
||||||
bool Play(const bool pause, const quint64 offset_nanosec) override;
|
bool Play(const bool pause, const quint64 offset_nanosec) override;
|
||||||
void Stop(const bool stop_after = false) override;
|
void Stop(const bool stop_after = false) override;
|
||||||
void Pause() override;
|
void Pause() override;
|
||||||
@@ -133,7 +133,7 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
|
|||||||
void StopTimers();
|
void StopTimers();
|
||||||
|
|
||||||
GstEnginePipelinePtr CreatePipeline();
|
GstEnginePipelinePtr CreatePipeline();
|
||||||
GstEnginePipelinePtr CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db);
|
GstEnginePipelinePtr CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const double ebur128_loudness_normalizing_gain_db);
|
||||||
|
|
||||||
void FinishPipeline(GstEnginePipelinePtr pipeline);
|
void FinishPipeline(GstEnginePipelinePtr pipeline);
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
|||||||
ebur128_loudness_normalizing_gain_db_(0.0),
|
ebur128_loudness_normalizing_gain_db_(0.0),
|
||||||
segment_start_(0),
|
segment_start_(0),
|
||||||
segment_start_received_(false),
|
segment_start_received_(false),
|
||||||
|
beginning_offset_nanosec_(-1),
|
||||||
end_offset_nanosec_(-1),
|
end_offset_nanosec_(-1),
|
||||||
next_beginning_offset_nanosec_(-1),
|
next_beginning_offset_nanosec_(-1),
|
||||||
next_end_offset_nanosec_(-1),
|
next_end_offset_nanosec_(-1),
|
||||||
@@ -173,7 +174,9 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
|||||||
logged_unsupported_analyzer_format_(false),
|
logged_unsupported_analyzer_format_(false),
|
||||||
about_to_finish_(false),
|
about_to_finish_(false),
|
||||||
finish_requested_(false),
|
finish_requested_(false),
|
||||||
finished_(false) {
|
finished_(false),
|
||||||
|
set_state_in_progress_(0),
|
||||||
|
set_state_async_in_progress_(0) {
|
||||||
|
|
||||||
eq_band_gains_.reserve(kEqBandCount);
|
eq_band_gains_.reserve(kEqBandCount);
|
||||||
for (int i = 0; i < kEqBandCount; ++i) eq_band_gains_ << 0;
|
for (int i = 0; i < kEqBandCount; ++i) eq_band_gains_ << 0;
|
||||||
@@ -418,7 +421,7 @@ bool GstEnginePipeline::Finish() {
|
|||||||
|
|
||||||
Disconnect();
|
Disconnect();
|
||||||
|
|
||||||
if (IsStateNull()) {
|
if (IsStateNull() && set_state_async_in_progress_ == 0 && set_state_in_progress_ == 0) {
|
||||||
finished_ = true;
|
finished_ = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -429,7 +432,7 @@ bool GstEnginePipeline::Finish() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) {
|
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) {
|
||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker l(&mutex_url_);
|
QMutexLocker l(&mutex_url_);
|
||||||
@@ -438,7 +441,8 @@ bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_ur
|
|||||||
gst_url_ = gst_url;
|
gst_url_ = gst_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
end_offset_nanosec_ = end_nanosec;
|
beginning_offset_nanosec_ = beginning_offset_nanosec;
|
||||||
|
end_offset_nanosec_ = end_offset_nanosec;
|
||||||
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
||||||
|
|
||||||
guint version_major = 0, version_minor = 0, version_micro = 0, version_nano = 0;
|
guint version_major = 0, version_minor = 0, version_micro = 0, version_nano = 0;
|
||||||
@@ -1328,6 +1332,7 @@ GstPadProbeReturn GstEnginePipeline::BufferProbeCallback(GstPad *pad, GstPadProb
|
|||||||
if (instance->end_offset_nanosec_.value() > 0 && end_time > instance->end_offset_nanosec_.value()) {
|
if (instance->end_offset_nanosec_.value() > 0 && end_time > instance->end_offset_nanosec_.value()) {
|
||||||
if (instance->HasMatchingNextUrl() && instance->next_beginning_offset_nanosec_.value() == instance->end_offset_nanosec_.value()) {
|
if (instance->HasMatchingNextUrl() && instance->next_beginning_offset_nanosec_.value() == instance->end_offset_nanosec_.value()) {
|
||||||
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
|
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
|
||||||
|
instance->beginning_offset_nanosec_ = instance->next_beginning_offset_nanosec_;
|
||||||
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
|
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
|
||||||
instance->next_media_url_.clear();
|
instance->next_media_url_.clear();
|
||||||
instance->next_stream_url_.clear();
|
instance->next_stream_url_.clear();
|
||||||
@@ -1477,6 +1482,7 @@ void GstEnginePipeline::StreamStartMessageReceived() {
|
|||||||
next_media_url_.clear();
|
next_media_url_.clear();
|
||||||
next_gst_url_.clear();
|
next_gst_url_.clear();
|
||||||
}
|
}
|
||||||
|
beginning_offset_nanosec_ = next_beginning_offset_nanosec_;
|
||||||
end_offset_nanosec_ = next_end_offset_nanosec_;
|
end_offset_nanosec_ = next_end_offset_nanosec_;
|
||||||
next_beginning_offset_nanosec_ = 0;
|
next_beginning_offset_nanosec_ = 0;
|
||||||
next_end_offset_nanosec_ = 0;
|
next_end_offset_nanosec_ = 0;
|
||||||
@@ -1788,7 +1794,17 @@ bool GstEnginePipeline::IsStateNull() const {
|
|||||||
|
|
||||||
void GstEnginePipeline::SetStateAsync(const GstState state) {
|
void GstEnginePipeline::SetStateAsync(const GstState state) {
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "SetState", Qt::QueuedConnection, Q_ARG(GstState, state));
|
++set_state_async_in_progress_;
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "SetStateAsyncSlot", Qt::QueuedConnection, Q_ARG(GstState, state));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::SetStateAsyncSlot(const GstState state) {
|
||||||
|
|
||||||
|
--set_state_async_in_progress_;
|
||||||
|
|
||||||
|
SetState(state);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1796,6 +1812,8 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state)
|
|||||||
|
|
||||||
qLog(Debug) << "Setting pipeline" << id() << "state to" << GstStateText(state);
|
qLog(Debug) << "Setting pipeline" << id() << "state to" << GstStateText(state);
|
||||||
|
|
||||||
|
++set_state_in_progress_;
|
||||||
|
|
||||||
QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
|
QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
|
||||||
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, state]() {
|
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, state]() {
|
||||||
const GstStateChangeReturn state_change_return = watcher->result();
|
const GstStateChangeReturn state_change_return = watcher->result();
|
||||||
@@ -1811,13 +1829,15 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state)
|
|||||||
|
|
||||||
void GstEnginePipeline::SetStateFinishedSlot(const GstState state, const GstStateChangeReturn state_change_return) {
|
void GstEnginePipeline::SetStateFinishedSlot(const GstState state, const GstStateChangeReturn state_change_return) {
|
||||||
|
|
||||||
|
--set_state_in_progress_;
|
||||||
|
|
||||||
switch (state_change_return) {
|
switch (state_change_return) {
|
||||||
case GST_STATE_CHANGE_SUCCESS:
|
case GST_STATE_CHANGE_SUCCESS:
|
||||||
case GST_STATE_CHANGE_ASYNC:
|
case GST_STATE_CHANGE_ASYNC:
|
||||||
case GST_STATE_CHANGE_NO_PREROLL:
|
case GST_STATE_CHANGE_NO_PREROLL:
|
||||||
qLog(Debug) << "Pipeline" << id() << "state successfully set to" << GstStateText(state);
|
qLog(Debug) << "Pipeline" << id() << "state successfully set to" << GstStateText(state);
|
||||||
Q_EMIT SetStateFinished(state_change_return);
|
Q_EMIT SetStateFinished(state_change_return);
|
||||||
if (!finished_.value() && finish_requested_.value()) {
|
if (!finished_.value() && finish_requested_.value() && set_state_async_in_progress_ == 0 && set_state_in_progress_ == 0) {
|
||||||
finished_ = true;
|
finished_ = true;
|
||||||
Q_EMIT Finished();
|
Q_EMIT Finished();
|
||||||
}
|
}
|
||||||
@@ -2137,7 +2157,7 @@ bool GstEnginePipeline::HasMatchingNextUrl() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_nanosec, const qint64 end_nanosec) {
|
void GstEnginePipeline::PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec) {
|
||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker l(&mutex_next_url_);
|
QMutexLocker l(&mutex_next_url_);
|
||||||
@@ -2146,8 +2166,8 @@ void GstEnginePipeline::PrepareNextUrl(const QUrl &media_url, const QUrl &stream
|
|||||||
next_gst_url_ = gst_url;
|
next_gst_url_ = gst_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
next_beginning_offset_nanosec_ = beginning_nanosec;
|
next_beginning_offset_nanosec_ = beginning_offset_nanosec;
|
||||||
next_end_offset_nanosec_ = end_nanosec;
|
next_end_offset_nanosec_ = end_offset_nanosec;
|
||||||
|
|
||||||
if (about_to_finish_.value()) {
|
if (about_to_finish_.value()) {
|
||||||
SetNextUrl();
|
SetNextUrl();
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
#include "includes/mutex_protected.h"
|
#include "includes/mutex_protected.h"
|
||||||
@@ -83,7 +84,7 @@ class GstEnginePipeline : public QObject {
|
|||||||
bool Finish();
|
bool Finish();
|
||||||
|
|
||||||
// Creates the pipeline, returns false on error
|
// Creates the pipeline, returns false on error
|
||||||
bool InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error);
|
bool InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error);
|
||||||
|
|
||||||
// GstBufferConsumers get fed audio data. Thread-safe.
|
// GstBufferConsumers get fed audio data. Thread-safe.
|
||||||
void AddBufferConsumer(GstBufferConsumer *consumer);
|
void AddBufferConsumer(GstBufferConsumer *consumer);
|
||||||
@@ -105,7 +106,7 @@ class GstEnginePipeline : public QObject {
|
|||||||
// If this is set then it will be loaded automatically when playback finishes for gapless playback
|
// If this is set then it will be loaded automatically when playback finishes for gapless playback
|
||||||
bool HasNextUrl() const;
|
bool HasNextUrl() const;
|
||||||
bool HasMatchingNextUrl() const;
|
bool HasMatchingNextUrl() const;
|
||||||
void PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_nanosec, const qint64 end_nanosec);
|
void PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec);
|
||||||
void SetNextUrl();
|
void SetNextUrl();
|
||||||
|
|
||||||
void SetSourceDevice(const QString &device);
|
void SetSourceDevice(const QString &device);
|
||||||
@@ -197,6 +198,7 @@ class GstEnginePipeline : public QObject {
|
|||||||
void ProcessPendingSeek(const GstState state);
|
void ProcessPendingSeek(const GstState state);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
|
void SetStateAsyncSlot(const GstState state);
|
||||||
void SetStateFinishedSlot(const GstState state, const GstStateChangeReturn state_change_return);
|
void SetStateFinishedSlot(const GstState state, const GstStateChangeReturn state_change_return);
|
||||||
void SetFaderVolume(const qreal volume);
|
void SetFaderVolume(const qreal volume);
|
||||||
void FaderTimelineStateChanged(const QTimeLine::State state);
|
void FaderTimelineStateChanged(const QTimeLine::State state);
|
||||||
@@ -287,6 +289,7 @@ class GstEnginePipeline : public QObject {
|
|||||||
mutex_protected<bool> segment_start_received_;
|
mutex_protected<bool> segment_start_received_;
|
||||||
GstSegment last_playbin_segment_{};
|
GstSegment last_playbin_segment_{};
|
||||||
|
|
||||||
|
mutex_protected<qint64> beginning_offset_nanosec_;
|
||||||
// If this is > 0 then the pipeline will be forced to stop when playback goes past this position.
|
// If this is > 0 then the pipeline will be forced to stop when playback goes past this position.
|
||||||
mutex_protected<qint64> end_offset_nanosec_;
|
mutex_protected<qint64> end_offset_nanosec_;
|
||||||
|
|
||||||
@@ -367,8 +370,11 @@ class GstEnginePipeline : public QObject {
|
|||||||
mutex_protected<bool> about_to_finish_;
|
mutex_protected<bool> about_to_finish_;
|
||||||
mutex_protected<bool> finish_requested_;
|
mutex_protected<bool> finish_requested_;
|
||||||
mutex_protected<bool> finished_;
|
mutex_protected<bool> finished_;
|
||||||
|
|
||||||
|
mutex_protected<int> set_state_in_progress_;
|
||||||
|
mutex_protected<int> set_state_async_in_progress_;
|
||||||
};
|
};
|
||||||
|
|
||||||
using GstEnginePipelinePtr = SharedPtr<GstEnginePipeline>;
|
using GstEnginePipelinePtr = QSharedPointer<GstEnginePipeline>;
|
||||||
|
|
||||||
#endif // GSTENGINEPIPELINE_H
|
#endif // GSTENGINEPIPELINE_H
|
||||||
|
|||||||
@@ -26,12 +26,12 @@
|
|||||||
#include <gst/pbutils/pbutils.h>
|
#include <gst/pbutils/pbutils.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/standardpaths.h"
|
||||||
#include "utilities/envutils.h"
|
#include "utilities/envutils.h"
|
||||||
|
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
@@ -145,7 +145,7 @@ void SetEnvironment() {
|
|||||||
#endif // USE_BUNDLE
|
#endif // USE_BUNDLE
|
||||||
|
|
||||||
#if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
|
||||||
QString gst_registry_filename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QStringLiteral("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
|
QString gst_registry_filename = StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + QStringLiteral("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
|
||||||
qLog(Debug) << "Setting GStreamer registry file to" << gst_registry_filename;
|
qLog(Debug) << "Setting GStreamer registry file to" << gst_registry_filename;
|
||||||
Utilities::SetEnv("GST_REGISTRY", gst_registry_filename);
|
Utilities::SetEnv("GST_REGISTRY", gst_registry_filename);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -188,8 +188,15 @@ FilterTree *FilterParser::parseSearchTerm() {
|
|||||||
QString value;
|
QString value;
|
||||||
|
|
||||||
bool in_quotes = false;
|
bool in_quotes = false;
|
||||||
|
bool previous_char_operator = false;
|
||||||
|
|
||||||
for (; iter_ != end_; ++iter_) {
|
for (; iter_ != end_; ++iter_) {
|
||||||
|
if (previous_char_operator) {
|
||||||
|
if (iter_->isSpace()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
previous_char_operator = false;
|
||||||
|
}
|
||||||
if (in_quotes) {
|
if (in_quotes) {
|
||||||
if (*iter_ == u'"') {
|
if (*iter_ == u'"') {
|
||||||
in_quotes = false;
|
in_quotes = false;
|
||||||
@@ -206,6 +213,7 @@ FilterTree *FilterParser::parseSearchTerm() {
|
|||||||
column = buf_.toLower();
|
column = buf_.toLower();
|
||||||
buf_.clear();
|
buf_.clear();
|
||||||
prefix.clear(); // Prefix isn't allowed here - let's ignore it
|
prefix.clear(); // Prefix isn't allowed here - let's ignore it
|
||||||
|
previous_char_operator = true;
|
||||||
}
|
}
|
||||||
else if (iter_->isSpace() || *iter_ == u'(' || *iter_ == u')' || *iter_ == u'-') {
|
else if (iter_->isSpace() || *iter_ == u'(' || *iter_ == u')' || *iter_ == u'-') {
|
||||||
break;
|
break;
|
||||||
@@ -214,9 +222,11 @@ FilterTree *FilterParser::parseSearchTerm() {
|
|||||||
// We don't know whether there is a column part in this search term thus we assume the latter and just try and read a prefix
|
// We don't know whether there is a column part in this search term thus we assume the latter and just try and read a prefix
|
||||||
if (prefix.isEmpty() && (*iter_ == u'>' || *iter_ == u'<' || *iter_ == u'=' || *iter_ == u'!')) {
|
if (prefix.isEmpty() && (*iter_ == u'>' || *iter_ == u'<' || *iter_ == u'=' || *iter_ == u'!')) {
|
||||||
prefix += *iter_;
|
prefix += *iter_;
|
||||||
|
previous_char_operator = true;
|
||||||
}
|
}
|
||||||
else if (prefix != u'=' && *iter_ == u'=') {
|
else if (prefix != u'=' && *iter_ == u'=') {
|
||||||
prefix += *iter_;
|
prefix += *iter_;
|
||||||
|
previous_char_operator = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
buf_ += *iter_;
|
buf_ += *iter_;
|
||||||
|
|||||||
@@ -57,6 +57,16 @@ class mutex_protected : public boost::noncopyable {
|
|||||||
value_ = value;
|
value_ = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator++() {
|
||||||
|
QMutexLocker l(&mutex_);
|
||||||
|
++value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator--() {
|
||||||
|
QMutexLocker l(&mutex_);
|
||||||
|
--value_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T value_;
|
T value_;
|
||||||
mutable QMutex mutex_;
|
mutable QMutex mutex_;
|
||||||
|
|||||||
13
src/main.cpp
13
src/main.cpp
@@ -48,7 +48,6 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
#include <QFileDevice>
|
#include <QFileDevice>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
@@ -70,6 +69,7 @@
|
|||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/standardpaths.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
#include "utilities/envutils.h"
|
#include "utilities/envutils.h"
|
||||||
@@ -131,13 +131,8 @@ int main(int argc, char *argv[]) {
|
|||||||
mac::MacMain();
|
mac::MacMain();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
|
|
||||||
QCoreApplication::setApplicationName(u"Strawberry"_s);
|
QCoreApplication::setApplicationName(u"Strawberry"_s);
|
||||||
QCoreApplication::setOrganizationName(u"Strawberry"_s);
|
QCoreApplication::setOrganizationName(u"Strawberry"_s);
|
||||||
#else
|
|
||||||
QCoreApplication::setApplicationName(u"strawberry"_s);
|
|
||||||
QCoreApplication::setOrganizationName(u"strawberry"_s);
|
|
||||||
#endif
|
|
||||||
QCoreApplication::setApplicationVersion(QStringLiteral(STRAWBERRY_VERSION_DISPLAY));
|
QCoreApplication::setApplicationVersion(QStringLiteral(STRAWBERRY_VERSION_DISPLAY));
|
||||||
QCoreApplication::setOrganizationDomain(u"strawberrymusicplayer.org"_s);
|
QCoreApplication::setOrganizationDomain(u"strawberrymusicplayer.org"_s);
|
||||||
|
|
||||||
@@ -157,7 +152,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// Only start a core application now, so we can check if there's another instance without requiring an X server.
|
// Only start a core application now, so we can check if there's another instance without requiring an X server.
|
||||||
// This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
|
// This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
|
||||||
QCoreApplication core_app(argc, argv);
|
QCoreApplication core_app(argc, argv);
|
||||||
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
KDSingleApplication single_app(QCoreApplication::applicationName().toLower(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
||||||
// Parse commandline options - need to do this before starting the full QApplication, so it works without an X server
|
// Parse commandline options - need to do this before starting the full QApplication, so it works without an X server
|
||||||
if (!options.Parse()) return 1;
|
if (!options.Parse()) return 1;
|
||||||
logging::SetLevels(options.log_levels());
|
logging::SetLevels(options.log_levels());
|
||||||
@@ -174,7 +169,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
// Must happen after QCoreApplication::setOrganizationName().
|
// Must happen after QCoreApplication::setOrganizationName().
|
||||||
Utilities::SetEnv("XDG_CONFIG_HOME", QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
|
Utilities::SetEnv("XDG_CONFIG_HOME", StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppConfigLocation));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Output the version, so when people attach log output to bug reports they don't have to tell us which version they're using.
|
// Output the version, so when people attach log output to bug reports they don't have to tell us which version they're using.
|
||||||
@@ -194,7 +189,7 @@ int main(int argc, char *argv[]) {
|
|||||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
KDSingleApplication single_app(QCoreApplication::applicationName().toLower(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
||||||
if (!single_app.isPrimaryInstance()) {
|
if (!single_app.isPrimaryInstance()) {
|
||||||
if (options.is_empty()) {
|
if (options.is_empty()) {
|
||||||
qLog(Info) << "Strawberry is already running - activating existing window (2)";
|
qLog(Info) << "Strawberry is already running - activating existing window (2)";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "includes/shared_ptr.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
@@ -33,6 +34,8 @@
|
|||||||
#include "moodbarloader.h"
|
#include "moodbarloader.h"
|
||||||
#include "moodbarpipeline.h"
|
#include "moodbarpipeline.h"
|
||||||
|
|
||||||
|
using std::make_shared;
|
||||||
|
|
||||||
MoodbarController::MoodbarController(const SharedPtr<Player> player, const SharedPtr<MoodbarLoader> moodbar_loader, QObject *parent)
|
MoodbarController::MoodbarController(const SharedPtr<Player> player, const SharedPtr<MoodbarLoader> moodbar_loader, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
player_(player),
|
player_(player),
|
||||||
@@ -56,25 +59,27 @@ void MoodbarController::CurrentSongChanged(const Song &song) {
|
|||||||
|
|
||||||
if (!enabled_) return;
|
if (!enabled_) return;
|
||||||
|
|
||||||
QByteArray data;
|
const MoodbarLoader::LoadResult load_result = moodbar_loader_->Load(song.url(), song.has_cue());
|
||||||
MoodbarPipeline *pipeline = nullptr;
|
switch (load_result.status) {
|
||||||
const MoodbarLoader::Result result = moodbar_loader_->Load(song.url(), song.has_cue(), &data, &pipeline);
|
case MoodbarLoader::LoadStatus::CannotLoad:
|
||||||
|
Q_EMIT CurrentMoodbarDataChanged();
|
||||||
switch (result) {
|
|
||||||
case MoodbarLoader::Result::CannotLoad:
|
|
||||||
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MoodbarLoader::Result::Loaded:
|
case MoodbarLoader::LoadStatus::Loaded:
|
||||||
Q_EMIT CurrentMoodbarDataChanged(data);
|
Q_EMIT CurrentMoodbarDataChanged(load_result.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MoodbarLoader::Result::WillLoadAsync:
|
case MoodbarLoader::LoadStatus::WillLoadAsync:
|
||||||
// Emit an empty array for now so the GUI reverts to a normal progress
|
// Emit an empty array for now so the GUI reverts to a normal progressbar. Our slot will be called when the data is actually loaded.
|
||||||
// bar. Our slot will be called when the data is actually loaded.
|
Q_EMIT CurrentMoodbarDataChanged();
|
||||||
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
|
|
||||||
|
|
||||||
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, pipeline, song]() { AsyncLoadComplete(pipeline, song.url()); });
|
MoodbarPipelinePtr pipeline = load_result.pipeline;
|
||||||
|
Q_ASSERT(pipeline);
|
||||||
|
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||||
|
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, pipeline, song]() {
|
||||||
|
AsyncLoadComplete(pipeline, song.url());
|
||||||
|
QObject::disconnect(*connection);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,12 +88,12 @@ void MoodbarController::CurrentSongChanged(const Song &song) {
|
|||||||
void MoodbarController::PlaybackStopped() {
|
void MoodbarController::PlaybackStopped() {
|
||||||
|
|
||||||
if (enabled_) {
|
if (enabled_) {
|
||||||
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
|
Q_EMIT CurrentMoodbarDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoodbarController::AsyncLoadComplete(MoodbarPipeline *pipeline, const QUrl &url) {
|
void MoodbarController::AsyncLoadComplete(MoodbarPipelinePtr pipeline, const QUrl &url) {
|
||||||
|
|
||||||
// Is this song still playing?
|
// Is this song still playing?
|
||||||
PlaylistItemPtr current_item = player_->GetCurrentItem();
|
PlaylistItemPtr current_item = player_->GetCurrentItem();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,9 +28,9 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
|
#include "moodbarpipeline.h"
|
||||||
|
|
||||||
class MoodbarLoader;
|
class MoodbarLoader;
|
||||||
class MoodbarPipeline;
|
|
||||||
class Song;
|
class Song;
|
||||||
class Player;
|
class Player;
|
||||||
|
|
||||||
@@ -43,15 +43,15 @@ class MoodbarController : public QObject {
|
|||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void CurrentMoodbarDataChanged(const QByteArray &data);
|
void CurrentMoodbarDataChanged(const QByteArray &data = QByteArray());
|
||||||
void StyleChanged();
|
void StyleChanged();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void CurrentSongChanged(const Song &song);
|
void CurrentSongChanged(const Song &song);
|
||||||
void PlaybackStopped();
|
void PlaybackStopped();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private:
|
||||||
void AsyncLoadComplete(MoodbarPipeline *pipeline, const QUrl &url);
|
void AsyncLoadComplete(MoodbarPipelinePtr pipeline, const QUrl &url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const SharedPtr<Player> player_;
|
const SharedPtr<Player> player_;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -36,6 +36,8 @@
|
|||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QRect>
|
#include <QRect>
|
||||||
|
|
||||||
|
#include "includes/shared_ptr.h"
|
||||||
|
#include "core/logging.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "playlist/playlist.h"
|
#include "playlist/playlist.h"
|
||||||
#include "playlist/playlistview.h"
|
#include "playlist/playlistview.h"
|
||||||
@@ -48,6 +50,8 @@
|
|||||||
|
|
||||||
#include "constants/moodbarsettings.h"
|
#include "constants/moodbarsettings.h"
|
||||||
|
|
||||||
|
using std::make_shared;
|
||||||
|
|
||||||
MoodbarItemDelegate::Data::Data() : state_(State::None) {}
|
MoodbarItemDelegate::Data::Data() : state_(State::None) {}
|
||||||
|
|
||||||
MoodbarItemDelegate::MoodbarItemDelegate(const SharedPtr<MoodbarLoader> moodbar_loader, PlaylistView *playlist_view, QObject *parent)
|
MoodbarItemDelegate::MoodbarItemDelegate(const SharedPtr<MoodbarLoader> moodbar_loader, PlaylistView *playlist_view, QObject *parent)
|
||||||
@@ -113,7 +117,11 @@ QPixmap MoodbarItemDelegate::PixmapForIndex(const QModelIndex &idx, const QSize
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data = new Data;
|
data = new Data;
|
||||||
if (!data_.insert(url, data)) return QPixmap();
|
if (!data_.insert(url, data)) {
|
||||||
|
qLog(Error) << "Could not insert moodbar data for URL" << url << "into cache";
|
||||||
|
delete data;
|
||||||
|
return QPixmap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data->indexes_.insert(idx);
|
data->indexes_.insert(idx);
|
||||||
@@ -150,21 +158,24 @@ void MoodbarItemDelegate::StartLoadingData(const QUrl &url, const bool has_cue,
|
|||||||
data->state_ = Data::State::LoadingData;
|
data->state_ = Data::State::LoadingData;
|
||||||
|
|
||||||
// Load a mood file for this song and generate some colors from it
|
// Load a mood file for this song and generate some colors from it
|
||||||
QByteArray bytes;
|
const MoodbarLoader::LoadResult load_result = moodbar_loader_->Load(url, has_cue);
|
||||||
MoodbarPipeline *pipeline = nullptr;
|
switch (load_result.status) {
|
||||||
switch (moodbar_loader_->Load(url, has_cue, &bytes, &pipeline)) {
|
case MoodbarLoader::LoadStatus::CannotLoad:
|
||||||
case MoodbarLoader::Result::CannotLoad:
|
|
||||||
data->state_ = Data::State::CannotLoad;
|
data->state_ = Data::State::CannotLoad;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MoodbarLoader::Result::Loaded:
|
case MoodbarLoader::LoadStatus::Loaded:
|
||||||
// We got the data immediately.
|
StartLoadingColors(url, load_result.data, data);
|
||||||
StartLoadingColors(url, bytes, data);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MoodbarLoader::Result::WillLoadAsync:
|
case MoodbarLoader::LoadStatus::WillLoadAsync:
|
||||||
// Maybe in a little while.
|
MoodbarPipelinePtr pipeline = load_result.pipeline;
|
||||||
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, url, pipeline]() { DataLoaded(url, pipeline); });
|
Q_ASSERT(pipeline);
|
||||||
|
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||||
|
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, url, pipeline]() {
|
||||||
|
DataLoaded(url, pipeline);
|
||||||
|
QObject::disconnect(*connection);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +205,7 @@ void MoodbarItemDelegate::ReloadAllColors() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoodbarItemDelegate::DataLoaded(const QUrl &url, MoodbarPipeline *pipeline) {
|
void MoodbarItemDelegate::DataLoaded(const QUrl &url, MoodbarPipelinePtr pipeline) {
|
||||||
|
|
||||||
if (!data_.contains(url)) return;
|
if (!data_.contains(url)) return;
|
||||||
|
|
||||||
@@ -291,8 +302,7 @@ void MoodbarItemDelegate::ImageLoaded(const QUrl &url, const QImage &image) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (source_index.model() != playlist) {
|
if (source_index.model() != playlist) {
|
||||||
// The pixmap was for an index in a different playlist, maybe the user
|
// The pixmap was for an index in a different playlist, maybe the user switched to a different one.
|
||||||
// switched to a different one.
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
#include "constants/moodbarsettings.h"
|
#include "constants/moodbarsettings.h"
|
||||||
|
#include "moodbarpipeline.h"
|
||||||
|
|
||||||
class QPainter;
|
class QPainter;
|
||||||
class MoodbarLoader;
|
class MoodbarLoader;
|
||||||
class MoodbarPipeline;
|
|
||||||
class PlaylistView;
|
class PlaylistView;
|
||||||
|
|
||||||
class MoodbarItemDelegate : public QItemDelegate {
|
class MoodbarItemDelegate : public QItemDelegate {
|
||||||
@@ -55,13 +55,14 @@ class MoodbarItemDelegate : public QItemDelegate {
|
|||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
void DataLoaded(const QUrl &url, MoodbarPipeline *pipeline);
|
|
||||||
void ColorsLoaded(const QUrl &url, const ColorVector &colors);
|
|
||||||
void ImageLoaded(const QUrl &url, const QImage &image);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void StyleChanged();
|
void StyleChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DataLoaded(const QUrl &url, MoodbarPipelinePtr pipeline);
|
||||||
|
void ColorsLoaded(const QUrl &url, const ColorVector &colors);
|
||||||
|
void ImageLoaded(const QUrl &url, const QImage &image);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Data {
|
struct Data {
|
||||||
Data();
|
Data();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,21 +28,21 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QAbstractNetworkCache>
|
#include <QAbstractNetworkCache>
|
||||||
#include <QNetworkDiskCache>
|
#include <QNetworkDiskCache>
|
||||||
#include <QTimer>
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
#include "includes/scoped_ptr.h"
|
#include "includes/scoped_ptr.h"
|
||||||
|
#include "includes/shared_ptr.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/standardpaths.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
#include "moodbarpipeline.h"
|
#include "moodbarpipeline.h"
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
using std::make_shared;
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
@@ -63,7 +64,10 @@ MoodbarLoader::MoodbarLoader(QObject *parent)
|
|||||||
kMaxActiveRequests(qMax(1, QThread::idealThreadCount() / 2)),
|
kMaxActiveRequests(qMax(1, QThread::idealThreadCount() / 2)),
|
||||||
save_(false) {
|
save_(false) {
|
||||||
|
|
||||||
cache_->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/moodbar"_s);
|
setObjectName(QLatin1String(metaObject()->className()));
|
||||||
|
thread_->setObjectName(objectName());
|
||||||
|
|
||||||
|
cache_->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/moodbar"_s);
|
||||||
cache_->setMaximumCacheSize(60LL * 1024LL * 1024LL); // 60MB - enough for 20,000 moodbars
|
cache_->setMaximumCacheSize(60LL * 1024LL * 1024LL); // 60MB - enough for 20,000 moodbars
|
||||||
|
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
@@ -104,16 +108,15 @@ QUrl MoodbarLoader::CacheUrlEntry(const QString &filename) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, QByteArray *data, MoodbarPipeline **async_pipeline) {
|
MoodbarLoader::LoadResult MoodbarLoader::Load(const QUrl &url, const bool has_cue) {
|
||||||
|
|
||||||
if (!url.isLocalFile() || has_cue) {
|
if (!url.isLocalFile() || has_cue) {
|
||||||
return Result::CannotLoad;
|
return LoadStatus::CannotLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we in the middle of loading this moodbar already?
|
// Are we in the middle of loading this moodbar already?
|
||||||
if (requests_.contains(url)) {
|
if (requests_.contains(url)) {
|
||||||
*async_pipeline = requests_.value(url);
|
return LoadResult(LoadStatus::WillLoadAsync, requests_.value(url));
|
||||||
return Result::WillLoadAsync;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a mood file exists for this file already
|
// Check if a mood file exists for this file already
|
||||||
@@ -121,16 +124,18 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
|
|||||||
|
|
||||||
const QStringList possible_mood_files = MoodFilenames(filename);
|
const QStringList possible_mood_files = MoodFilenames(filename);
|
||||||
for (const QString &possible_mood_file : possible_mood_files) {
|
for (const QString &possible_mood_file : possible_mood_files) {
|
||||||
QFile f(possible_mood_file);
|
QFile file(possible_mood_file);
|
||||||
if (f.exists()) {
|
if (file.exists()) {
|
||||||
if (f.open(QIODevice::ReadOnly)) {
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
qLog(Info) << "Loading moodbar data from" << possible_mood_file;
|
qLog(Info) << "Loading moodbar data from" << possible_mood_file;
|
||||||
*data = f.readAll();
|
const QByteArray data = file.readAll();
|
||||||
f.close();
|
file.close();
|
||||||
return Result::Loaded;
|
if (!data.isEmpty()) {
|
||||||
|
return LoadResult(LoadStatus::Loaded, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Error) << "Failed to load moodbar data from" << possible_mood_file << f.errorString();
|
qLog(Error) << "Failed to load moodbar data from" << possible_mood_file << file.errorString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,9 +147,9 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
|
|||||||
ScopedPtr<QIODevice> device_cache_file(cache_->data(disk_cache_metadata.url()));
|
ScopedPtr<QIODevice> device_cache_file(cache_->data(disk_cache_metadata.url()));
|
||||||
if (device_cache_file) {
|
if (device_cache_file) {
|
||||||
qLog(Info) << "Loading cached moodbar data for" << filename;
|
qLog(Info) << "Loading cached moodbar data for" << filename;
|
||||||
*data = device_cache_file->readAll();
|
const QByteArray data = device_cache_file->readAll();
|
||||||
if (!data->isEmpty()) {
|
if (!data.isEmpty()) {
|
||||||
return Result::Loaded;
|
return LoadResult(LoadStatus::Loaded, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,17 +157,20 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
|
|||||||
if (!thread_->isRunning()) thread_->start(QThread::IdlePriority);
|
if (!thread_->isRunning()) thread_->start(QThread::IdlePriority);
|
||||||
|
|
||||||
// There was no existing file, analyze the audio file and create one.
|
// There was no existing file, analyze the audio file and create one.
|
||||||
MoodbarPipeline *pipeline = new MoodbarPipeline(url);
|
MoodbarPipelinePtr pipeline = MoodbarPipelinePtr(new MoodbarPipeline(url));
|
||||||
pipeline->moveToThread(thread_);
|
pipeline->moveToThread(thread_);
|
||||||
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, pipeline, url]() { RequestFinished(pipeline, url); });
|
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||||
|
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, pipeline, url]() {
|
||||||
|
RequestFinished(pipeline, url);
|
||||||
|
QObject::disconnect(*connection);
|
||||||
|
});
|
||||||
|
|
||||||
requests_[url] = pipeline;
|
requests_[url] = pipeline;
|
||||||
queued_requests_ << url;
|
queued_requests_ << url;
|
||||||
|
|
||||||
MaybeTakeNextRequest();
|
MaybeTakeNextRequest();
|
||||||
|
|
||||||
*async_pipeline = pipeline;
|
return LoadResult(LoadStatus::WillLoadAsync, pipeline);
|
||||||
return Result::WillLoadAsync;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,15 +186,17 @@ void MoodbarLoader::MaybeTakeNextRequest() {
|
|||||||
active_requests_ << url;
|
active_requests_ << url;
|
||||||
|
|
||||||
qLog(Info) << "Creating moodbar data for" << url.toLocalFile();
|
qLog(Info) << "Creating moodbar data for" << url.toLocalFile();
|
||||||
QMetaObject::invokeMethod(requests_.value(url), &MoodbarPipeline::Start, Qt::QueuedConnection);
|
|
||||||
|
MoodbarPipelinePtr pipeline = requests_.value(url);
|
||||||
|
QMetaObject::invokeMethod(&*pipeline, &MoodbarPipeline::Start, Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
|
void MoodbarLoader::RequestFinished(MoodbarPipelinePtr pipeline, const QUrl &url) {
|
||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||||
|
|
||||||
if (request->success()) {
|
if (pipeline->success()) {
|
||||||
|
|
||||||
const QString filename = url.toLocalFile();
|
const QString filename = url.toLocalFile();
|
||||||
|
|
||||||
@@ -201,7 +211,7 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
|
|||||||
|
|
||||||
QIODevice *device_cache_file = cache_->prepare(disk_cache_metadata);
|
QIODevice *device_cache_file = cache_->prepare(disk_cache_metadata);
|
||||||
if (device_cache_file) {
|
if (device_cache_file) {
|
||||||
const qint64 data_written = device_cache_file->write(request->data());
|
const qint64 data_written = device_cache_file->write(pipeline->data());
|
||||||
if (data_written > 0) {
|
if (data_written > 0) {
|
||||||
cache_->insert(device_cache_file);
|
cache_->insert(device_cache_file);
|
||||||
}
|
}
|
||||||
@@ -213,7 +223,7 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
|
|||||||
const QString mood_filename(mood_filenames[0]);
|
const QString mood_filename(mood_filenames[0]);
|
||||||
QFile mood_file(mood_filename);
|
QFile mood_file(mood_filename);
|
||||||
if (mood_file.open(QIODevice::WriteOnly)) {
|
if (mood_file.open(QIODevice::WriteOnly)) {
|
||||||
if (mood_file.write(request->data()) <= 0) {
|
if (mood_file.write(pipeline->data()) <= 0) {
|
||||||
qLog(Error) << "Error writing to mood file" << mood_filename << mood_file.errorString();
|
qLog(Error) << "Error writing to mood file" << mood_filename << mood_file.errorString();
|
||||||
}
|
}
|
||||||
mood_file.close();
|
mood_file.close();
|
||||||
@@ -233,8 +243,6 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
|
|||||||
requests_.remove(url);
|
requests_.remove(url);
|
||||||
active_requests_.remove(url);
|
active_requests_.remove(url);
|
||||||
|
|
||||||
QTimer::singleShot(1s, request, &MoodbarLoader::deleteLater);
|
|
||||||
|
|
||||||
MaybeTakeNextRequest();
|
MaybeTakeNextRequest();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -30,10 +30,11 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "moodbarpipeline.h"
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
class QByteArray;
|
class QByteArray;
|
||||||
class QNetworkDiskCache;
|
class QNetworkDiskCache;
|
||||||
class MoodbarPipeline;
|
|
||||||
|
|
||||||
class MoodbarLoader : public QObject {
|
class MoodbarLoader : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -42,7 +43,7 @@ class MoodbarLoader : public QObject {
|
|||||||
explicit MoodbarLoader(QObject *parent = nullptr);
|
explicit MoodbarLoader(QObject *parent = nullptr);
|
||||||
~MoodbarLoader() override;
|
~MoodbarLoader() override;
|
||||||
|
|
||||||
enum class Result {
|
enum class LoadStatus {
|
||||||
// The URL isn't a local file or the moodbar plugin was not available -
|
// The URL isn't a local file or the moodbar plugin was not available -
|
||||||
// moodbar data can never be loaded.
|
// moodbar data can never be loaded.
|
||||||
CannotLoad,
|
CannotLoad,
|
||||||
@@ -55,17 +56,25 @@ class MoodbarLoader : public QObject {
|
|||||||
WillLoadAsync
|
WillLoadAsync
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class LoadResult {
|
||||||
|
public:
|
||||||
|
LoadResult(const LoadStatus _status) : status(_status) {}
|
||||||
|
LoadResult(const LoadStatus _status, MoodbarPipelinePtr _pipeline) : status(_status), pipeline(_pipeline) {}
|
||||||
|
LoadResult(const LoadStatus _status, const QByteArray &_data) : status(_status), data(_data) {}
|
||||||
|
LoadStatus status;
|
||||||
|
MoodbarPipelinePtr pipeline;
|
||||||
|
QByteArray data;
|
||||||
|
};
|
||||||
|
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
Result Load(const QUrl &url, const bool has_cue, QByteArray *data, MoodbarPipeline **async_pipeline);
|
LoadResult Load(const QUrl &url, const bool has_cue);
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void RequestFinished(MoodbarPipeline *request, const QUrl &url);
|
|
||||||
void MaybeTakeNextRequest();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static QStringList MoodFilenames(const QString &song_filename);
|
static QStringList MoodFilenames(const QString &song_filename);
|
||||||
static QUrl CacheUrlEntry(const QString &filename);
|
static QUrl CacheUrlEntry(const QString &filename);
|
||||||
|
void RequestFinished(MoodbarPipelinePtr pipeline, const QUrl &url);
|
||||||
|
void MaybeTakeNextRequest();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void MoodbarEnabled(const bool enabled);
|
void MoodbarEnabled(const bool enabled);
|
||||||
@@ -78,7 +87,7 @@ class MoodbarLoader : public QObject {
|
|||||||
|
|
||||||
const int kMaxActiveRequests;
|
const int kMaxActiveRequests;
|
||||||
|
|
||||||
QMap<QUrl, MoodbarPipeline*> requests_;
|
QMap<QUrl, MoodbarPipelinePtr> requests_;
|
||||||
QList<QUrl> queued_requests_;
|
QList<QUrl> queued_requests_;
|
||||||
QSet<QUrl> active_requests_;
|
QSet<QUrl> active_requests_;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -56,20 +56,24 @@ MoodbarPipeline::MoodbarPipeline(const QUrl &url, QObject *parent)
|
|||||||
success_(false),
|
success_(false),
|
||||||
running_(false) {}
|
running_(false) {}
|
||||||
|
|
||||||
MoodbarPipeline::~MoodbarPipeline() { Cleanup(); }
|
MoodbarPipeline::~MoodbarPipeline() {
|
||||||
|
|
||||||
GstElement *MoodbarPipeline::CreateElement(const QString &factory_name) {
|
Cleanup();
|
||||||
|
|
||||||
GstElement *ret = gst_element_factory_make(factory_name.toLatin1().constData(), nullptr);
|
}
|
||||||
|
|
||||||
if (ret) {
|
GstElement *MoodbarPipeline::CreateElement(const QByteArray &factory_name) {
|
||||||
gst_bin_add(GST_BIN(pipeline_), ret);
|
|
||||||
|
GstElement *element = gst_element_factory_make(factory_name.constData(), nullptr);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
gst_bin_add(GST_BIN(pipeline_), element);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Warning) << "Unable to create gstreamer element" << factory_name;
|
qLog(Warning) << "Unable to create gstreamer element" << factory_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return element;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +90,7 @@ QByteArray MoodbarPipeline::ToGstUrl(const QUrl &url) {
|
|||||||
|
|
||||||
void MoodbarPipeline::Start() {
|
void MoodbarPipeline::Start() {
|
||||||
|
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
||||||
|
|
||||||
Utilities::SetThreadIOPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
Utilities::SetThreadIOPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||||
@@ -96,10 +101,10 @@ void MoodbarPipeline::Start() {
|
|||||||
|
|
||||||
pipeline_ = gst_pipeline_new("moodbar-pipeline");
|
pipeline_ = gst_pipeline_new("moodbar-pipeline");
|
||||||
|
|
||||||
GstElement *decodebin = CreateElement(u"uridecodebin"_s);
|
GstElement *decodebin = CreateElement("uridecodebin");
|
||||||
convert_element_ = CreateElement(u"audioconvert"_s);
|
convert_element_ = CreateElement("audioconvert");
|
||||||
GstElement *spectrum = CreateElement(u"strawberry-fastspectrum"_s);
|
GstElement *spectrum = CreateElement("strawberry-fastspectrum");
|
||||||
GstElement *fakesink = CreateElement(u"fakesink"_s);
|
GstElement *fakesink = CreateElement("fakesink");
|
||||||
|
|
||||||
if (!decodebin || !convert_element_ || !spectrum || !fakesink) {
|
if (!decodebin || !convert_element_ || !spectrum || !fakesink) {
|
||||||
gst_object_unref(GST_OBJECT(pipeline_));
|
gst_object_unref(GST_OBJECT(pipeline_));
|
||||||
@@ -204,6 +209,8 @@ GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus *bus, GstMessage *messag
|
|||||||
|
|
||||||
MoodbarPipeline *instance = reinterpret_cast<MoodbarPipeline*>(self);
|
MoodbarPipeline *instance = reinterpret_cast<MoodbarPipeline*>(self);
|
||||||
|
|
||||||
|
if (!instance->running_) return GST_BUS_PASS;
|
||||||
|
|
||||||
switch (GST_MESSAGE_TYPE(message)) {
|
switch (GST_MESSAGE_TYPE(message)) {
|
||||||
case GST_MESSAGE_EOS:
|
case GST_MESSAGE_EOS:
|
||||||
instance->Stop(true);
|
instance->Stop(true);
|
||||||
@@ -224,23 +231,34 @@ GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus *bus, GstMessage *messag
|
|||||||
|
|
||||||
void MoodbarPipeline::Stop(const bool success) {
|
void MoodbarPipeline::Stop(const bool success) {
|
||||||
|
|
||||||
success_ = success;
|
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "Finish", Qt::QueuedConnection, Q_ARG(bool, success));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MoodbarPipeline::Finish(const bool success) {
|
||||||
|
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
||||||
|
|
||||||
|
success_ = success;
|
||||||
|
|
||||||
if (builder_) {
|
if (builder_) {
|
||||||
data_ = builder_->Finish(1000);
|
data_ = builder_->Finish(1000);
|
||||||
builder_.reset();
|
builder_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cleanup();
|
||||||
|
|
||||||
Q_EMIT Finished(success);
|
Q_EMIT Finished(success);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoodbarPipeline::Cleanup() {
|
void MoodbarPipeline::Cleanup() {
|
||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() == thread());
|
|
||||||
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
|
||||||
|
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
if (pipeline_) {
|
if (pipeline_) {
|
||||||
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
|
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
|
||||||
if (bus) {
|
if (bus) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib-object.h>
|
#include <glib-object.h>
|
||||||
@@ -46,18 +47,18 @@ class MoodbarPipeline : public QObject {
|
|||||||
bool success() const { return success_; }
|
bool success() const { return success_; }
|
||||||
const QByteArray &data() const { return data_; }
|
const QByteArray &data() const { return data_; }
|
||||||
|
|
||||||
public Q_SLOTS:
|
Q_INVOKABLE void Start();
|
||||||
void Start();
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void Finished(const bool success);
|
void Finished(const bool success);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GstElement *CreateElement(const QString &factory_name);
|
GstElement *CreateElement(const QByteArray &factory_name);
|
||||||
|
|
||||||
QByteArray ToGstUrl(const QUrl &url);
|
QByteArray ToGstUrl(const QUrl &url);
|
||||||
void ReportError(GstMessage *msg);
|
void ReportError(GstMessage *msg);
|
||||||
void Stop(const bool success);
|
void Stop(const bool success);
|
||||||
|
Q_INVOKABLE void Finish(const bool success);
|
||||||
void Cleanup();
|
void Cleanup();
|
||||||
|
|
||||||
static void NewPadCallback(GstElement *element, GstPad *pad, gpointer self);
|
static void NewPadCallback(GstElement *element, GstPad *pad, gpointer self);
|
||||||
@@ -75,4 +76,6 @@ class MoodbarPipeline : public QObject {
|
|||||||
QByteArray data_;
|
QByteArray data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using MoodbarPipelinePtr = QSharedPointer<MoodbarPipeline>;
|
||||||
|
|
||||||
#endif // MOODBARPIPELINE_H
|
#endif // MOODBARPIPELINE_H
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ void MoodbarProxyStyle::drawComplexControl(ComplexControl control, const QStyleO
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoodbarProxyStyle::Render(ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget) {
|
void MoodbarProxyStyle::Render(const ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget) {
|
||||||
|
|
||||||
const qreal fade_value = fade_timeline_->currentValue();
|
const qreal fade_value = fade_timeline_->currentValue();
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class MoodbarProxyStyle : public QProxyStyle {
|
|||||||
private:
|
private:
|
||||||
void NextState();
|
void NextState();
|
||||||
|
|
||||||
void Render(ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget);
|
void Render(const ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget);
|
||||||
void EnsureMoodbarRendered(const QStyleOptionSlider *opt);
|
void EnsureMoodbarRendered(const QStyleOptionSlider *opt);
|
||||||
void DrawArrow(const QStyleOptionSlider *option, QPainter *painter) const;
|
void DrawArrow(const QStyleOptionSlider *option, QPainter *painter) const;
|
||||||
void ShowContextMenu(const QPoint pos);
|
void ShowContextMenu(const QPoint pos);
|
||||||
@@ -92,7 +92,7 @@ class MoodbarProxyStyle : public QProxyStyle {
|
|||||||
static QPixmap MoodbarPixmap(const ColorVector &colors, const QSize size, const QPalette &palette, const QStyleOptionSlider *opt);
|
static QPixmap MoodbarPixmap(const ColorVector &colors, const QSize size, const QPalette &palette, const QStyleOptionSlider *opt);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void FaderValueChanged(qreal value);
|
void FaderValueChanged(const qreal value);
|
||||||
void SetStyle(QAction *action);
|
void SetStyle(QAction *action);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
|||||||
@@ -106,8 +106,7 @@ Mpris2::Mpris2(const SharedPtr<Player> player,
|
|||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
player_(player),
|
player_(player),
|
||||||
playlist_manager_(playlist_manager),
|
playlist_manager_(playlist_manager),
|
||||||
current_albumcover_loader_(current_albumcover_loader),
|
current_albumcover_loader_(current_albumcover_loader) {
|
||||||
app_name_(QCoreApplication::applicationName()) {
|
|
||||||
|
|
||||||
new Mpris2Root(this);
|
new Mpris2Root(this);
|
||||||
new Mpris2TrackList(this);
|
new Mpris2TrackList(this);
|
||||||
@@ -135,8 +134,6 @@ Mpris2::Mpris2(const SharedPtr<Player> player,
|
|||||||
QObject::connect(&*playlist_manager_, &PlaylistManager::PlaylistChanged, this, &Mpris2::PlaylistChangedSlot);
|
QObject::connect(&*playlist_manager_, &PlaylistManager::PlaylistChanged, this, &Mpris2::PlaylistChangedSlot);
|
||||||
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentChanged, this, &Mpris2::PlaylistCollectionChanged);
|
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentChanged, this, &Mpris2::PlaylistCollectionChanged);
|
||||||
|
|
||||||
app_name_[0] = app_name_[0].toUpper();
|
|
||||||
|
|
||||||
QStringList data_dirs = QString::fromUtf8(qgetenv("XDG_DATA_DIRS")).split(u':');
|
QStringList data_dirs = QString::fromUtf8(qgetenv("XDG_DATA_DIRS")).split(u':');
|
||||||
|
|
||||||
if (!data_dirs.contains("/usr/local/share"_L1)) {
|
if (!data_dirs.contains("/usr/local/share"_L1)) {
|
||||||
@@ -238,7 +235,7 @@ bool Mpris2::CanRaise() const { return true; }
|
|||||||
|
|
||||||
bool Mpris2::HasTrackList() const { return true; }
|
bool Mpris2::HasTrackList() const { return true; }
|
||||||
|
|
||||||
QString Mpris2::Identity() const { return app_name_; }
|
QString Mpris2::Identity() const { return QCoreApplication::applicationName(); }
|
||||||
|
|
||||||
QString Mpris2::DesktopEntryAbsolutePath() const {
|
QString Mpris2::DesktopEntryAbsolutePath() const {
|
||||||
|
|
||||||
@@ -386,8 +383,12 @@ void Mpris2::SetRating(double rating) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QDBusObjectPath Mpris2::current_track_id() const {
|
int Mpris2::current_playlist_row() const {
|
||||||
return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/Track/%1").arg(QString::number(playlist_manager_->active()->current_row())));
|
return playlist_manager_->active()->current_row();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusObjectPath Mpris2::current_track_id(const int current_row) const {
|
||||||
|
return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/Track/%1").arg(current_row));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We send Metadata change notification as soon as the process of changing song starts...
|
// We send Metadata change notification as soon as the process of changing song starts...
|
||||||
@@ -405,11 +406,14 @@ void Mpris2::CurrentSongChanged(const Song &song) {
|
|||||||
// ... and we add the cover information later, when it's available.
|
// ... and we add the cover information later, when it's available.
|
||||||
void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
|
void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
|
||||||
|
|
||||||
|
const int current_row = current_playlist_row();
|
||||||
|
if (current_row == -1) return;
|
||||||
|
|
||||||
last_metadata_ = QVariantMap();
|
last_metadata_ = QVariantMap();
|
||||||
song.ToXesam(&last_metadata_);
|
song.ToXesam(&last_metadata_);
|
||||||
|
|
||||||
using mpris::AddMetadata;
|
using mpris::AddMetadata;
|
||||||
AddMetadata(u"mpris:trackid"_s, current_track_id(), &last_metadata_);
|
AddMetadata(u"mpris:trackid"_s, current_track_id(current_row), &last_metadata_);
|
||||||
|
|
||||||
QUrl cover_url;
|
QUrl cover_url;
|
||||||
if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) {
|
if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) {
|
||||||
@@ -519,7 +523,11 @@ void Mpris2::Seek(qint64 offset) {
|
|||||||
|
|
||||||
void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) {
|
void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) {
|
||||||
|
|
||||||
if (CanSeek() && trackId == current_track_id() && offset >= 0) {
|
const int current_row = current_playlist_row();
|
||||||
|
|
||||||
|
if (current_row == -1) return;
|
||||||
|
|
||||||
|
if (CanSeek() && trackId == current_track_id(current_row) && offset >= 0) {
|
||||||
offset *= kNsecPerUsec;
|
offset *= kNsecPerUsec;
|
||||||
|
|
||||||
if (offset < player_->GetCurrentItem()->Metadata().length_nanosec()) {
|
if (offset < player_->GetCurrentItem()->Metadata().length_nanosec()) {
|
||||||
|
|||||||
@@ -230,7 +230,8 @@ class Mpris2 : public QObject {
|
|||||||
|
|
||||||
QString PlaybackStatus(EngineBase::State state) const;
|
QString PlaybackStatus(EngineBase::State state) const;
|
||||||
|
|
||||||
QDBusObjectPath current_track_id() const;
|
int current_playlist_row() const;
|
||||||
|
QDBusObjectPath current_track_id(const int current_row) const;
|
||||||
|
|
||||||
bool CanSeek(EngineBase::State state) const;
|
bool CanSeek(EngineBase::State state) const;
|
||||||
|
|
||||||
@@ -241,7 +242,6 @@ class Mpris2 : public QObject {
|
|||||||
const SharedPtr<PlaylistManager> playlist_manager_;
|
const SharedPtr<PlaylistManager> playlist_manager_;
|
||||||
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader_;
|
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader_;
|
||||||
|
|
||||||
QString app_name_;
|
|
||||||
QString desktopfilepath_;
|
QString desktopfilepath_;
|
||||||
QVariantMap last_metadata_;
|
QVariantMap last_metadata_;
|
||||||
|
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ void Organize::ProcessSomeFiles() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song::FileType Organize::CheckTranscode(Song::FileType original_type) const {
|
Song::FileType Organize::CheckTranscode(const Song::FileType original_type) const {
|
||||||
|
|
||||||
if (original_type == Song::FileType::Stream) return Song::FileType::Unknown;
|
if (original_type == Song::FileType::Stream) return Song::FileType::Unknown;
|
||||||
|
|
||||||
@@ -319,7 +319,7 @@ Song::FileType Organize::CheckTranscode(Song::FileType original_type) const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Organize::SetSongProgress(float progress, bool transcoded) {
|
void Organize::SetSongProgress(const float progress, const bool transcoded) {
|
||||||
|
|
||||||
const int max = transcoded ? 50 : 100;
|
const int max = transcoded ? 50 : 100;
|
||||||
current_copy_progress_ = (transcoded ? 50 : 0) + qBound(0, static_cast<int>(progress * static_cast<float>(max)), max - 1);
|
current_copy_progress_ = (transcoded ? 50 : 0) + qBound(0, static_cast<int>(progress * static_cast<float>(max)), max - 1);
|
||||||
@@ -359,7 +359,7 @@ void Organize::UpdateProgress() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Organize::FileTranscoded(const QString &input, const QString &output, bool success) {
|
void Organize::FileTranscoded(const QString &input, const QString &output, const bool success) {
|
||||||
|
|
||||||
Q_UNUSED(output);
|
Q_UNUSED(output);
|
||||||
|
|
||||||
|
|||||||
@@ -87,13 +87,13 @@ class Organize : public QObject {
|
|||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void ProcessSomeFiles();
|
void ProcessSomeFiles();
|
||||||
void FileTranscoded(const QString &input, const QString &output, bool success);
|
void FileTranscoded(const QString &input, const QString &output, const bool success);
|
||||||
void LogLine(const QString &message);
|
void LogLine(const QString &message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetSongProgress(float progress, bool transcoded = false);
|
void SetSongProgress(const float progress, const bool transcoded = false);
|
||||||
void UpdateProgress();
|
void UpdateProgress();
|
||||||
Song::FileType CheckTranscode(Song::FileType original_type) const;
|
Song::FileType CheckTranscode(const Song::FileType original_type) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Task {
|
struct Task {
|
||||||
|
|||||||
@@ -411,7 +411,6 @@ void OrganizeDialog::SetLoadingSongs(const bool loading) {
|
|||||||
SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) const {
|
SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) const {
|
||||||
|
|
||||||
SongList songs;
|
SongList songs;
|
||||||
Song song;
|
|
||||||
|
|
||||||
QStringList filenames_copy = filenames;
|
QStringList filenames_copy = filenames;
|
||||||
while (!filenames_copy.isEmpty()) {
|
while (!filenames_copy.isEmpty()) {
|
||||||
@@ -427,6 +426,7 @@ SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) const {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Song song;
|
||||||
const TagReaderResult result = tagreader_client_->ReadFileBlocking(filename, &song);
|
const TagReaderResult result = tagreader_client_->ReadFileBlocking(filename, &song);
|
||||||
if (result.success() && song.is_valid()) {
|
if (result.success() && song.is_valid()) {
|
||||||
songs << song;
|
songs << song;
|
||||||
@@ -476,6 +476,7 @@ Organize::NewSongInfoList OrganizeDialog::ComputeNewSongsFilenames(const SongLis
|
|||||||
}
|
}
|
||||||
new_songs_info << Organize::NewSongInfo(song, result.filename, result.unique_filename);
|
new_songs_info << Organize::NewSongInfo(song, result.filename, result.unique_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new_songs_info;
|
return new_songs_info;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ OSDBase::OSDBase(const SharedPtr<SystemTrayIcon> tray_icon, QObject *parent)
|
|||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
tray_icon_(tray_icon),
|
tray_icon_(tray_icon),
|
||||||
pretty_popup_(new OSDPretty(OSDPretty::Mode::Popup)),
|
pretty_popup_(new OSDPretty(OSDPretty::Mode::Popup)),
|
||||||
app_name_(QCoreApplication::applicationName()),
|
|
||||||
timeout_msec_(5000),
|
timeout_msec_(5000),
|
||||||
type_(OSDSettings::Type::Native),
|
type_(OSDSettings::Type::Native),
|
||||||
show_on_volume_change_(false),
|
show_on_volume_change_(false),
|
||||||
@@ -59,11 +58,7 @@ OSDBase::OSDBase(const SharedPtr<SystemTrayIcon> tray_icon, QObject *parent)
|
|||||||
use_custom_text_(false),
|
use_custom_text_(false),
|
||||||
force_show_next_(false),
|
force_show_next_(false),
|
||||||
ignore_next_stopped_(false),
|
ignore_next_stopped_(false),
|
||||||
playing_(false) {
|
playing_(false) {}
|
||||||
|
|
||||||
app_name_[0] = app_name_[0].toUpper();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
OSDBase::~OSDBase() {
|
OSDBase::~OSDBase() {
|
||||||
delete pretty_popup_;
|
delete pretty_popup_;
|
||||||
@@ -264,7 +259,7 @@ void OSDBase::Stopped() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OSDBase::StopAfterToggle(const bool stop) {
|
void OSDBase::StopAfterToggle(const bool stop) {
|
||||||
ShowMessage(app_name_, tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
|
ShowMessage(QCoreApplication::applicationName(), tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
|
||||||
}
|
}
|
||||||
|
|
||||||
void OSDBase::PlaylistFinished() {
|
void OSDBase::PlaylistFinished() {
|
||||||
@@ -272,7 +267,7 @@ void OSDBase::PlaylistFinished() {
|
|||||||
// We get a PlaylistFinished followed by a Stopped from the player
|
// We get a PlaylistFinished followed by a Stopped from the player
|
||||||
ignore_next_stopped_ = true;
|
ignore_next_stopped_ = true;
|
||||||
|
|
||||||
ShowMessage(app_name_, tr("Playlist finished"));
|
ShowMessage(QCoreApplication::applicationName(), tr("Playlist finished"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +285,7 @@ void OSDBase::VolumeChanged(const uint value) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ShowMessage(app_name_, message);
|
ShowMessage(QCoreApplication::applicationName(), message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +341,7 @@ void OSDBase::ShuffleModeChanged(const PlaylistSequence::ShuffleMode mode) {
|
|||||||
case PlaylistSequence::ShuffleMode::InsideAlbum: current_mode = tr("Shuffle tracks in this album"); break;
|
case PlaylistSequence::ShuffleMode::InsideAlbum: current_mode = tr("Shuffle tracks in this album"); break;
|
||||||
case PlaylistSequence::ShuffleMode::Albums: current_mode = tr("Shuffle albums"); break;
|
case PlaylistSequence::ShuffleMode::Albums: current_mode = tr("Shuffle albums"); break;
|
||||||
}
|
}
|
||||||
ShowMessage(app_name_, current_mode);
|
ShowMessage(QCoreApplication::applicationName(), current_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -363,7 +358,7 @@ void OSDBase::RepeatModeChanged(const PlaylistSequence::RepeatMode mode) {
|
|||||||
case PlaylistSequence::RepeatMode::OneByOne: current_mode = tr("Stop after every track"); break;
|
case PlaylistSequence::RepeatMode::OneByOne: current_mode = tr("Stop after every track"); break;
|
||||||
case PlaylistSequence::RepeatMode::Intro: current_mode = tr("Intro tracks"); break;
|
case PlaylistSequence::RepeatMode::Intro: current_mode = tr("Intro tracks"); break;
|
||||||
}
|
}
|
||||||
ShowMessage(app_name_, current_mode);
|
ShowMessage(QCoreApplication::applicationName(), current_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ class OSDBase : public QObject {
|
|||||||
virtual bool SupportsNativeNotifications();
|
virtual bool SupportsNativeNotifications();
|
||||||
virtual bool SupportsTrayPopups();
|
virtual bool SupportsTrayPopups();
|
||||||
|
|
||||||
QString app_name() { return app_name_; }
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
@@ -88,7 +86,6 @@ class OSDBase : public QObject {
|
|||||||
const SharedPtr<SystemTrayIcon> tray_icon_;
|
const SharedPtr<SystemTrayIcon> tray_icon_;
|
||||||
OSDPretty *pretty_popup_;
|
OSDPretty *pretty_popup_;
|
||||||
|
|
||||||
QString app_name_;
|
|
||||||
int timeout_msec_;
|
int timeout_msec_;
|
||||||
OSDSettings::Type type_;
|
OSDSettings::Type type_;
|
||||||
bool show_on_volume_change_;
|
bool show_on_volume_change_;
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ void OSDDBus::ShowMessageNative(const QString &summary, const QString &message,
|
|||||||
id = notification_id_;
|
id = notification_id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDBusPendingReply<uint> reply = interface_->Notify(app_name(), id, icon, summary_stripped, message, QStringList(), hints, timeout_msec());
|
QDBusPendingReply<uint> reply = interface_->Notify(QCoreApplication::applicationName(), id, icon, summary_stripped, message, QStringList(), hints, timeout_msec());
|
||||||
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
|
||||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &OSDDBus::CallFinished);
|
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &OSDDBus::CallFinished);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -313,8 +313,8 @@ QVariant Playlist::data(const QModelIndex &idx, const int role) const {
|
|||||||
case Qt::EditRole:
|
case Qt::EditRole:
|
||||||
case Qt::ToolTipRole:
|
case Qt::ToolTipRole:
|
||||||
case Qt::DisplayRole:{
|
case Qt::DisplayRole:{
|
||||||
PlaylistItemPtr item = items_[idx.row()];
|
const PlaylistItemPtr item = items_[idx.row()];
|
||||||
Song song = item->Metadata();
|
const Song song = item->Metadata();
|
||||||
|
|
||||||
// Don't forget to change Playlist::CompareItems when adding new columns
|
// Don't forget to change Playlist::CompareItems when adding new columns
|
||||||
switch (static_cast<Column>(idx.column())) {
|
switch (static_cast<Column>(idx.column())) {
|
||||||
@@ -423,8 +423,8 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
|
|||||||
|
|
||||||
Q_UNUSED(role);
|
Q_UNUSED(role);
|
||||||
|
|
||||||
int row = idx.row();
|
const int row = idx.row();
|
||||||
PlaylistItemPtr item = item_at(row);
|
const PlaylistItemPtr item = item_at(row);
|
||||||
Song song = item->OriginalMetadata();
|
Song song = item->OriginalMetadata();
|
||||||
|
|
||||||
if (idx.data() == value) return false;
|
if (idx.data() == value) return false;
|
||||||
@@ -487,7 +487,7 @@ void Playlist::ItemReload(const QPersistentModelIndex &idx, const Song &old_meta
|
|||||||
void Playlist::ItemReloadComplete(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit) {
|
void Playlist::ItemReloadComplete(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit) {
|
||||||
|
|
||||||
if (idx.isValid()) {
|
if (idx.isValid()) {
|
||||||
PlaylistItemPtr item = item_at(idx.row());
|
const PlaylistItemPtr item = item_at(idx.row());
|
||||||
if (item) {
|
if (item) {
|
||||||
ItemChanged(idx.row(), ChangedColumns(old_metadata, item->Metadata()));
|
ItemChanged(idx.row(), ChangedColumns(old_metadata, item->Metadata()));
|
||||||
if (idx.row() == current_row()) {
|
if (idx.row() == current_row()) {
|
||||||
@@ -555,12 +555,12 @@ int Playlist::NextVirtualIndex(int i, const bool ignore_repeat_track) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We need to advance i until we get something else on the same album
|
// We need to advance i until we get something else on the same album
|
||||||
Song last_song = current_item_metadata();
|
const Song last_song = current_item_metadata();
|
||||||
for (int j = i + 1; j < virtual_items_.count(); ++j) {
|
for (int j = i + 1; j < virtual_items_.count(); ++j) {
|
||||||
if (item_at(virtual_items_[j])->GetShouldSkip()) {
|
if (item_at(virtual_items_[j])->GetShouldSkip()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Song this_song = item_at(virtual_items_[j])->Metadata();
|
const Song this_song = item_at(virtual_items_[j])->Metadata();
|
||||||
if (((last_song.is_compilation() && this_song.is_compilation()) ||
|
if (((last_song.is_compilation() && this_song.is_compilation()) ||
|
||||||
last_song.effective_albumartist() == this_song.effective_albumartist()) &&
|
last_song.effective_albumartist() == this_song.effective_albumartist()) &&
|
||||||
last_song.album() == this_song.album() &&
|
last_song.album() == this_song.album() &&
|
||||||
@@ -677,13 +677,13 @@ int Playlist::previous_row(const bool ignore_repeat_track) {
|
|||||||
|
|
||||||
void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const bool is_stopping, const bool force_inform) {
|
void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const bool is_stopping, const bool force_inform) {
|
||||||
|
|
||||||
QPersistentModelIndex old_current_item_index = current_item_index_;
|
const QPersistentModelIndex old_current_item_index = current_item_index_;
|
||||||
QPersistentModelIndex new_current_item_index;
|
QPersistentModelIndex new_current_item_index;
|
||||||
if (i != -1) new_current_item_index = QPersistentModelIndex(index(i, 0, QModelIndex()));
|
if (i != -1) new_current_item_index = QPersistentModelIndex(index(i, 0, QModelIndex()));
|
||||||
|
|
||||||
if (new_current_item_index != current_item_index_) ClearStreamMetadata();
|
if (new_current_item_index != current_item_index_) ClearStreamMetadata();
|
||||||
|
|
||||||
int nextrow = next_row();
|
const int nextrow = next_row();
|
||||||
if (nextrow != -1 && nextrow != i) {
|
if (nextrow != -1 && nextrow != i) {
|
||||||
PlaylistItemPtr next_item = item_at(nextrow);
|
PlaylistItemPtr next_item = item_at(nextrow);
|
||||||
if (next_item) {
|
if (next_item) {
|
||||||
@@ -1127,7 +1127,7 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const in
|
|||||||
|
|
||||||
beginInsertRows(QModelIndex(), start, end);
|
beginInsertRows(QModelIndex(), start, end);
|
||||||
for (int i = start; i <= end; ++i) {
|
for (int i = start; i <= end; ++i) {
|
||||||
PlaylistItemPtr item = items[i - start];
|
const PlaylistItemPtr item = items[i - start];
|
||||||
items_.insert(i, item);
|
items_.insert(i, item);
|
||||||
virtual_items_ << static_cast<int>(virtual_items_.count());
|
virtual_items_ << static_cast<int>(virtual_items_.count());
|
||||||
|
|
||||||
@@ -1180,7 +1180,11 @@ void Playlist::InsertSongs(const SongList &songs, const int pos, const bool play
|
|||||||
InsertSongItems<SongPlaylistItem>(songs, pos, play_now, enqueue, enqueue_next);
|
InsertSongItems<SongPlaylistItem>(songs, pos, play_now, enqueue, enqueue_next);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) {
|
void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const QString &playlist_name, const int pos, const bool play_now, const bool enqueue, const bool enqueue_next) {
|
||||||
|
|
||||||
|
if (!playlist_name.isEmpty()) {
|
||||||
|
Q_EMIT Rename(id_, playlist_name);
|
||||||
|
}
|
||||||
|
|
||||||
PlaylistItemPtrList items;
|
PlaylistItemPtrList items;
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
@@ -1201,6 +1205,7 @@ void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const int pos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InsertItems(items, pos, play_now, enqueue, enqueue_next);
|
InsertItems(items, pos, play_now, enqueue, enqueue_next);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1304,11 +1309,11 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
|
|||||||
rows << idx.row();
|
rows << idx.row();
|
||||||
}
|
}
|
||||||
|
|
||||||
QBuffer buf;
|
QBuffer buffer;
|
||||||
if (!buf.open(QIODevice::WriteOnly)) {
|
if (!buffer.open(QIODevice::WriteOnly)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
QDataStream stream(&buf);
|
QDataStream stream(&buffer);
|
||||||
|
|
||||||
const Playlist *self = this;
|
const Playlist *self = this;
|
||||||
const qint64 pid = QCoreApplication::applicationPid();
|
const qint64 pid = QCoreApplication::applicationPid();
|
||||||
@@ -1316,11 +1321,11 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
|
|||||||
stream.writeRawData(reinterpret_cast<char*>(&self), sizeof(self)); // NOLINT(bugprone-sizeof-expression)
|
stream.writeRawData(reinterpret_cast<char*>(&self), sizeof(self)); // NOLINT(bugprone-sizeof-expression)
|
||||||
stream << rows;
|
stream << rows;
|
||||||
stream.writeRawData(reinterpret_cast<const char*>(&pid), sizeof(pid));
|
stream.writeRawData(reinterpret_cast<const char*>(&pid), sizeof(pid));
|
||||||
buf.close();
|
buffer.close();
|
||||||
|
|
||||||
QMimeData *mimedata = new QMimeData;
|
QMimeData *mimedata = new QMimeData;
|
||||||
mimedata->setUrls(urls);
|
mimedata->setUrls(urls);
|
||||||
mimedata->setData(QLatin1String(kRowsMimetype), buf.data());
|
mimedata->setData(QLatin1String(kRowsMimetype), buffer.data());
|
||||||
|
|
||||||
return mimedata;
|
return mimedata;
|
||||||
|
|
||||||
@@ -1607,21 +1612,21 @@ void Playlist::ItemsLoaded() {
|
|||||||
InsertItems(items, 0);
|
InsertItems(items, 0);
|
||||||
is_loading_ = false;
|
is_loading_ = false;
|
||||||
|
|
||||||
PlaylistBackend::Playlist p = playlist_backend_->GetPlaylist(id_);
|
const PlaylistBackend::Playlist playlist = playlist_backend_->GetPlaylist(id_);
|
||||||
|
|
||||||
// The newly loaded list of items might be shorter than it was before so look out for a bad last_played index
|
// The newly loaded list of items might be shorter than it was before so look out for a bad last_played index
|
||||||
last_played_item_index_ = p.last_played == -1 || p.last_played >= rowCount() ? QModelIndex() : index(p.last_played);
|
last_played_item_index_ = playlist.last_played == -1 || playlist.last_played >= rowCount() ? QModelIndex() : index(playlist.last_played);
|
||||||
|
|
||||||
if (p.dynamic_type == PlaylistGenerator::Type::Query) {
|
if (playlist.dynamic_type == PlaylistGenerator::Type::Query) {
|
||||||
PlaylistGeneratorPtr gen = PlaylistGenerator::Create(p.dynamic_type);
|
PlaylistGeneratorPtr gen = PlaylistGenerator::Create(playlist.dynamic_type);
|
||||||
if (gen) {
|
if (gen) {
|
||||||
|
|
||||||
SharedPtr<CollectionBackend> backend = nullptr;
|
SharedPtr<CollectionBackend> backend = nullptr;
|
||||||
if (p.dynamic_backend == collection_backend_->songs_table()) backend = collection_backend_;
|
if (playlist.dynamic_backend == collection_backend_->songs_table()) backend = collection_backend_;
|
||||||
|
|
||||||
if (backend) {
|
if (backend) {
|
||||||
gen->set_collection_backend(collection_backend_);
|
gen->set_collection_backend(collection_backend_);
|
||||||
gen->Load(p.dynamic_data);
|
gen->Load(playlist.dynamic_data);
|
||||||
TurnOnDynamicPlaylist(gen);
|
TurnOnDynamicPlaylist(gen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1637,7 +1642,7 @@ void Playlist::ItemsLoaded() {
|
|||||||
|
|
||||||
// Should we gray out deleted songs asynchronously on startup?
|
// Should we gray out deleted songs asynchronously on startup?
|
||||||
if (greyout) {
|
if (greyout) {
|
||||||
(void)QtConcurrent::run(&Playlist::InvalidateDeletedSongs, this);
|
InvalidateDeletedSongs();
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT PlaylistLoaded();
|
Q_EMIT PlaylistLoaded();
|
||||||
@@ -1725,11 +1730,11 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co
|
|||||||
|
|
||||||
// Remove items
|
// Remove items
|
||||||
beginRemoveRows(QModelIndex(), row, row + count - 1);
|
beginRemoveRows(QModelIndex(), row, row + count - 1);
|
||||||
PlaylistItemPtrList ret;
|
PlaylistItemPtrList items;
|
||||||
ret.reserve(count);
|
items.reserve(count);
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
PlaylistItemPtr item(items_.takeAt(row));
|
PlaylistItemPtr item(items_.takeAt(row));
|
||||||
ret << item;
|
items << item;
|
||||||
|
|
||||||
if (item->source() == Song::Source::Collection) {
|
if (item->source() == Song::Source::Collection) {
|
||||||
int id = item->Metadata().id();
|
int id = item->Metadata().id();
|
||||||
@@ -1769,13 +1774,13 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co
|
|||||||
|
|
||||||
ScheduleSave();
|
ScheduleSave();
|
||||||
|
|
||||||
return ret;
|
return items;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::StopAfter(const int row) {
|
void Playlist::StopAfter(const int row) {
|
||||||
|
|
||||||
QModelIndex old_stop_after = stop_after_;
|
const QModelIndex old_stop_after = stop_after_;
|
||||||
|
|
||||||
if ((stop_after_.isValid() && stop_after_.row() == row) || row == -1) {
|
if ((stop_after_.isValid() && stop_after_.row() == row) || row == -1) {
|
||||||
stop_after_ = QModelIndex();
|
stop_after_ = QModelIndex();
|
||||||
@@ -1911,9 +1916,9 @@ void Playlist::RemoveItemsNotInQueue() {
|
|||||||
|
|
||||||
void Playlist::ReloadItems(const QList<int> &rows) {
|
void Playlist::ReloadItems(const QList<int> &rows) {
|
||||||
|
|
||||||
for (int row : rows) {
|
for (const int row : rows) {
|
||||||
PlaylistItemPtr item = item_at(row);
|
const PlaylistItemPtr item = item_at(row);
|
||||||
QPersistentModelIndex idx = index(row, 0);
|
const QPersistentModelIndex idx = index(row, 0);
|
||||||
if (idx.isValid()) {
|
if (idx.isValid()) {
|
||||||
ItemReload(idx, item->Metadata(), false);
|
ItemReload(idx, item->Metadata(), false);
|
||||||
}
|
}
|
||||||
@@ -1921,18 +1926,6 @@ void Playlist::ReloadItems(const QList<int> &rows) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::ReloadItemsBlocking(const QList<int> &rows) {
|
|
||||||
|
|
||||||
for (int row : rows) {
|
|
||||||
PlaylistItemPtr item = item_at(row);
|
|
||||||
Song old_metadata = item->Metadata();
|
|
||||||
item->Reload();
|
|
||||||
QPersistentModelIndex idx = index(row, 0);
|
|
||||||
ItemReloadComplete(idx, old_metadata, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Playlist::Shuffle() {
|
void Playlist::Shuffle() {
|
||||||
|
|
||||||
PlaylistItemPtrList new_items(items_);
|
PlaylistItemPtrList new_items(items_);
|
||||||
@@ -1951,7 +1944,7 @@ void Playlist::Shuffle() {
|
|||||||
|
|
||||||
const int count = static_cast<int>(items_.count());
|
const int count = static_cast<int>(items_.count());
|
||||||
for (int i = begin; i < count; ++i) {
|
for (int i = begin; i < count; ++i) {
|
||||||
int new_pos = i + (rand() % (count - i));
|
const int new_pos = i + (rand() % (count - i));
|
||||||
|
|
||||||
std::swap(new_items[i], new_items[new_pos]);
|
std::swap(new_items[i], new_items[new_pos]);
|
||||||
}
|
}
|
||||||
@@ -2053,12 +2046,12 @@ PlaylistFilter *Playlist::filter() const { return filter_; }
|
|||||||
|
|
||||||
SongList Playlist::GetAllSongs() const {
|
SongList Playlist::GetAllSongs() const {
|
||||||
|
|
||||||
SongList ret;
|
SongList songs;
|
||||||
ret.reserve(items_.count());
|
songs.reserve(items_.count());
|
||||||
for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference
|
for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference
|
||||||
ret << item->Metadata();
|
songs << item->Metadata();
|
||||||
}
|
}
|
||||||
return ret;
|
return songs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2066,12 +2059,13 @@ PlaylistItemPtrList Playlist::GetAllItems() const { return items_; }
|
|||||||
|
|
||||||
quint64 Playlist::GetTotalLength() const {
|
quint64 Playlist::GetTotalLength() const {
|
||||||
|
|
||||||
quint64 ret = 0;
|
quint64 total_length = 0;
|
||||||
for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference
|
for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference
|
||||||
qint64 length = item->Metadata().length_nanosec();
|
qint64 length = item->Metadata().length_nanosec();
|
||||||
if (length > 0) ret += length;
|
if (length > 0) total_length += length;
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
|
return total_length;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2112,7 +2106,7 @@ void Playlist::TracksEnqueued(const QModelIndex &parent_idx, const int begin, co
|
|||||||
void Playlist::QueueLayoutChanged() {
|
void Playlist::QueueLayoutChanged() {
|
||||||
|
|
||||||
for (int i = 0; i < queue_->rowCount(); ++i) {
|
for (int i = 0; i < queue_->rowCount(); ++i) {
|
||||||
const QModelIndex &idx = queue_->mapToSource(queue_->index(i, static_cast<int>(Column::Title)));
|
const QModelIndex idx = queue_->mapToSource(queue_->index(i, static_cast<int>(Column::Title)));
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2309,10 +2303,10 @@ void Playlist::InvalidateDeletedSongs() {
|
|||||||
|
|
||||||
for (int row = 0; row < items_.count(); ++row) {
|
for (int row = 0; row < items_.count(); ++row) {
|
||||||
PlaylistItemPtr item = items_.value(row);
|
PlaylistItemPtr item = items_.value(row);
|
||||||
Song song = item->Metadata();
|
const Song song = item->Metadata();
|
||||||
|
|
||||||
if (song.url().isLocalFile()) {
|
if (song.url().isValid() && song.url().isLocalFile()) {
|
||||||
bool exists = QFile::exists(song.url().toLocalFile());
|
const bool exists = QFile::exists(song.url().toLocalFile());
|
||||||
|
|
||||||
if (!exists && !item->HasForegroundColor(kInvalidSongPriority)) {
|
if (!exists && !item->HasForegroundColor(kInvalidSongPriority)) {
|
||||||
// Gray out the song if it's not there
|
// Gray out the song if it's not there
|
||||||
@@ -2327,12 +2321,7 @@ void Playlist::InvalidateDeletedSongs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!invalidated_rows.isEmpty()) {
|
if (!invalidated_rows.isEmpty()) {
|
||||||
if (QThread::currentThread() == thread()) {
|
ReloadItems(invalidated_rows);
|
||||||
ReloadItems(invalidated_rows);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ReloadItemsBlocking(invalidated_rows);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2342,8 +2331,8 @@ void Playlist::RemoveDeletedSongs() {
|
|||||||
QList<int> rows_to_remove;
|
QList<int> rows_to_remove;
|
||||||
|
|
||||||
for (int row = 0; row < items_.count(); ++row) {
|
for (int row = 0; row < items_.count(); ++row) {
|
||||||
PlaylistItemPtr item = items_.value(row);
|
const PlaylistItemPtr item = items_.value(row);
|
||||||
Song song = item->Metadata();
|
const Song song = item->Metadata();
|
||||||
|
|
||||||
if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) {
|
if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) {
|
||||||
rows_to_remove.append(row); // clazy:exclude=reserve-candidates
|
rows_to_remove.append(row); // clazy:exclude=reserve-candidates
|
||||||
@@ -2376,7 +2365,7 @@ void Playlist::RemoveDuplicateSongs() {
|
|||||||
std::unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
|
std::unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
|
||||||
|
|
||||||
for (int row = 0; row < items_.count(); ++row) {
|
for (int row = 0; row < items_.count(); ++row) {
|
||||||
PlaylistItemPtr item = items_.value(row);
|
const PlaylistItemPtr item = items_.value(row);
|
||||||
const Song &song = item->Metadata();
|
const Song &song = item->Metadata();
|
||||||
|
|
||||||
bool found_duplicate = false;
|
bool found_duplicate = false;
|
||||||
@@ -2409,7 +2398,7 @@ void Playlist::RemoveUnavailableSongs() {
|
|||||||
|
|
||||||
QList<int> rows_to_remove;
|
QList<int> rows_to_remove;
|
||||||
for (int row = 0; row < items_.count(); ++row) {
|
for (int row = 0; row < items_.count(); ++row) {
|
||||||
PlaylistItemPtr item = items_.value(row);
|
const PlaylistItemPtr item = items_.value(row);
|
||||||
const Song &song = item->Metadata();
|
const Song &song = item->Metadata();
|
||||||
|
|
||||||
// Check only local files
|
// Check only local files
|
||||||
@@ -2424,10 +2413,10 @@ void Playlist::RemoveUnavailableSongs() {
|
|||||||
|
|
||||||
bool Playlist::ApplyValidityOnCurrentSong(const QUrl &url, const bool valid) {
|
bool Playlist::ApplyValidityOnCurrentSong(const QUrl &url, const bool valid) {
|
||||||
|
|
||||||
PlaylistItemPtr current = current_item();
|
const PlaylistItemPtr current = current_item();
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
Song current_song = current->Metadata();
|
const Song current_song = current->Metadata();
|
||||||
|
|
||||||
// If validity has changed, reload the item
|
// If validity has changed, reload the item
|
||||||
if (current_song.source() == Song::Source::LocalFile || current_song.source() == Song::Source::Collection) {
|
if (current_song.source() == Song::Source::LocalFile || current_song.source() == Song::Source::Collection) {
|
||||||
@@ -2523,7 +2512,7 @@ void Playlist::TurnOffDynamicPlaylist() {
|
|||||||
void Playlist::RateSong(const QModelIndex &idx, const float rating) {
|
void Playlist::RateSong(const QModelIndex &idx, const float rating) {
|
||||||
|
|
||||||
if (has_item_at(idx.row())) {
|
if (has_item_at(idx.row())) {
|
||||||
PlaylistItemPtr item = item_at(idx.row());
|
const PlaylistItemPtr item = item_at(idx.row());
|
||||||
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
|
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
|
||||||
collection_backend_->UpdateSongRatingAsync(item->Metadata().id(), rating);
|
collection_backend_->UpdateSongRatingAsync(item->Metadata().id(), rating);
|
||||||
}
|
}
|
||||||
@@ -2537,7 +2526,7 @@ void Playlist::RateSongs(const QModelIndexList &index_list, const float rating)
|
|||||||
for (const QModelIndex &idx : index_list) {
|
for (const QModelIndex &idx : index_list) {
|
||||||
const int row = idx.row();
|
const int row = idx.row();
|
||||||
if (has_item_at(row)) {
|
if (has_item_at(row)) {
|
||||||
PlaylistItemPtr item = item_at(row);
|
const PlaylistItemPtr item = item_at(row);
|
||||||
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
|
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
|
||||||
id_list << item->Metadata().id(); // clazy:exclude=reserve-candidates
|
id_list << item->Metadata().id(); // clazy:exclude=reserve-candidates
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ class Playlist : public QAbstractListModel {
|
|||||||
void InsertItems(const PlaylistItemPtrList &itemsIn, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
void InsertItems(const PlaylistItemPtrList &itemsIn, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||||
void InsertCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
void InsertCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||||
void InsertSongs(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
void InsertSongs(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||||
void InsertSongsOrCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
void InsertSongsOrCollectionItems(const SongList &songs, const QString &playlist_name = QString(), const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||||
void InsertSmartPlaylist(PlaylistGeneratorPtr gen, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
void InsertSmartPlaylist(PlaylistGeneratorPtr gen, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||||
void InsertStreamingItems(StreamingServicePtr service, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
void InsertStreamingItems(StreamingServicePtr service, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||||
void InsertRadioItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
void InsertRadioItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||||
@@ -239,14 +239,12 @@ class Playlist : public QAbstractListModel {
|
|||||||
// song will be reloaded to even out the situation because obviously something has changed.
|
// song will be reloaded to even out the situation because obviously something has changed.
|
||||||
// This returns true if this playlist had current item when the method was invoked.
|
// This returns true if this playlist had current item when the method was invoked.
|
||||||
bool ApplyValidityOnCurrentSong(const QUrl &url, bool valid);
|
bool ApplyValidityOnCurrentSong(const QUrl &url, bool valid);
|
||||||
// Grays out and reloads all deleted songs in all playlists. Also, "ungreys" those songs which were once deleted but now got restored somehow.
|
|
||||||
void InvalidateDeletedSongs();
|
|
||||||
// Removes from the playlist all local files that don't exist anymore.
|
// Removes from the playlist all local files that don't exist anymore.
|
||||||
void RemoveDeletedSongs();
|
void RemoveDeletedSongs();
|
||||||
|
|
||||||
void StopAfter(const int row);
|
void StopAfter(const int row);
|
||||||
void ReloadItems(const QList<int> &rows);
|
void ReloadItems(const QList<int> &rows);
|
||||||
void ReloadItemsBlocking(const QList<int> &rows);
|
|
||||||
void InformOfCurrentSongChange(const bool minor);
|
void InformOfCurrentSongChange(const bool minor);
|
||||||
|
|
||||||
// Just emits the dataChanged() signal so the mood column is repainted.
|
// Just emits the dataChanged() signal so the mood column is repainted.
|
||||||
@@ -329,6 +327,8 @@ class Playlist : public QAbstractListModel {
|
|||||||
// Signals that the queue has changed, meaning that the remaining queued items should update their position.
|
// Signals that the queue has changed, meaning that the remaining queued items should update their position.
|
||||||
void QueueChanged();
|
void QueueChanged();
|
||||||
|
|
||||||
|
void Rename(const int id, const QString &name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetCurrentIsPaused(const bool paused);
|
void SetCurrentIsPaused(const bool paused);
|
||||||
int NextVirtualIndex(int i, const bool ignore_repeat_track) const;
|
int NextVirtualIndex(int i, const bool ignore_repeat_track) const;
|
||||||
@@ -354,6 +354,9 @@ class Playlist : public QAbstractListModel {
|
|||||||
void TurnOnDynamicPlaylist(PlaylistGeneratorPtr gen);
|
void TurnOnDynamicPlaylist(PlaylistGeneratorPtr gen);
|
||||||
void InsertDynamicItems(const int count);
|
void InsertDynamicItems(const int count);
|
||||||
|
|
||||||
|
// Grays out and reloads all deleted songs in all playlists. Also, "ungreys" those songs which were once deleted but now got restored somehow.
|
||||||
|
void InvalidateDeletedSongs();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void TracksAboutToBeDequeued(const QModelIndex&, const int begin, const int end);
|
void TracksAboutToBeDequeued(const QModelIndex&, const int begin, const int end);
|
||||||
void TracksDequeued();
|
void TracksDequeued();
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ PlaylistItemPtr PlaylistBackend::RestoreCueData(PlaylistItemPtr item, SharedPtr<
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList song_list;
|
SongList songs;
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&state->mutex_);
|
QMutexLocker locker(&state->mutex_);
|
||||||
|
|
||||||
@@ -306,16 +306,16 @@ PlaylistItemPtr PlaylistBackend::RestoreCueData(PlaylistItemPtr item, SharedPtr<
|
|||||||
QFile cue_file(cue_path);
|
QFile cue_file(cue_path);
|
||||||
if (!cue_file.open(QIODevice::ReadOnly)) return item;
|
if (!cue_file.open(QIODevice::ReadOnly)) return item;
|
||||||
|
|
||||||
song_list = cue_parser.Load(&cue_file, cue_path, QDir(cue_path.section(u'/', 0, -2)));
|
songs = cue_parser.Load(&cue_file, cue_path, QDir(cue_path.section(u'/', 0, -2))).songs;
|
||||||
cue_file.close();
|
cue_file.close();
|
||||||
state->cached_cues_[cue_path] = song_list;
|
state->cached_cues_[cue_path] = songs;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
song_list = state->cached_cues_[cue_path];
|
songs = state->cached_cues_[cue_path];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const Song &from_list : std::as_const(song_list)) {
|
for (const Song &from_list : std::as_const(songs)) {
|
||||||
if (from_list.url().toEncoded() == song.url().toEncoded() && from_list.beginning_nanosec() == song.beginning_nanosec()) {
|
if (from_list.url().toEncoded() == song.url().toEncoded() && from_list.beginning_nanosec() == song.beginning_nanosec()) {
|
||||||
// We found a matching section; replace the input item with a new one containing CUE metadata
|
// We found a matching section; replace the input item with a new one containing CUE metadata
|
||||||
return make_shared<SongPlaylistItem>(from_list);
|
return make_shared<SongPlaylistItem>(from_list);
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ Playlist *PlaylistManager::AddPlaylist(const int id, const QString &name, const
|
|||||||
QObject::connect(ret, &Playlist::EditingFinished, this, &PlaylistManager::EditingFinished);
|
QObject::connect(ret, &Playlist::EditingFinished, this, &PlaylistManager::EditingFinished);
|
||||||
QObject::connect(ret, &Playlist::Error, this, &PlaylistManager::Error);
|
QObject::connect(ret, &Playlist::Error, this, &PlaylistManager::Error);
|
||||||
QObject::connect(ret, &Playlist::PlayRequested, this, &PlaylistManager::PlayRequested);
|
QObject::connect(ret, &Playlist::PlayRequested, this, &PlaylistManager::PlayRequested);
|
||||||
|
QObject::connect(ret, &Playlist::Rename, this, &PlaylistManager::Rename);
|
||||||
QObject::connect(playlist_container_->view(), &PlaylistView::ColumnAlignmentChanged, ret, &Playlist::SetColumnAlignment);
|
QObject::connect(playlist_container_->view(), &PlaylistView::ColumnAlignmentChanged, ret, &Playlist::SetColumnAlignment);
|
||||||
QObject::connect(&*current_albumcover_loader_, &CurrentAlbumCoverLoader::AlbumCoverLoaded, ret, &Playlist::AlbumCoverLoaded);
|
QObject::connect(&*current_albumcover_loader_, &CurrentAlbumCoverLoader::AlbumCoverLoaded, ret, &Playlist::AlbumCoverLoaded);
|
||||||
|
|
||||||
@@ -208,7 +209,7 @@ void PlaylistManager::Load(const QString &filename) {
|
|||||||
|
|
||||||
QFileInfo fileinfo(filename);
|
QFileInfo fileinfo(filename);
|
||||||
|
|
||||||
int id = playlist_backend_->CreatePlaylist(fileinfo.completeBaseName(), QString());
|
const int id = playlist_backend_->CreatePlaylist(fileinfo.completeBaseName(), QString());
|
||||||
|
|
||||||
if (id == -1) {
|
if (id == -1) {
|
||||||
Q_EMIT Error(tr("Couldn't create playlist"));
|
Q_EMIT Error(tr("Couldn't create playlist"));
|
||||||
@@ -221,17 +222,17 @@ void PlaylistManager::Load(const QString &filename) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::Save(const int id, const QString &filename, const PlaylistSettings::PathType path_type) {
|
void PlaylistManager::Save(const int id, const QString &playlist_name, const QString &filename, const PlaylistSettings::PathType path_type) {
|
||||||
|
|
||||||
if (playlists_.contains(id)) {
|
if (playlists_.contains(id)) {
|
||||||
parser_->Save(playlist(id)->GetAllSongs(), filename, path_type);
|
parser_->Save(playlist_name, playlist(id)->GetAllSongs(), filename, path_type);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Playlist is not in the playlist manager: probably save action was triggered from the left sidebar and the playlist isn't loaded.
|
// Playlist is not in the playlist manager: probably save action was triggered from the left sidebar and the playlist isn't loaded.
|
||||||
QFuture<SongList> future = QtConcurrent::run(&PlaylistBackend::GetPlaylistSongs, playlist_backend_, id);
|
QFuture<SongList> future = QtConcurrent::run(&PlaylistBackend::GetPlaylistSongs, playlist_backend_, id);
|
||||||
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
|
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
|
||||||
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, filename, path_type]() {
|
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, playlist_name, filename, path_type]() {
|
||||||
ItemsLoadedForSavePlaylist(watcher->result(), filename, path_type);
|
ItemsLoadedForSavePlaylist(playlist_name, watcher->result(), filename, path_type);
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
});
|
});
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
@@ -239,9 +240,9 @@ void PlaylistManager::Save(const int id, const QString &filename, const Playlist
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::ItemsLoadedForSavePlaylist(const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type) {
|
void PlaylistManager::ItemsLoadedForSavePlaylist(const QString &playlist_name, const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type) {
|
||||||
|
|
||||||
parser_->Save(songs, filename, path_type);
|
parser_->Save(playlist_name, songs, filename, path_type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +256,7 @@ void PlaylistManager::SaveWithUI(const int id, const QString &playlist_name) {
|
|||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
QString suggested_filename = playlist_name;
|
QString suggested_filename = playlist_name;
|
||||||
QString filename = last_save_path + QLatin1Char('/') + suggested_filename.remove(QRegularExpression(QLatin1String(kProblematicCharactersRegex), QRegularExpression::CaseInsensitiveOption)) + QLatin1Char('.') + last_save_extension;
|
QString filename = last_save_path + QLatin1Char('/') + suggested_filename.remove(u'/').remove(QRegularExpression(QLatin1String(kProblematicCharactersRegex), QRegularExpression::CaseInsensitiveOption)) + QLatin1Char('.') + last_save_extension;
|
||||||
|
|
||||||
QFileInfo fileinfo;
|
QFileInfo fileinfo;
|
||||||
Q_FOREVER {
|
Q_FOREVER {
|
||||||
@@ -283,7 +284,7 @@ void PlaylistManager::SaveWithUI(const int id, const QString &playlist_name) {
|
|||||||
s.setValue(PlaylistSettings::kLastSaveExtension, fileinfo.suffix());
|
s.setValue(PlaylistSettings::kLastSaveExtension, fileinfo.suffix());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
Save(id == -1 ? current_id() : id, filename, path_type);
|
Save(id == -1 ? current_id() : id, playlist_name, filename, path_type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,15 +526,6 @@ void PlaylistManager::RemoveCurrentSong() const {
|
|||||||
active()->removeRows(active()->current_index().row(), 1);
|
active()->removeRows(active()->current_index().row(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::InvalidateDeletedSongs() {
|
|
||||||
|
|
||||||
const QList<Playlist*> playlists = GetAllPlaylists();
|
|
||||||
for (Playlist *playlist : playlists) {
|
|
||||||
playlist->InvalidateDeletedSongs();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlaylistManager::RemoveDeletedSongs() {
|
void PlaylistManager::RemoveDeletedSongs() {
|
||||||
|
|
||||||
const QList<Playlist*> playlists = GetAllPlaylists();
|
const QList<Playlist*> playlists = GetAllPlaylists();
|
||||||
@@ -618,7 +610,7 @@ void PlaylistManager::SaveAllPlaylists() {
|
|||||||
for (QMap<int, Data>::const_iterator it = playlists_.constBegin(); it != playlists_.constEnd(); ++it) {
|
for (QMap<int, Data>::const_iterator it = playlists_.constBegin(); it != playlists_.constEnd(); ++it) {
|
||||||
const Data &data = *it;
|
const Data &data = *it;
|
||||||
const QString filepath = path + QLatin1Char('/') + data.name + QLatin1Char('.') + extension;
|
const QString filepath = path + QLatin1Char('/') + data.name + QLatin1Char('.') + extension;
|
||||||
Save(it.key(), filepath, path_type);
|
Save(it.key(), data.name, filepath, path_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,8 +74,6 @@ class PlaylistManager : public PlaylistManagerInterface {
|
|||||||
|
|
||||||
// Returns the collection of playlists managed by this PlaylistManager.
|
// Returns the collection of playlists managed by this PlaylistManager.
|
||||||
QList<Playlist*> GetAllPlaylists() const override;
|
QList<Playlist*> GetAllPlaylists() const override;
|
||||||
// Grays out and reloads all deleted songs in all playlists.
|
|
||||||
void InvalidateDeletedSongs() override;
|
|
||||||
// Removes all deleted songs from all playlists.
|
// Removes all deleted songs from all playlists.
|
||||||
void RemoveDeletedSongs() override;
|
void RemoveDeletedSongs() override;
|
||||||
// Returns true if the playlist is open
|
// Returns true if the playlist is open
|
||||||
@@ -99,7 +97,7 @@ class PlaylistManager : public PlaylistManagerInterface {
|
|||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void New(const QString &name, const SongList &songs = SongList(), const QString &special_type = QString()) override;
|
void New(const QString &name, const SongList &songs = SongList(), const QString &special_type = QString()) override;
|
||||||
void Load(const QString &filename) override;
|
void Load(const QString &filename) override;
|
||||||
void Save(const int id, const QString &filename, const PlaylistSettings::PathType path_type) override;
|
void Save(const int id, const QString &playlist_name, const QString &filename, const PlaylistSettings::PathType path_type) override;
|
||||||
// Display a file dialog to let user choose a file before saving the file
|
// Display a file dialog to let user choose a file before saving the file
|
||||||
void SaveWithUI(const int id, const QString &playlist_name);
|
void SaveWithUI(const int id, const QString &playlist_name);
|
||||||
void Rename(const int id, const QString &new_name) override;
|
void Rename(const int id, const QString &new_name) override;
|
||||||
@@ -150,7 +148,7 @@ class PlaylistManager : public PlaylistManagerInterface {
|
|||||||
void OneOfPlaylistsChanged();
|
void OneOfPlaylistsChanged();
|
||||||
void UpdateSummaryText();
|
void UpdateSummaryText();
|
||||||
void UpdateCollectionSongs(const SongList &songs);
|
void UpdateCollectionSongs(const SongList &songs);
|
||||||
void ItemsLoadedForSavePlaylist(const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type);
|
void ItemsLoadedForSavePlaylist(const QString &playlist_name, const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type);
|
||||||
void PlaylistLoaded();
|
void PlaylistLoaded();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -59,8 +59,6 @@ class PlaylistManagerInterface : public QObject {
|
|||||||
|
|
||||||
// Returns the collection of playlists managed by this PlaylistManager.
|
// Returns the collection of playlists managed by this PlaylistManager.
|
||||||
virtual QList<Playlist*> GetAllPlaylists() const = 0;
|
virtual QList<Playlist*> GetAllPlaylists() const = 0;
|
||||||
// Grays out and reloads all deleted songs in all playlists.
|
|
||||||
virtual void InvalidateDeletedSongs() = 0;
|
|
||||||
// Removes all deleted songs from all playlists.
|
// Removes all deleted songs from all playlists.
|
||||||
virtual void RemoveDeletedSongs() = 0;
|
virtual void RemoveDeletedSongs() = 0;
|
||||||
|
|
||||||
@@ -81,7 +79,7 @@ class PlaylistManagerInterface : public QObject {
|
|||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
virtual void New(const QString &name, const SongList &songs = SongList(), const QString &special_type = QString()) = 0;
|
virtual void New(const QString &name, const SongList &songs = SongList(), const QString &special_type = QString()) = 0;
|
||||||
virtual void Load(const QString &filename) = 0;
|
virtual void Load(const QString &filename) = 0;
|
||||||
virtual void Save(const int id, const QString &filename, const PlaylistSettings::PathType path_type) = 0;
|
virtual void Save(const int id, const QString &playlist_name, const QString &filename, const PlaylistSettings::PathType path_type) = 0;
|
||||||
virtual void Rename(const int id, const QString &new_name) = 0;
|
virtual void Rename(const int id, const QString &new_name) = 0;
|
||||||
virtual void Delete(const int id) = 0;
|
virtual void Delete(const int id) = 0;
|
||||||
virtual bool Close(const int id) = 0;
|
virtual bool Close(const int id) = 0;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ SongLoaderInserter::SongLoaderInserter(const SharedPtr<TaskManager> task_manager
|
|||||||
|
|
||||||
SongLoaderInserter::~SongLoaderInserter() { qDeleteAll(pending_); }
|
SongLoaderInserter::~SongLoaderInserter() { qDeleteAll(pending_); }
|
||||||
|
|
||||||
void SongLoaderInserter::Load(Playlist *destination, int row, bool play_now, bool enqueue, bool enqueue_next, const QList<QUrl> &urls) {
|
void SongLoaderInserter::Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, const QList<QUrl> &urls) {
|
||||||
|
|
||||||
destination_ = destination;
|
destination_ = destination;
|
||||||
row_ = row;
|
row_ = row;
|
||||||
@@ -69,15 +69,16 @@ void SongLoaderInserter::Load(Playlist *destination, int row, bool play_now, boo
|
|||||||
for (const QUrl &url : urls) {
|
for (const QUrl &url : urls) {
|
||||||
SongLoader *loader = new SongLoader(url_handlers_, collection_backend_, tagreader_client_, this);
|
SongLoader *loader = new SongLoader(url_handlers_, collection_backend_, tagreader_client_, this);
|
||||||
|
|
||||||
SongLoader::Result ret = loader->Load(url);
|
const SongLoader::Result result = loader->Load(url);
|
||||||
|
|
||||||
if (ret == SongLoader::Result::BlockingLoadRequired) {
|
if (result == SongLoader::Result::BlockingLoadRequired) {
|
||||||
pending_.append(loader);
|
pending_.append(loader);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == SongLoader::Result::Success) {
|
if (result == SongLoader::Result::Success) {
|
||||||
songs_ << loader->songs();
|
songs_ << loader->songs();
|
||||||
|
playlist_name_ = loader->playlist_name();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const QStringList errors = loader->errors();
|
const QStringList errors = loader->errors();
|
||||||
@@ -101,7 +102,7 @@ void SongLoaderInserter::Load(Playlist *destination, int row, bool play_now, boo
|
|||||||
// First, we add tracks (without metadata) into the playlist
|
// First, we add tracks (without metadata) into the playlist
|
||||||
// In the meantime, MusicBrainz will be queried to get songs' metadata.
|
// In the meantime, MusicBrainz will be queried to get songs' metadata.
|
||||||
// AudioCDTagsLoaded will be called next, and playlist's items will be updated.
|
// AudioCDTagsLoaded will be called next, and playlist's items will be updated.
|
||||||
void SongLoaderInserter::LoadAudioCD(Playlist *destination, int row, bool play_now, bool enqueue, bool enqueue_next) {
|
void SongLoaderInserter::LoadAudioCD(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next) {
|
||||||
|
|
||||||
destination_ = destination;
|
destination_ = destination;
|
||||||
row_ = row;
|
row_ = row;
|
||||||
@@ -113,8 +114,8 @@ void SongLoaderInserter::LoadAudioCD(Playlist *destination, int row, bool play_n
|
|||||||
QObject::connect(loader, &SongLoader::AudioCDTracksLoadFinished, this, [this, loader]() { AudioCDTracksLoadFinished(loader); });
|
QObject::connect(loader, &SongLoader::AudioCDTracksLoadFinished, this, [this, loader]() { AudioCDTracksLoadFinished(loader); });
|
||||||
QObject::connect(loader, &SongLoader::LoadAudioCDFinished, this, &SongLoaderInserter::AudioCDTagsLoaded);
|
QObject::connect(loader, &SongLoader::LoadAudioCDFinished, this, &SongLoaderInserter::AudioCDTagsLoaded);
|
||||||
qLog(Info) << "Loading audio CD...";
|
qLog(Info) << "Loading audio CD...";
|
||||||
SongLoader::Result ret = loader->LoadAudioCD();
|
const SongLoader::Result result = loader->LoadAudioCD();
|
||||||
if (ret == SongLoader::Result::Error) {
|
if (result == SongLoader::Result::Error) {
|
||||||
if (loader->errors().isEmpty())
|
if (loader->errors().isEmpty())
|
||||||
Q_EMIT Error(tr("Error while loading audio CD."));
|
Q_EMIT Error(tr("Error while loading audio CD."));
|
||||||
else {
|
else {
|
||||||
@@ -166,7 +167,7 @@ void SongLoaderInserter::InsertSongs() {
|
|||||||
|
|
||||||
// Insert songs (that haven't been completely loaded) to allow user to see and play them while not loaded completely
|
// Insert songs (that haven't been completely loaded) to allow user to see and play them while not loaded completely
|
||||||
if (destination_) {
|
if (destination_) {
|
||||||
destination_->InsertSongsOrCollectionItems(songs_, row_, play_now_, enqueue_, enqueue_next_);
|
destination_->InsertSongsOrCollectionItems(songs_, playlist_name_, row_, play_now_, enqueue_, enqueue_next_);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -180,10 +181,10 @@ void SongLoaderInserter::AsyncLoad() {
|
|||||||
bool first_loaded = false;
|
bool first_loaded = false;
|
||||||
for (int i = 0; i < pending_.count(); ++i) {
|
for (int i = 0; i < pending_.count(); ++i) {
|
||||||
SongLoader *loader = pending_.value(i);
|
SongLoader *loader = pending_.value(i);
|
||||||
SongLoader::Result res = loader->LoadFilenamesBlocking();
|
const SongLoader::Result result = loader->LoadFilenamesBlocking();
|
||||||
task_manager_->SetTaskProgress(async_load_id, ++async_progress);
|
task_manager_->SetTaskProgress(async_load_id, ++async_progress);
|
||||||
|
|
||||||
if (res == SongLoader::Result::Error) {
|
if (result == SongLoader::Result::Error) {
|
||||||
const QStringList errors = loader->errors();
|
const QStringList errors = loader->errors();
|
||||||
for (const QString &error : errors) {
|
for (const QString &error : errors) {
|
||||||
Q_EMIT Error(error);
|
Q_EMIT Error(error);
|
||||||
@@ -199,6 +200,7 @@ void SongLoaderInserter::AsyncLoad() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
songs_ << loader->songs();
|
songs_ << loader->songs();
|
||||||
|
playlist_name_ = loader->playlist_name();
|
||||||
|
|
||||||
}
|
}
|
||||||
task_manager_->SetTaskFinished(async_load_id);
|
task_manager_->SetTaskFinished(async_load_id);
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ class SongLoaderInserter : public QObject {
|
|||||||
|
|
||||||
~SongLoaderInserter() override;
|
~SongLoaderInserter() override;
|
||||||
|
|
||||||
void Load(Playlist *destination, int row, bool play_now, bool enqueue, bool enqueue_next, const QList<QUrl> &urls);
|
void Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, const QList<QUrl> &urls);
|
||||||
void LoadAudioCD(Playlist *destination, int row, bool play_now, bool enqueue, bool enqueue_next);
|
void LoadAudioCD(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void Error(const QString &message);
|
void Error(const QString &message);
|
||||||
@@ -82,6 +82,7 @@ class SongLoaderInserter : public QObject {
|
|||||||
bool enqueue_next_;
|
bool enqueue_next_;
|
||||||
|
|
||||||
SongList songs_;
|
SongList songs_;
|
||||||
|
QString playlist_name_;
|
||||||
|
|
||||||
QList<SongLoader*> pending_;
|
QList<SongLoader*> pending_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ bool AsxIniParser::TryMagic(const QByteArray &data) const {
|
|||||||
return data.toLower().contains("[reference]");
|
return data.toLower().contains("[reference]");
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList AsxIniParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
ParserBase::LoadResult AsxIniParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
||||||
|
|
||||||
Q_UNUSED(playlist_path);
|
Q_UNUSED(playlist_path);
|
||||||
|
|
||||||
@@ -66,7 +66,9 @@ SongList AsxIniParser::Load(QIODevice *device, const QString &playlist_path, con
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsxIniParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
void AsxIniParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
||||||
|
|
||||||
|
Q_UNUSED(playlist_name)
|
||||||
|
|
||||||
QTextStream s(device);
|
QTextStream s(device);
|
||||||
s << "[Reference]" << Qt::endl;
|
s << "[Reference]" << Qt::endl;
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ class AsxIniParser : public ParserBase {
|
|||||||
|
|
||||||
bool TryMagic(const QByteArray &data) const override;
|
bool TryMagic(const QByteArray &data) const override;
|
||||||
|
|
||||||
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
||||||
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ASXINIPARSER_H
|
#endif // ASXINIPARSER_H
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class CollectionBackendInterface;
|
|||||||
ASXParser::ASXParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
ASXParser::ASXParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
||||||
: XMLParser(tagreader_client, collection_backend, parent) {}
|
: XMLParser(tagreader_client, collection_backend, parent) {}
|
||||||
|
|
||||||
SongList ASXParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
ParserBase::LoadResult ASXParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
||||||
|
|
||||||
Q_UNUSED(playlist_path);
|
Q_UNUSED(playlist_path);
|
||||||
|
|
||||||
@@ -133,8 +133,9 @@ return_song:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASXParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
void ASXParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
||||||
|
|
||||||
|
Q_UNUSED(playlist_name)
|
||||||
Q_UNUSED(dir)
|
Q_UNUSED(dir)
|
||||||
Q_UNUSED(path_type)
|
Q_UNUSED(path_type)
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ class ASXParser : public XMLParser {
|
|||||||
|
|
||||||
bool TryMagic(const QByteArray &data) const override;
|
bool TryMagic(const QByteArray &data) const override;
|
||||||
|
|
||||||
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
||||||
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Song ParseTrack(QXmlStreamReader *reader, const QDir &dir, const bool collection_lookup) const;
|
Song ParseTrack(QXmlStreamReader *reader, const QDir &dir, const bool collection_lookup) const;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ constexpr char kDisc[] = "discnumber";
|
|||||||
CueParser::CueParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
CueParser::CueParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
||||||
: ParserBase(tagreader_client, collection_backend, parent) {}
|
: ParserBase(tagreader_client, collection_backend, parent) {}
|
||||||
|
|
||||||
SongList CueParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
ParserBase::LoadResult CueParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
||||||
|
|
||||||
SongList ret;
|
SongList ret;
|
||||||
|
|
||||||
@@ -378,8 +378,9 @@ qint64 CueParser::IndexToMarker(const QString &index) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CueParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
void CueParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
||||||
|
|
||||||
|
Q_UNUSED(playlist_name);
|
||||||
Q_UNUSED(songs);
|
Q_UNUSED(songs);
|
||||||
Q_UNUSED(device);
|
Q_UNUSED(device);
|
||||||
Q_UNUSED(dir);
|
Q_UNUSED(dir);
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ class CueParser : public ParserBase {
|
|||||||
|
|
||||||
bool TryMagic(const QByteArray &data) const override;
|
bool TryMagic(const QByteArray &data) const override;
|
||||||
|
|
||||||
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
||||||
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
||||||
|
|
||||||
static QString FindCueFilename(const QString &filename);
|
static QString FindCueFilename(const QString &filename);
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class CollectionBackendInterface;
|
|||||||
M3UParser::M3UParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
M3UParser::M3UParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
||||||
: ParserBase(tagreader_client, collection_backend, parent) {}
|
: ParserBase(tagreader_client, collection_backend, parent) {}
|
||||||
|
|
||||||
SongList M3UParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
ParserBase::LoadResult M3UParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
||||||
|
|
||||||
Q_UNUSED(playlist_path);
|
Q_UNUSED(playlist_path);
|
||||||
|
|
||||||
@@ -125,7 +125,9 @@ bool M3UParser::ParseMetadata(const QString &line, M3UParser::Metadata *metadata
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void M3UParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
void M3UParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
||||||
|
|
||||||
|
Q_UNUSED(playlist_name)
|
||||||
|
|
||||||
device->write("#EXTM3U\n");
|
device->write("#EXTM3U\n");
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ class M3UParser : public ParserBase {
|
|||||||
|
|
||||||
bool TryMagic(const QByteArray &data) const override;
|
bool TryMagic(const QByteArray &data) const override;
|
||||||
|
|
||||||
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
||||||
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class M3UType {
|
enum class M3UType {
|
||||||
|
|||||||
@@ -46,6 +46,13 @@ class ParserBase : public QObject {
|
|||||||
public:
|
public:
|
||||||
explicit ParserBase(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent = nullptr);
|
explicit ParserBase(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
class LoadResult {
|
||||||
|
public:
|
||||||
|
LoadResult(const SongList &_songs = SongList(), const QString &_playlist_name = QString()) : songs(_songs), playlist_name(_playlist_name) {}
|
||||||
|
SongList songs;
|
||||||
|
QString playlist_name;
|
||||||
|
};
|
||||||
|
|
||||||
virtual QString name() const = 0;
|
virtual QString name() const = 0;
|
||||||
virtual QStringList file_extensions() const = 0;
|
virtual QStringList file_extensions() const = 0;
|
||||||
virtual bool load_supported() const = 0;
|
virtual bool load_supported() const = 0;
|
||||||
@@ -59,8 +66,8 @@ class ParserBase : public QObject {
|
|||||||
// This method might not return all the songs found in the playlist.
|
// This method might not return all the songs found in the playlist.
|
||||||
// Any playlist parser may decide to leave out some entries if it finds them incomplete or invalid.
|
// Any playlist parser may decide to leave out some entries if it finds them incomplete or invalid.
|
||||||
// This means that the final resulting SongList should be considered valid (at least from the parser's point of view).
|
// This means that the final resulting SongList should be considered valid (at least from the parser's point of view).
|
||||||
virtual SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const = 0;
|
virtual LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const = 0;
|
||||||
virtual void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const = 0;
|
virtual void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const = 0;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void Error(const QString &error) const;
|
void Error(const QString &error) const;
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ SongList PlaylistParser::LoadFromFile(const QString &filename) const {
|
|||||||
return SongList();
|
return SongList();
|
||||||
}
|
}
|
||||||
|
|
||||||
const SongList songs = parser->Load(&file, filename, fileinfo.absolutePath(), true);
|
const SongList songs = parser->Load(&file, filename, fileinfo.absolutePath(), true).songs;
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
return songs;
|
return songs;
|
||||||
@@ -210,11 +210,11 @@ SongList PlaylistParser::LoadFromDevice(QIODevice *device, const QString &path_h
|
|||||||
return SongList();
|
return SongList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return parser->Load(device, path_hint, dir_hint);
|
return parser->Load(device, path_hint, dir_hint).songs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistParser::Save(const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type) const {
|
void PlaylistParser::Save(const QString &playlist_name, const SongList &songs, const QString &filename, const PlaylistSettings::PathType path_type) const {
|
||||||
|
|
||||||
QFileInfo fileinfo(filename);
|
QFileInfo fileinfo(filename);
|
||||||
QDir dir(fileinfo.path());
|
QDir dir(fileinfo.path());
|
||||||
@@ -248,7 +248,7 @@ void PlaylistParser::Save(const SongList &songs, const QString &filename, const
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parser->Save(songs, &file, dir, path_type);
|
parser->Save(playlist_name, songs, &file, dir, path_type);
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class PlaylistParser : public QObject {
|
|||||||
|
|
||||||
SongList LoadFromFile(const QString &filename) const;
|
SongList LoadFromFile(const QString &filename) const;
|
||||||
SongList LoadFromDevice(QIODevice *device, const QString &path_hint = QString(), const QDir &dir_hint = QDir()) const;
|
SongList LoadFromDevice(QIODevice *device, const QString &path_hint = QString(), const QDir &dir_hint = QDir()) const;
|
||||||
void Save(const SongList &songs, const QString &filename, const PlaylistSettings::PathType) const;
|
void Save(const QString &playlist_name, const SongList &songs, const QString &filename, const PlaylistSettings::PathType) const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void Error(const QString &error) const;
|
void Error(const QString &error) const;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class CollectionBackendInterface;
|
|||||||
PLSParser::PLSParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
PLSParser::PLSParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
||||||
: ParserBase(tagreader_client, collection_backend, parent) {}
|
: ParserBase(tagreader_client, collection_backend, parent) {}
|
||||||
|
|
||||||
SongList PLSParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
ParserBase::LoadResult PLSParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
||||||
|
|
||||||
Q_UNUSED(playlist_path);
|
Q_UNUSED(playlist_path);
|
||||||
|
|
||||||
@@ -83,7 +83,9 @@ SongList PLSParser::Load(QIODevice *device, const QString &playlist_path, const
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PLSParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
void PLSParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
||||||
|
|
||||||
|
Q_UNUSED(playlist_name)
|
||||||
|
|
||||||
QTextStream s(device);
|
QTextStream s(device);
|
||||||
s << "[playlist]" << Qt::endl;
|
s << "[playlist]" << Qt::endl;
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ class PLSParser : public ParserBase {
|
|||||||
|
|
||||||
bool TryMagic(const QByteArray &data) const override;
|
bool TryMagic(const QByteArray &data) const override;
|
||||||
|
|
||||||
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
||||||
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLSPARSER_H
|
#endif // PLSPARSER_H
|
||||||
|
|||||||
@@ -46,21 +46,21 @@ bool WplParser::TryMagic(const QByteArray &data) const {
|
|||||||
return data.contains("<?wpl") || data.contains("<smil>");
|
return data.contains("<?wpl") || data.contains("<smil>");
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList WplParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
ParserBase::LoadResult WplParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
||||||
|
|
||||||
Q_UNUSED(playlist_path);
|
Q_UNUSED(playlist_path);
|
||||||
|
|
||||||
SongList ret;
|
|
||||||
|
|
||||||
QXmlStreamReader reader(device);
|
QXmlStreamReader reader(device);
|
||||||
if (!Utilities::ParseUntilElement(&reader, u"smil"_s) || !Utilities::ParseUntilElement(&reader, u"body"_s)) {
|
if (!Utilities::ParseUntilElement(&reader, u"smil"_s) || !Utilities::ParseUntilElement(&reader, u"body"_s)) {
|
||||||
return ret;
|
return LoadResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SongList songs;
|
||||||
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, u"seq"_s)) {
|
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, u"seq"_s)) {
|
||||||
ParseSeq(dir, &reader, &ret, collection_lookup);
|
ParseSeq(dir, &reader, &songs, collection_lookup);
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
|
return songs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +98,9 @@ void WplParser::ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *so
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WplParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
void WplParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
||||||
|
|
||||||
|
Q_UNUSED(playlist_name)
|
||||||
|
|
||||||
QXmlStreamWriter writer(device);
|
QXmlStreamWriter writer(device);
|
||||||
writer.setAutoFormatting(true);
|
writer.setAutoFormatting(true);
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ class WplParser : public XMLParser {
|
|||||||
|
|
||||||
bool TryMagic(const QByteArray &data) const override;
|
bool TryMagic(const QByteArray &data) const override;
|
||||||
|
|
||||||
SongList Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup = true) const override;
|
LoadResult Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup = true) const override;
|
||||||
void Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *songs, const bool collection_lookup) const;
|
void ParseSeq(const QDir &dir, QXmlStreamReader *reader, SongList *songs, const bool collection_lookup) const;
|
||||||
|
|||||||
@@ -44,17 +44,27 @@ class CollectionBackendInterface;
|
|||||||
XSPFParser::XSPFParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
XSPFParser::XSPFParser(const SharedPtr<TagReaderClient> tagreader_client, const SharedPtr<CollectionBackendInterface> collection_backend, QObject *parent)
|
||||||
: XMLParser(tagreader_client, collection_backend, parent) {}
|
: XMLParser(tagreader_client, collection_backend, parent) {}
|
||||||
|
|
||||||
SongList XSPFParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
ParserBase::LoadResult XSPFParser::Load(QIODevice *device, const QString &playlist_path, const QDir &dir, const bool collection_lookup) const {
|
||||||
|
|
||||||
Q_UNUSED(playlist_path);
|
Q_UNUSED(playlist_path);
|
||||||
|
|
||||||
SongList songs;
|
QString playlist_name;
|
||||||
|
{
|
||||||
QXmlStreamReader reader(device);
|
QXmlStreamReader reader(device);
|
||||||
if (!Utilities::ParseUntilElement(&reader, u"playlist"_s) || !Utilities::ParseUntilElement(&reader, u"trackList"_s)) {
|
if (Utilities::ParseUntilElement(&reader, u"playlist"_s) && Utilities::ParseUntilElement(&reader, u"title"_s)) {
|
||||||
return songs;
|
playlist_name = reader.readElementText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device->seek(0);
|
||||||
|
QXmlStreamReader reader(device);
|
||||||
|
if (!Utilities::ParseUntilElement(&reader, u"playlist"_s)) {
|
||||||
|
return LoadResult();
|
||||||
|
}
|
||||||
|
if (!Utilities::ParseUntilElement(&reader, u"trackList"_s)) {
|
||||||
|
return LoadResult();
|
||||||
|
}
|
||||||
|
SongList songs;
|
||||||
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, u"track"_s)) {
|
while (!reader.atEnd() && Utilities::ParseUntilElement(&reader, u"track"_s)) {
|
||||||
const Song song = ParseTrack(&reader, dir, collection_lookup);
|
const Song song = ParseTrack(&reader, dir, collection_lookup);
|
||||||
if (song.is_valid()) {
|
if (song.is_valid()) {
|
||||||
@@ -62,7 +72,7 @@ SongList XSPFParser::Load(QIODevice *device, const QString &playlist_path, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return songs;
|
return LoadResult(songs, playlist_name);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +150,7 @@ return_song:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
void XSPFParser::Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir, const PlaylistSettings::PathType path_type) const {
|
||||||
|
|
||||||
QXmlStreamWriter writer(device);
|
QXmlStreamWriter writer(device);
|
||||||
writer.setAutoFormatting(true);
|
writer.setAutoFormatting(true);
|
||||||
@@ -150,6 +160,8 @@ void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir,
|
|||||||
writer.writeAttribute("version"_L1, "1"_L1);
|
writer.writeAttribute("version"_L1, "1"_L1);
|
||||||
writer.writeDefaultNamespace("http://xspf.org/ns/0/"_L1);
|
writer.writeDefaultNamespace("http://xspf.org/ns/0/"_L1);
|
||||||
|
|
||||||
|
writer.writeTextElement("title"_L1, playlist_name);
|
||||||
|
|
||||||
Settings s;
|
Settings s;
|
||||||
s.beginGroup(PlaylistSettings::kSettingsGroup);
|
s.beginGroup(PlaylistSettings::kSettingsGroup);
|
||||||
bool write_metadata = s.value(PlaylistSettings::kWriteMetadata, true).toBool();
|
bool write_metadata = s.value(PlaylistSettings::kWriteMetadata, true).toBool();
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ class XSPFParser : public XMLParser {
|
|||||||
|
|
||||||
bool TryMagic(const QByteArray &data) const override;
|
bool TryMagic(const QByteArray &data) const override;
|
||||||
|
|
||||||
SongList Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
LoadResult Load(QIODevice *device, const QString &playlist_path = QLatin1String(""), const QDir &dir = QDir(), const bool collection_lookup = true) const override;
|
||||||
void Save(const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
void Save(const QString &playlist_name, const SongList &songs, QIODevice *device, const QDir &dir = QDir(), const PlaylistSettings::PathType path_type = PlaylistSettings::PathType::Automatic) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Song ParseTrack(QXmlStreamReader *reader, const QDir &dir, const bool collection_lookup) const;
|
Song ParseTrack(QXmlStreamReader *reader, const QDir &dir, const bool collection_lookup) const;
|
||||||
|
|||||||
@@ -77,11 +77,7 @@ void AudioScrobbler::RemoveService(ScrobblerServicePtr service) {
|
|||||||
|
|
||||||
QList<ScrobblerServicePtr> AudioScrobbler::GetAll() {
|
QList<ScrobblerServicePtr> AudioScrobbler::GetAll() {
|
||||||
|
|
||||||
QList<ScrobblerServicePtr> services;
|
return services_.values();
|
||||||
|
|
||||||
services = services_.values();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -237,11 +237,17 @@ ListenBrainzScrobbler::ReplyResult ListenBrainzScrobbler::GetJsonObject(QNetwork
|
|||||||
ReplyResult reply_error_type = ReplyResult::ServerError;
|
ReplyResult reply_error_type = ReplyResult::ServerError;
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isValid()) {
|
||||||
reply_error_type = ReplyResult::Success;
|
const int http_status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
if (http_status_code == 200) {
|
||||||
|
reply_error_type = ReplyResult::Success;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_description = QStringLiteral("Received HTTP code %1").arg(http_status_code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
error_description = QStringLiteral("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
error_description = u"Missing HTTP status code"_s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
@@ -38,6 +37,7 @@
|
|||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/standardpaths.h"
|
||||||
|
|
||||||
#include "scrobblercache.h"
|
#include "scrobblercache.h"
|
||||||
#include "scrobblercacheitem.h"
|
#include "scrobblercacheitem.h"
|
||||||
@@ -49,7 +49,7 @@ using std::make_shared;
|
|||||||
ScrobblerCache::ScrobblerCache(const QString &filename, QObject *parent)
|
ScrobblerCache::ScrobblerCache(const QString &filename, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
timer_flush_(new QTimer(this)),
|
timer_flush_(new QTimer(this)),
|
||||||
filename_(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') + filename),
|
filename_(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + QLatin1Char('/') + filename),
|
||||||
loaded_(false) {
|
loaded_(false) {
|
||||||
|
|
||||||
ReadCache();
|
ReadCache();
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ void ScrobblerSettingsService::ReloadSettings() {
|
|||||||
<< Song::Source::Tidal
|
<< Song::Source::Tidal
|
||||||
<< Song::Source::Subsonic
|
<< Song::Source::Subsonic
|
||||||
<< Song::Source::Qobuz
|
<< Song::Source::Qobuz
|
||||||
|
<< Song::Source::Spotify
|
||||||
<< Song::Source::SomaFM
|
<< Song::Source::SomaFM
|
||||||
<< Song::Source::RadioParadise;
|
<< Song::Source::RadioParadise;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,11 +158,17 @@ ScrobblingAPI20::ReplyResult ScrobblingAPI20::GetJsonObject(QNetworkReply *reply
|
|||||||
ReplyResult reply_error_type = ReplyResult::ServerError;
|
ReplyResult reply_error_type = ReplyResult::ServerError;
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isValid()) {
|
||||||
reply_error_type = ReplyResult::Success;
|
const int http_status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
if (http_status_code == 200) {
|
||||||
|
reply_error_type = ReplyResult::Success;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
error_description = QStringLiteral("Received HTTP code %1").arg(http_status_code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
error_description = QStringLiteral("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
error_description = u"Missing HTTP status code"_s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -242,6 +248,7 @@ void ScrobblingAPI20::Authenticate() {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrobblingAPI20::RedirectArrived() {
|
void ScrobblingAPI20::RedirectArrived() {
|
||||||
@@ -468,6 +475,7 @@ void ScrobblingAPI20::Scrobble(const Song &song) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StartSubmit(true);
|
StartSubmit(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScrobblingAPI20::StartSubmit(const bool initial) {
|
void ScrobblingAPI20::StartSubmit(const bool initial) {
|
||||||
@@ -687,7 +695,7 @@ void ScrobblingAPI20::ScrobbleRequestFinished(QNetworkReply *reply, ScrobblerCac
|
|||||||
qLog(Debug) << name_ << "Scrobble for" << song << "accepted";
|
qLog(Debug) << name_ << "Scrobble for" << song << "accepted";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StartSubmit();
|
StartSubmit();
|
||||||
|
|
||||||
@@ -916,6 +924,7 @@ void ScrobblingAPI20::Error(const QString &error, const QVariant &debug) {
|
|||||||
if (settings_->show_error_dialog()) {
|
if (settings_->show_error_dialog()) {
|
||||||
Q_EMIT ErrorMessage(tr("Scrobbler %1 error: %2").arg(name_, error));
|
Q_EMIT ErrorMessage(tr("Scrobbler %1 error: %2").arg(name_, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ScrobblingAPI20::ErrorString(const ScrobbleErrorCode error) {
|
QString ScrobblingAPI20::ErrorString(const ScrobbleErrorCode error) {
|
||||||
|
|||||||
@@ -37,7 +37,6 @@
|
|||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
#include <QStandardPaths>
|
|
||||||
|
|
||||||
#include "constants/behavioursettings.h"
|
#include "constants/behavioursettings.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
|
|||||||
@@ -24,11 +24,12 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
#include <QItemSelectionModel>
|
#include <QItemSelectionModel>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QStorageInfo>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
@@ -43,7 +44,9 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "constants/filesystemconstants.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
|
#include "core/standardpaths.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "utilities/strutils.h"
|
#include "utilities/strutils.h"
|
||||||
#include "collection/collectionlibrary.h"
|
#include "collection/collectionlibrary.h"
|
||||||
@@ -243,10 +246,16 @@ void CollectionSettingsPage::AddDirectory() {
|
|||||||
Settings s;
|
Settings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
|
|
||||||
QString path = s.value(kLastPath, QStandardPaths::writableLocation(QStandardPaths::MusicLocation)).toString();
|
QString path = s.value(kLastPath, StandardPaths::WritableLocation(StandardPaths::StandardLocation::MusicLocation)).toString();
|
||||||
path = QDir::cleanPath(QFileDialog::getExistingDirectory(this, tr("Add directory..."), path));
|
path = QDir::cleanPath(QFileDialog::getExistingDirectory(this, tr("Add directory..."), path));
|
||||||
|
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty()) {
|
||||||
|
const QByteArray filesystemtype = QStorageInfo(QFileInfo(path).canonicalFilePath()).fileSystemType();
|
||||||
|
if (kRejectedFileSystems.contains(filesystemtype)) {
|
||||||
|
QMessageBox messagebox(QMessageBox::Critical, QObject::tr("Invalid collection directory"), QObject::tr("Can't add directory %1 with special filesystem %2 to collection").arg(path).arg(QString::fromUtf8(filesystemtype)));
|
||||||
|
(void)messagebox.exec();
|
||||||
|
return;
|
||||||
|
}
|
||||||
collectionsettings_directory_model_->AddDirectory(path);
|
collectionsettings_directory_model_->AddDirectory(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ void ScrobblerSettingsPage::Load() {
|
|||||||
ui_->checkbox_source_subsonic->setChecked(scrobbler_->sources().contains(Song::Source::Subsonic));
|
ui_->checkbox_source_subsonic->setChecked(scrobbler_->sources().contains(Song::Source::Subsonic));
|
||||||
ui_->checkbox_source_tidal->setChecked(scrobbler_->sources().contains(Song::Source::Tidal));
|
ui_->checkbox_source_tidal->setChecked(scrobbler_->sources().contains(Song::Source::Tidal));
|
||||||
ui_->checkbox_source_qobuz->setChecked(scrobbler_->sources().contains(Song::Source::Qobuz));
|
ui_->checkbox_source_qobuz->setChecked(scrobbler_->sources().contains(Song::Source::Qobuz));
|
||||||
|
ui_->checkbox_source_spotify->setChecked(scrobbler_->sources().contains(Song::Source::Spotify));
|
||||||
ui_->checkbox_source_stream->setChecked(scrobbler_->sources().contains(Song::Source::Stream));
|
ui_->checkbox_source_stream->setChecked(scrobbler_->sources().contains(Song::Source::Stream));
|
||||||
ui_->checkbox_source_somafm->setChecked(scrobbler_->sources().contains(Song::Source::SomaFM));
|
ui_->checkbox_source_somafm->setChecked(scrobbler_->sources().contains(Song::Source::SomaFM));
|
||||||
ui_->checkbox_source_radioparadise->setChecked(scrobbler_->sources().contains(Song::Source::RadioParadise));
|
ui_->checkbox_source_radioparadise->setChecked(scrobbler_->sources().contains(Song::Source::RadioParadise));
|
||||||
@@ -152,6 +153,7 @@ void ScrobblerSettingsPage::Save() {
|
|||||||
if (ui_->checkbox_source_subsonic->isChecked()) sources << Song::TextForSource(Song::Source::Subsonic);
|
if (ui_->checkbox_source_subsonic->isChecked()) sources << Song::TextForSource(Song::Source::Subsonic);
|
||||||
if (ui_->checkbox_source_tidal->isChecked()) sources << Song::TextForSource(Song::Source::Tidal);
|
if (ui_->checkbox_source_tidal->isChecked()) sources << Song::TextForSource(Song::Source::Tidal);
|
||||||
if (ui_->checkbox_source_qobuz->isChecked()) sources << Song::TextForSource(Song::Source::Qobuz);
|
if (ui_->checkbox_source_qobuz->isChecked()) sources << Song::TextForSource(Song::Source::Qobuz);
|
||||||
|
if (ui_->checkbox_source_spotify->isChecked()) sources << Song::TextForSource(Song::Source::Spotify);
|
||||||
if (ui_->checkbox_source_stream->isChecked()) sources << Song::TextForSource(Song::Source::Stream);
|
if (ui_->checkbox_source_stream->isChecked()) sources << Song::TextForSource(Song::Source::Stream);
|
||||||
if (ui_->checkbox_source_somafm->isChecked()) sources << Song::TextForSource(Song::Source::SomaFM);
|
if (ui_->checkbox_source_somafm->isChecked()) sources << Song::TextForSource(Song::Source::SomaFM);
|
||||||
if (ui_->checkbox_source_radioparadise->isChecked()) sources << Song::TextForSource(Song::Source::RadioParadise);
|
if (ui_->checkbox_source_radioparadise->isChecked()) sources << Song::TextForSource(Song::Source::RadioParadise);
|
||||||
|
|||||||
@@ -39,8 +39,11 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkbox_offline">
|
<widget class="QCheckBox" name="checkbox_offline">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>With this option enabled, scrobbles will be cached to disk but not sent to the server. This option can be enabled in cases where the server or the internet connection is unstable, the scrobbles will be sent when the option is disabled.</string>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Work in offline mode (Only cache scrobbles)</string>
|
<string>Offline mode (Only cache scrobbles)</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -62,6 +65,9 @@
|
|||||||
<layout class="QHBoxLayout" name="layout_submit">
|
<layout class="QHBoxLayout" name="layout_submit">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_submit">
|
<widget class="QLabel" name="label_submit">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>This is the delay between when a song is scrobbled and when scrobbles are submitted to the server. Setting the time to 0 seconds will submit scrobbles immediately.</string>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Submit scrobbles every</string>
|
<string>Submit scrobbles every</string>
|
||||||
</property>
|
</property>
|
||||||
@@ -95,16 +101,6 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_submit_info">
|
|
||||||
<property name="text">
|
|
||||||
<string>(This is the delay between when a song is scrobbled and when scrobbles are submitted to the server. Setting the time to 0 seconds will submit scrobbles immediately).</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkbox_albumartist">
|
<widget class="QCheckBox" name="checkbox_albumartist">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -180,6 +176,27 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkbox_source_somafm">
|
||||||
|
<property name="text">
|
||||||
|
<string>SomaFM</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkbox_source_radioparadise">
|
||||||
|
<property name="text">
|
||||||
|
<string>Radio Paradise</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkbox_source_spotify">
|
||||||
|
<property name="text">
|
||||||
|
<string>Spotify</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QCheckBox" name="checkbox_source_cdda">
|
<widget class="QCheckBox" name="checkbox_source_cdda">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -187,13 +204,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
|
||||||
<widget class="QCheckBox" name="checkbox_source_somafm">
|
|
||||||
<property name="text">
|
|
||||||
<string>SomaFM</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="0">
|
<item row="5" column="0">
|
||||||
<widget class="QCheckBox" name="checkbox_source_stream">
|
<widget class="QCheckBox" name="checkbox_source_stream">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -201,13 +211,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1">
|
|
||||||
<widget class="QCheckBox" name="checkbox_source_radioparadise">
|
|
||||||
<property name="text">
|
|
||||||
<string>Radio Paradise</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="0">
|
<item row="6" column="0">
|
||||||
<widget class="QCheckBox" name="checkbox_source_unknown">
|
<widget class="QCheckBox" name="checkbox_source_unknown">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -426,6 +429,7 @@
|
|||||||
<tabstop>spinbox_submit</tabstop>
|
<tabstop>spinbox_submit</tabstop>
|
||||||
<tabstop>checkbox_albumartist</tabstop>
|
<tabstop>checkbox_albumartist</tabstop>
|
||||||
<tabstop>checkbox_show_error_dialog</tabstop>
|
<tabstop>checkbox_show_error_dialog</tabstop>
|
||||||
|
<tabstop>checkbox_strip_remastered</tabstop>
|
||||||
<tabstop>checkbox_source_collection</tabstop>
|
<tabstop>checkbox_source_collection</tabstop>
|
||||||
<tabstop>checkbox_source_local</tabstop>
|
<tabstop>checkbox_source_local</tabstop>
|
||||||
<tabstop>checkbox_source_device</tabstop>
|
<tabstop>checkbox_source_device</tabstop>
|
||||||
@@ -435,6 +439,7 @@
|
|||||||
<tabstop>checkbox_source_subsonic</tabstop>
|
<tabstop>checkbox_source_subsonic</tabstop>
|
||||||
<tabstop>checkbox_source_tidal</tabstop>
|
<tabstop>checkbox_source_tidal</tabstop>
|
||||||
<tabstop>checkbox_source_qobuz</tabstop>
|
<tabstop>checkbox_source_qobuz</tabstop>
|
||||||
|
<tabstop>checkbox_source_spotify</tabstop>
|
||||||
<tabstop>checkbox_source_somafm</tabstop>
|
<tabstop>checkbox_source_somafm</tabstop>
|
||||||
<tabstop>checkbox_source_radioparadise</tabstop>
|
<tabstop>checkbox_source_radioparadise</tabstop>
|
||||||
<tabstop>checkbox_lastfm_enable</tabstop>
|
<tabstop>checkbox_lastfm_enable</tabstop>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user