Merge branch 'master' of github.com:poldi171254/strawberry

This commit is contained in:
Poldi 2024-03-14 11:17:07 +11:00
commit 992a16769a
123 changed files with 3707 additions and 1852 deletions

View File

@ -1,5 +1,10 @@
name: Build
on: [push, pull_request]
on:
push:
pull_request:
types: [opened, synchronize, reopened]
release:
types: [published]
jobs:
@ -137,32 +142,24 @@ jobs:
CC: gcc-10
CXX: g++-10
run: rpmbuild -ba ../dist/unix/strawberry.spec
- name: Set opensuse subdir
run: echo "opensuse_subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_ENV
- name: Upload artifacts
if: matrix.opensuse_version != 'tumbleweed'
- name: Set subdir
id: set-subdir
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
- name: Upload source
if: matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
uses: actions/upload-artifact@v4
with:
name: opensuse-${{env.opensuse_subdir}}-qt${{matrix.qt_version}}
name: source
path: |
/usr/src/packages/SOURCES/*.xz
- name: Upload rpm
if: matrix.opensuse_version != 'tumbleweed' && matrix.qt_version == '6'
uses: actions/upload-artifact@v4
with:
name: opensuse-${{steps.set-subdir.outputs.subdir}}
path: |
/usr/src/packages/SRPMS/*.rpm
/usr/src/packages/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/source ${{secrets.BUILDS_PATH}}/opensuse/${{env.opensuse_subdir}}
- name: rsync source
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /usr/src/packages/SOURCES/*.xz ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/source/
- name: rsync rpms
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version != 'tumbleweed'
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /usr/src/packages/SRPMS/*.rpm /usr/src/packages/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/opensuse/${{env.opensuse_subdir}}/
build-fedora:
@ -182,7 +179,7 @@ jobs:
run: dnf -y upgrade
- name: Install dependencies
run: >
dnf -y install
dnf -y --skip-broken install
@development-tools
redhat-lsb-core
which
@ -256,18 +253,6 @@ jobs:
path: |
/github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/fedora/${{matrix.fedora_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/fedora/${{matrix.fedora_version}}/
build-openmandriva:
@ -360,24 +345,13 @@ jobs:
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
- name: Upload artifacts
if: matrix.openmandriva_version != 'cooker'
uses: actions/upload-artifact@v4
with:
name: openmandriva-${{matrix.openmandriva_version}}
path: |
/github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/openmandriva/${{matrix.openmandriva_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/openmandriva/${{matrix.openmandriva_version}}/
build-mageia:
@ -468,18 +442,6 @@ jobs:
path: |
/github/home/rpmbuild/SRPMS/*.rpm
/github/home/rpmbuild/RPMS/x86_64/*.rpm
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/mageia/${{matrix.mageia_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/mageia/${{matrix.mageia_version}}/
build-debian:
@ -564,18 +526,6 @@ jobs:
with:
name: debian-${{matrix.debian_version}}
path: "*.deb"
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/debian/${{matrix.debian_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var *.deb ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/debian/${{matrix.debian_version}}/
build-ubuntu:
@ -585,7 +535,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ubuntu_version: [ 'focal', 'jammy', 'lunar', 'mantic' ]
ubuntu_version: [ 'focal', 'jammy', 'mantic', 'noble' ]
container:
image: ubuntu:${{matrix.ubuntu_version}}
steps:
@ -665,28 +615,16 @@ jobs:
path: |
*.deb
*.ddeb
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/ubuntu/${{matrix.ubuntu_version}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var *.deb *.ddeb ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/ubuntu/${{matrix.ubuntu_version}}/
upload-ubuntu-ppa:
name: Upload Ubuntu PPA
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
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')))
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ubuntu_version: [ 'focal', 'jammy', 'lunar', 'mantic' ]
ubuntu_version: [ 'focal', 'jammy', 'mantic', 'noble' ]
container:
image: ubuntu:${{matrix.ubuntu_version}}
steps:
@ -762,15 +700,11 @@ jobs:
gpg_private_key: ${{secrets.UBUNTU_PPA_GPG_PRIVATE_KEY}}
- name: dpkg-buildpackage
run: dpkg-buildpackage -S -d -k573D197B5EA20EDF
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Get release version
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
- name: Upload Unstable PPA
if: env.is_release != '1' && env.release_version == ''
if: github.event_name == 'push'
run: dput ppa:jonaski/strawberry-unstable ../*_source.changes
- name: Upload Stable PPA
if: env.is_release == '1' && env.release_version != ''
if: github.event_name == 'release'
run: dput ppa:jonaski/strawberry ../*_source.changes
@ -869,10 +803,10 @@ jobs:
working-directory: build
run: make deploy
- name: Codesign libsoup
- name: Manually Codesign
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
working-directory: build
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libsoup-3.0.0.dylib,libnghttp2.14.dylib,libpsl.5.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libzstd.1.dylib} strawberry.app
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libsoup-3.0.0.dylib,libnghttp2.14.dylib,libpsl.5.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libzstd.1.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
- name: Deploy check
working-directory: build
@ -888,34 +822,29 @@ jobs:
run: make dmg
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
if: github.repository == 'strawberrymusicplayer/strawberry' && (github.event_name == 'release' || (github.event_name == 'push' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')))
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Get release version
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
- name: Set Upload path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
id: set-upload-path
if: github.repository == 'strawberrymusicplayer/strawberry' && (github.event_name == 'release' || (github.event_name == 'push' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')))
run: |
if [ "${{env.is_release}}" = "1" ] && ! [ "${{env.release_version}}" = "" ]; then
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV
if [ "${{github.event_name}}" = "release" ]; then
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_OUTPUT
else
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_ENV
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_OUTPUT
fi
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{env.upload_path}}
if: steps.set-upload-path.outputs.upload_path != ''
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{steps.set-upload-path.outputs.upload_path}}
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{env.upload_path}}/
if: steps.set-upload-path.outputs.upload_path != ''
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{steps.set-upload-path.outputs.upload_path}}/
build-macos-private:
@ -996,6 +925,10 @@ jobs:
working-directory: build
run: make deploy
- name: Manually Codesign
working-directory: build
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
- name: Deploy check
working-directory: build
run: make deploycheck
@ -1008,22 +941,20 @@ jobs:
working-directory: build
run: make dmg
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Set Upload path
id: set-upload-path
run: |
if [ "${{env.is_release}}" = "1" ]; then
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV
if [ "${{github.event_name}}" = "release" ]; then
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_OUTPUT
else
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_ENV
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_OUTPUT
fi
- name: Create server path
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{env.upload_path}}
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{steps.set-upload-path.outputs.upload_path}}
- name: rsync
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{env.upload_path}}/
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{steps.set-upload-path.outputs.upload_path}}/
build-windows-mingw:
@ -1111,7 +1042,7 @@ jobs:
- name: Copy gstreamer plugins
working-directory: build
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/gstreamer-1.0/{libgstaes.dll,libgstaiff.dll,libgstapetag.dll,libgstapp.dll,libgstasf.dll,libgstasfmux.dll,libgstaudioconvert.dll,libgstaudiofx.dll,libgstaudiomixer.dll,libgstaudioparsers.dll,libgstaudiorate.dll,libgstaudioresample.dll,libgstaudiotestsrc.dll,libgstautodetect.dll,libgstbs2b.dll,libgstcoreelements.dll,libgstdash.dll,libgstdirectsound.dll,libgstequalizer.dll,libgstfaac.dll,libgstfaad.dll,libgstfdkaac.dll,libgstflac.dll,libgstgio.dll,libgstgme.dll,libgsthls.dll,libgsticydemux.dll,libgstid3demux.dll,libgstid3tag.dll,libgstisomp4.dll,libgstlame.dll,libgstlibav.dll,libgstmpg123.dll,libgstmusepack.dll,libgstogg.dll,libgstopenmpt.dll,libgstopus.dll,libgstopusparse.dll,libgstpbtypes.dll,libgstplayback.dll,libgstreplaygain.dll,libgstrtp.dll,libgstrtsp.dll,libgstsoup.dll,libgstspectrum.dll,libgstspeex.dll,libgsttaglib.dll,libgsttcp.dll,libgsttwolame.dll,libgsttypefindfunctions.dll,libgstudp.dll,libgstvolume.dll,libgstvorbis.dll,libgstwasapi.dll,libgstwavenc.dll,libgstwavpack.dll,libgstwavparse.dll,libgstxingmux.dll} ${GITHUB_WORKSPACE}/build/gstreamer-plugins/
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/gstreamer-1.0/*.dll ${GITHUB_WORKSPACE}/build/gstreamer-plugins/
- name: Copy extra binaries
working-directory: build
@ -1196,21 +1127,6 @@ jobs:
name: windows-mingw-${{matrix.arch}}-${{matrix.buildtype}}
path: build/StrawberrySetup*.exe
- name: SSH key setup
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{ secrets.SSH_KEY }}
- name: Create server path
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/windows/mingw
- name: rsync
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/StrawberrySetup*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/windows/mingw/
build-windows-msvc:
name: Build Windows MSVC
@ -1310,7 +1226,7 @@ jobs:
with:
arch: ${{matrix.arch}}
sdk: 10.0.20348.0
vsversion: 17
vsversion: 2022
toolset: 14.3
- name: Checkout
@ -1352,6 +1268,7 @@ jobs:
-DUSE_TAGLIB=ON
-DPKG_CONFIG_EXECUTABLE="${{env.prefix_path_forwardslash}}/bin/pkg-config.exe"
-DICU_ROOT="${{env.prefix_path_forwardslash}}"
-DFFTW3_DIR="${{env.prefix_path_forwardslash}}"
- name: Run Make
shell: cmd
@ -1417,66 +1334,7 @@ jobs:
- name: Copy gstreamer plugins
shell: cmd
working-directory: build
run: |
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaes.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaiff.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstapetag.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstapp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstasf.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstasfmux.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudioconvert.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudiofx.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudiomixer.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudioparsers.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudiorate.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudioresample.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstaudiotestsrc.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstautodetect.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstbs2b.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstcoreelements.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstdash.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstdirectsound.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstequalizer.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstfaac.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstfaad.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstfdkaac.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstflac.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstgio.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstgme.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsthls.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsticydemux.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstid3demux.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstid3tag.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstisomp4.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstlame.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstlibav.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstmpg123.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstmusepack.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstogg.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstopenmpt.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstopus.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstopusparse.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstpbtypes.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstplayback.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstreplaygain.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstrtp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstrtsp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstsoup.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstspectrum.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstspeex.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsttaglib.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsttcp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsttwolame.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gsttypefindfunctions.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstudp.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstvolume.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstvorbis.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwasapi.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwasapi2.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwavenc.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwavpack.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstwavparse.dll .\gstreamer-plugins\
copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\gstxingmux.dll .\gstreamer-plugins\
run: copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\*.dll .\gstreamer-plugins\
- name: Download copydlldeps.sh
shell: bash
@ -1557,38 +1415,9 @@ jobs:
path: build/StrawberrySetup*.exe
rsync-windows-msvc-builds:
name: Rsync Windows MSVC builds
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
runs-on: ubuntu-latest
needs:
- build-windows-msvc
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: builds
pattern: windows-msvc-*
- name: View files
run: find builds
- name: SSH key setup
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{secrets.SSH_KEY}}
- name: Create server path
shell: bash
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/windows/msvc
- name: rsync
shell: bash
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var builds/*/StrawberrySetup-*-msvc-*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/windows/msvc/
upload-release:
name: Upload release
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
upload:
name: Upload
if: github.repository == 'strawberrymusicplayer/strawberry' && (github.event_name == 'release' || (github.event_name == 'push' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')))
runs-on: ubuntu-latest
needs:
- build-opensuse
@ -1598,55 +1427,78 @@ jobs:
- build-windows-mingw
- build-windows-msvc
steps:
- name: Install packages
env:
DEBIAN_FRONTEND: noninteractive
run: sudo apt install -y git rsync hub
run: sudo apt install -y git rsync
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set is release
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
- name: Get release version
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
- name: Show release version
if: env.release_version != ''
run: echo "Release version:" ${{env.release_version}}
- name: Show release assets
if: env.release_version != ''
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: hub release show -f "%as" ${{env.release_version}}
- name: Download artifacts
if: env.release_version != ''
uses: actions/download-artifact@v4
with:
path: artifacts
- name: SSH key setup
if: env.release_version != ''
uses: shimataro/ssh-key-action@v2
with:
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
key: ${{secrets.SSH_KEY}}
- name: Upload
run: |
for i in $(find artifacts -type f); do
if [ "${{github.event_name}}" = "release" ]; then
upload_path="${{secrets.RELEASES_PATH}}/"
else
distro=$(echo "$i" | cut -d '/' -f 2)
if [ "$(echo "$i" | grep '-' || true)" = "" ]; then
upload_path="${{secrets.BUILDS_PATH}}/${distro}/"
else
distro_name=$(echo "${distro}" | cut -d '-' -f 1)
distro_version=$(echo "${distro}" | cut -d '-' -f 2)
upload_path="${{secrets.BUILDS_PATH}}/${distro_name}/${distro_version}/"
fi
fi
ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${upload_path}
rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var $i ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${upload_path}/
done
attach:
name: Attach to release
if: github.event_name == 'release'
runs-on: ubuntu-latest
needs:
- build-opensuse
- build-fedora
- build-debian
- build-ubuntu
- build-windows-mingw
- build-windows-msvc
steps:
- name: Install packages
env:
DEBIAN_FRONTEND: noninteractive
run: sudo apt install -y git jq gh
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Show release assets
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name'
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Add artifacts to release
if: env.is_release == '1' && env.release_version != ''
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
echo "Release version: ${{env.release_version}}"
echo "Release version: ${{github.event.release.tag_name}}"
filenames=()
files=()
a_files=()
for i in $(find artifacts -type f); do
filename=$(basename $i)
if [[ ${filenames[@]} =~ ${filename} ]]; then
@ -1654,22 +1506,16 @@ jobs:
continue
fi
filenames+=("${filename}")
existing_asset=$(hub release show -f "%as" ${{env.release_version}} | tr -d '[:blank:]' | grep ".*/${filename}\$" 2>/dev/null || true)
existing_asset=$(gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name' | tr -d '[:blank:]' | grep ".*/${filename}\$" 2>/dev/null || true)
if [ "${existing_asset}" = "" ]; then
echo "Adding file: ${filename}"
files+=("$i")
a_files+=("-a" "${i}")
files+=("${i}")
else
echo "Release already has file: ${filename}"
fi
done
files_list="${files[@]}"
a_files_list="${a_files[@]}"
if ! [ "${files_list}" = "" ]; then
echo "Uploading files: ${files_list}"
rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var ${files_list} ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.RELEASES_PATH}}/
fi
if ! [ "${a_files_list}" = "" ]; then
echo "Adding files to GitHub release: ${files_list}"
hub release edit -m "Strawberry ${{env.release_version}}" ${a_files_list} "${{env.release_version}}"
echo "Adding files to GitHub release"
gh release upload "${{github.event.release.tag_name}}" ${files_list}
fi

View File

@ -62,7 +62,7 @@ enum ENUM_ORDERING {
//
//
// Ansi structures and functions follow
// Ansi structures and functions follow
//
//
@ -409,7 +409,7 @@ int _getopt_long_only_r_a(int argc, char *const *argv, const char *options, cons
//
//
// Unicode Structures and Functions
// Unicode Structures and Functions
//
//
@ -750,4 +750,4 @@ int _getopt_long_r_w(int argc, wchar_t *const *argv, const wchar_t *options, con
int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d);
int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) {
return _getopt_internal_r_w(argc, argv, options, long_options, opt_index, 1, d, 0);
}
}

View File

@ -266,25 +266,19 @@ if(X11_FOUND)
endif(X11_FOUND)
option(USE_TAGLIB "Build with TagLib" OFF)
option(USE_TAGLIB "Build with TagLib" ON)
option(USE_TAGPARSER "Build with TagParser" OFF)
if(NOT USE_TAGLIB AND NOT USE_TAGPARSER)
set(USE_TAGLIB ON)
endif()
# TAGLIB
if(USE_TAGLIB)
pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1)
if(TAGLIB_FOUND)
find_path(HAVE_TAGLIB_DSFFILE_H taglib/dsffile.h)
find_path(HAVE_TAGLIB_DSDIFFFILE_H taglib/dsdifffile.h)
if(HAVE_TAGLIB_DSFFILE_H)
set(HAVE_TAGLIB_DSFFILE ON)
endif(HAVE_TAGLIB_DSFFILE_H)
if(HAVE_TAGLIB_DSDIFFFILE_H)
set(HAVE_TAGLIB_DSDIFFFILE ON)
endif(HAVE_TAGLIB_DSDIFFFILE_H)
find_package(TagLib 2.0)
if(TARGET TagLib::TagLib)
set(TAGLIB_FOUND ON)
set(TAGLIB_LIBRARIES TagLib::TagLib)
set(HAVE_TAGLIB_DSFFILE ON)
set(HAVE_TAGLIB_DSDIFFFILE ON)
else()
pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1)
endif()
endif()

View File

@ -48,24 +48,27 @@ small as possible.
### Commit messages
The first line should start with "Class:", which referer to the class
that is changed. Don't use a trailing period after the first line.
If this change affects more than one class, omit the class and write a
The first line should start with the name of the class that is changed
followed by a colon then a short explanation of the commit.
Don't use a trailing period after the first line.
If this change affects more than one class, omit the class name and write a
more general message.
You only need to include a main description (body) for larger changes
where the one line is not enough to describe everything.
The main description starts after two newlines, it is normal prose and
should use normal punctuation and capital letters where appropriate.
It should explain exactly what's changed, why it's changed,
and what bugs were fixed.
An example of the expected format for git commit messages is as follows:
```
class: Short explanation of the commit
StretchHeaderView: Set default section size
Longer explanation explaining exactly what's changed, why it's changed,
and what bugs were fixed.
As of Qt 6.6.1, style changes are resetting the column sizes. To prevent this, we set a default section size.
Fixes #1234
Fixes #1328
```

View File

@ -2,6 +2,36 @@ Strawberry Music Player
=======================
ChangeLog
Unreleased:
Bugfixes:
* Fixed crash when pressing CTRL + C (#1359).
* Pass on scroll events to page in settings to avoid changing settings when scrolling with mouse (#1380).
* (macOS/Windows) Fixed dash and hls streaming, plugins were missing.
Enhancements:
* Improve error messages when connecting and copying to devices.
* Allow enter to be used with multiselection to add songs to playlist (#1360)
* Add song progress to taskbar using D-Bus.
* Use API to receive Radio Paradise channels.
* (Unix) Add experimental GStreamer pipewire support.
* (Windows) Add experimental exclusive mode for WASAPI.
* (Windows MSVC) Add ASIO support.
* (Windows MSVC) Add back WASAPI2.
Version 1.0.23 (2024.01.11):
Bugfixes:
* Fixed possible duplication of song entries after organizing (#1341).
* Fixed possible crash when connecting devices (#1313).
* Fixed playlist sorting of original year (#1349).
* (macOS) Fixed crash when adding collection directory (QTBUG-120469) (#1350).
Enhancements:
* Treat all stream errors as non-fatal (#1347).
* Require KDSingleApplication 1.1.0.
* Fix logging of restored unavailable songs.
Version 1.0.22 (2023.12.09):
Bugfixes:

View File

@ -29,12 +29,12 @@ Resources:
* We do not take feature requests from users on GitHub. Any issues related to feature requests will be closed. This does not necessarily mean that we won't add new features, but we don't have time to take feature requests or answer questions about new features from users. It is still possible to suggest or discuss new features on the forum (https://forum.strawberrymusicplayer.org/).
* We do not maintain the Flatpak package. Do not report issues related to Flatpak unless the issue can be reproduced with a native package, use Flatpak support instead https://flatpak.org/about/
### :moneybag: Sponsoring
### :moneybag: Sponsoring
The program is free software, released under GPL. If you like this program and can make use of it, consider sponsoring or donating to help fund the project.
There are currently 3 options for sponsoring:
There are currently 4 options for sponsoring:
1. [GitHub Sponsors](https://github.com/sponsors/jonaski)
1. [GitHub](https://github.com/sponsors/jonaski)
2. [Patreon](https://www.patreon.com/jonaskvinge)
3. [Ko-fi](https://ko-fi.com/jonaskvinge)
4. [PayPal](https://paypal.me/jonaskvinge)
@ -54,7 +54,7 @@ Funding developers is a way to contribute to open source projects you appreciate
* Edit tags on audio files
* Fetch tags from MusicBrainz
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
* Song lyrics from [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/), [lololyrics.com](https://www.lololyrics.com/), [songlyrics.com](https://www.songlyrics.com/), [azlyrics.com](https://www.azlyrics.com/), [elyrics.net](https://www.elyrics.net/) and [lyricsmode.com](https://www.lyricsmode.com/)
* Song lyrics from [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/), [lololyrics.com](https://www.lololyrics.com/), [songlyrics.com](https://www.songlyrics.com/), [azlyrics.com](https://www.azlyrics.com/) and [elyrics.net](https://www.elyrics.net/)
* Support for multiple backends
* Audio analyzer
* Audio equalizer
@ -65,7 +65,7 @@ Funding developers is a way to contribute to open source projects you appreciate
It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
**macOS releases are currently limited to sponsors. This is because macOS releases require a developer account, Apple hardware and maintaining all libraries strawberry depends on. If you are sponsoring strawberry, e-mail support@strawberrymusicplayer.org for access to downloads.**
**macOS releases are currently limited to sponsors. This is because Strawberry mainly has one contributor/developer and supporting macOS requires Apple hardware, building libraries Strawberry depends and a Apple developer account for signing releases. If you are sponsoring strawberry through Patreon, releases are available directly on Patreon, if you are sponsoring through GitHub, Ko-fi or Paypal, please e-mail support@strawberrymusicplayer.org for access to downloads.**
### :heavy_exclamation_mark: Requirements
@ -97,7 +97,7 @@ Optional dependencies:
You should also install the gstreamer plugins base and good, and optionally bad, ugly and libav to support all audio formats.
### :wrench: Compiling from source
### :wrench: Compiling from source
### Get the code:
@ -118,6 +118,6 @@ Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/strawberrymusicplayer/strawberry-msvc
### :penguin: Packaging status
### :penguin: Packaging status
[![Packaging status](https://repology.org/badge/vertical-allrepos/strawberry.svg?exclude_unsupported=1)](https://repology.org/metapackage/strawberry/versions)

View File

@ -1,21 +1,33 @@
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
if(NOT GETTEXT_XGETTEXT_EXECUTABLE)
message(FATAL_ERROR "Could not find xgettext executable")
endif(NOT GETTEXT_XGETTEXT_EXECUTABLE)
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext REQUIRED)
find_program(CAT_EXECUTABLE cat REQUIRED)
set (XGETTEXT_OPTIONS
--qt
--keyword=tr:1,2c
--keyword=tr --flag=tr:1:pass-c-format --flag=tr:1:pass-qt-format
--keyword=trUtf8 --flag=tr:1:pass-c-format --flag=tr:1:pass-qt-format
--keyword=translate:2,3c
--keyword=translate:2 --flag=translate:2:pass-c-format --flag=translate:2:pass-qt-format
--keyword=QT_TR_NOOP --flag=QT_TR_NOOP:1:pass-c-format --flag=QT_TR_NOOP:1:pass-qt-format
--keyword=QT_TRANSLATE_NOOP:2 --flag=QT_TRANSLATE_NOOP:2:pass-c-format --flag=QT_TRANSLATE_NOOP:2:pass-qt-format
--keyword=_ --flag=_:1:pass-c-format --flag=_:1:pass-qt-format
--keyword=N_ --flag=N_:1:pass-c-format --flag=N_:1:pass-qt-format
--from-code=utf-8
)
list(APPEND XGETTEXT_OPTIONS
--qt
--keyword=tr:1,2c
--keyword=tr
--flag=tr:1:pass-c-format
--flag=tr:1:pass-qt-format
--keyword=trUtf8
--flag=tr:1:pass-c-format
--flag=tr:1:pass-qt-format
--keyword=translate:2,3c
--keyword=translate:2
--flag=translate:2:pass-c-format
--flag=translate:2:pass-qt-format
--keyword=QT_TR_NOOP
--flag=QT_TR_NOOP:1:pass-c-format
--flag=QT_TR_NOOP:1:pass-qt-format
--keyword=QT_TRANSLATE_NOOP:2
--flag=QT_TRANSLATE_NOOP:2:pass-c-format
--flag=QT_TRANSLATE_NOOP:2:pass-qt-format
--keyword=_
--flag=_:1:pass-c-format
--flag=_:1:pass-qt-format
--keyword=N_
--flag=N_:1:pass-c-format
--flag=N_:1:pass-qt-format
--from-code=utf-8
)
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/translations)
@ -32,7 +44,7 @@ macro(add_pot outfiles header pot)
add_custom_command(
OUTPUT ${pot}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTIONS} -s -C --omit-header --output=${CMAKE_CURRENT_BINARY_DIR}/pot.temp ${add_pot_sources}
COMMAND xgettext ${XGETTEXT_OPTIONS} -s -C --omit-header --output="${CMAKE_CURRENT_BINARY_DIR}/pot.temp" ${add_pot_sources}
COMMAND cat ${header} ${CMAKE_CURRENT_BINARY_DIR}/pot.temp > ${pot}
DEPENDS ${add_pot_sources} ${header}
)

View File

@ -1,6 +1,6 @@
set(STRAWBERRY_VERSION_MAJOR 1)
set(STRAWBERRY_VERSION_MINOR 0)
set(STRAWBERRY_VERSION_PATCH 22)
set(STRAWBERRY_VERSION_PATCH 23)
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION ON)

View File

@ -35,32 +35,32 @@
background-color: %palette-base;
}
QToolButton {
QToolButton[accessibleName="MenuPopupToolButton"] {
border: 2px solid transparent;
border-radius: 3px;
padding: 1px;
}
QToolButton:hover {
QToolButton:hover[accessibleName="MenuPopupToolButton"] {
border: 2px solid %palette-highlight;
background-color: %palette-highlight-lighter;
}
QToolButton:pressed {
QToolButton:pressed[accessibleName="MenuPopupToolButton"] {
border: 2px solid %palette-highlight-darker;
background-color: %palette-highlight-lighter;
}
QToolButton[popupMode="MenuButtonPopup"], QToolButton:hover[popupMode="MenuButtonPopup"], QToolButton:pressed[popupMode="MenuButtonPopup"] {
QToolButton[popupMode="MenuButtonPopup"][accessibleName="MenuPopupToolButton"], QToolButton:hover[popupMode="MenuButtonPopup"][accessibleName="MenuPopupToolButton"], QToolButton:pressed[popupMode="MenuButtonPopup"][accessibleName="MenuPopupToolButton"] {
padding-right: 16px;
}
/* For backwards compatibility with Qt 5 as it does not support property name */
QToolButton[popupMode="1"], QToolButton:hover[popupMode="1"], QToolButton:pressed[popupMode="1"] {
QToolButton[popupMode="1"][accessibleName="MenuPopupToolButton"], QToolButton:hover[popupMode="1"][accessibleName="MenuPopupToolButton"], QToolButton:pressed[popupMode="1"][accessibleName="MenuPopupToolButton"] {
padding-right: 16px;
}
QToolButton::menu-button {
QToolButton::menu-button[accessibleName="MenuPopupToolButton"] {
width: 16px;
border: none;
}

2
debian/control.in vendored
View File

@ -53,7 +53,7 @@ Description: music player and music collection organizer
- Edit tags on audio files
- Automatically retrieve tags from MusicBrainz
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net and lyricsmode.com
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net
- Audio analyzer
- Audio equalizer
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic

View File

@ -62,6 +62,7 @@ cp -v -f "${GST_PLUGIN_SCANNER}" "${bundledir}/Contents/PlugIns/" || exit 1
install_name_tool -add_rpath "@loader_path/../Frameworks" "${bundledir}/Contents/PlugIns/$(basename ${GST_PLUGIN_SCANNER})" || exit 1
gst_plugins="
libgstadaptivedemux2
libgstaes
libgstaiff
libgstapetag
@ -70,16 +71,14 @@ libgstasf
libgstasfmux
libgstaudioconvert
libgstaudiofx
libgstaudiomixer
libgstaudioparsers
libgstaudiorate
libgstaudioresample
libgstaudiotestsrc
libgstautodetect
libgstbs2b
libgstcdio
libgstcoreelements
libgstdash
libgstdsd
libgstequalizer
libgstfaac
libgstfaad
@ -92,6 +91,10 @@ libgstid3demux
libgstid3tag
libgstisomp4
libgstlame
libgstmpegpsdemux
libgstmpegpsmux
libgstmpegtsdemux
libgstmpegtsmux
libgstlibav
libgstmpg123
libgstmusepack

View File

@ -29,7 +29,7 @@
<li>Edit tags on audio files</li>
<li>Automatically retrieve tags from MusicBrainz</li>
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
<li>Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net and lyricsmode.com</li>
<li>Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net</li>
<li>Support for multiple backends</li>
<li>Audio analyzer and equalizer</li>
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
@ -50,6 +50,7 @@
</screenshots>
<update_contact>eclipseo@fedoraproject.org</update_contact>
<releases>
<release version="1.0.23" date="2024-01-11"/>
<release version="1.0.22" date="2023-12-09"/>
<release version="1.0.21" date="2023-10-21"/>
<release version="1.0.20" date="2023-09-24"/>

View File

@ -99,7 +99,7 @@ Features:
- Edit tags on audio files
- Automatically retrieve tags from MusicBrainz
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net and lyricsmode.com
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net
- Support for multiple backends
- Audio analyzer
- Audio equalizer

View File

@ -282,8 +282,10 @@ Section "Strawberry" Strawberry
File "libgstaudio-1.0-0.dll"
File "libgstbadaudio-1.0-0.dll"
File "libgstbase-1.0-0.dll"
File "libgstcodecparsers-1.0-0.dll"
File "libgstfft-1.0-0.dll"
File "libgstisoff-1.0-0.dll"
File "libgstmpegts-1.0-0.dll"
File "libgstnet-1.0-0.dll"
File "libgstpbutils-1.0-0.dll"
File "libgstreamer-1.0-0.dll"
@ -422,8 +424,10 @@ Section "Strawberry" Strawberry
File "gstaudio-1.0-0.dll"
File "gstbadaudio-1.0-0.dll"
File "gstbase-1.0-0.dll"
File "gstcodecparsers-1.0-0.dll"
File "gstfft-1.0-0.dll"
File "gstisoff-1.0-0.dll"
File "gstmpegts-1.0-0.dll"
File "gstnet-1.0-0.dll"
File "gstpbutils-1.0-0.dll"
File "gstreamer-1.0-0.dll"
@ -627,6 +631,7 @@ Section "Gstreamer plugins" gstreamer-plugins
SetOutPath "$INSTDIR\gstreamer-plugins"
!ifdef mingw
File "/oname=libgstadaptivedemux2.dll" "gstreamer-plugins\libgstadaptivedemux2.dll"
File "/oname=libgstaes.dll" "gstreamer-plugins\libgstaes.dll"
File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll"
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
@ -635,16 +640,14 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll"
File "/oname=libgstaudioconvert.dll" "gstreamer-plugins\libgstaudioconvert.dll"
File "/oname=libgstaudiofx.dll" "gstreamer-plugins\libgstaudiofx.dll"
File "/oname=libgstaudiomixer.dll" "gstreamer-plugins\libgstaudiomixer.dll"
File "/oname=libgstaudioparsers.dll" "gstreamer-plugins\libgstaudioparsers.dll"
File "/oname=libgstaudiorate.dll" "gstreamer-plugins\libgstaudiorate.dll"
File "/oname=libgstaudioresample.dll" "gstreamer-plugins\libgstaudioresample.dll"
File "/oname=libgstaudiotestsrc.dll" "gstreamer-plugins\libgstaudiotestsrc.dll"
File "/oname=libgstautodetect.dll" "gstreamer-plugins\libgstautodetect.dll"
File "/oname=libgstbs2b.dll" "gstreamer-plugins\libgstbs2b.dll"
File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll"
File "/oname=libgstdash.dll" "gstreamer-plugins\libgstdash.dll"
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
File "/oname=libgstdsd.dll" "gstreamer-plugins\libgstdsd.dll"
File "/oname=libgstequalizer.dll" "gstreamer-plugins\libgstequalizer.dll"
File "/oname=libgstfaac.dll" "gstreamer-plugins\libgstfaac.dll"
File "/oname=libgstfaad.dll" "gstreamer-plugins\libgstfaad.dll"
@ -659,6 +662,10 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstisomp4.dll" "gstreamer-plugins\libgstisomp4.dll"
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
File "/oname=libgstlibav.dll" "gstreamer-plugins\libgstlibav.dll"
File "/oname=libgstmpegpsdemux.dll" "gstreamer-plugins\libgstmpegpsdemux.dll"
File "/oname=libgstmpegpsmux.dll" "gstreamer-plugins\libgstmpegpsmux.dll"
File "/oname=libgstmpegtsdemux.dll" "gstreamer-plugins\libgstmpegtsdemux.dll"
File "/oname=libgstmpegtsmux.dll" "gstreamer-plugins\libgstmpegtsmux.dll"
File "/oname=libgstmpg123.dll" "gstreamer-plugins\libgstmpg123.dll"
File "/oname=libgstmusepack.dll" "gstreamer-plugins\libgstmusepack.dll"
File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll"
@ -681,6 +688,7 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll"
File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll"
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
File "/oname=libgstwaveform.dll" "gstreamer-plugins\libgstwaveform.dll"
File "/oname=libgstwavenc.dll" "gstreamer-plugins\libgstwavenc.dll"
File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
@ -688,24 +696,24 @@ Section "Gstreamer plugins" gstreamer-plugins
!endif ; MinGW
!ifdef msvc
File "/oname=gstadaptivedemux2.dll" "gstreamer-plugins\gstadaptivedemux2.dll"
File "/oname=gstaes.dll" "gstreamer-plugins\gstaes.dll"
File "/oname=gstaiff.dll" "gstreamer-plugins\gstaiff.dll"
File "/oname=gstapetag.dll" "gstreamer-plugins\gstapetag.dll"
File "/oname=gstapp.dll" "gstreamer-plugins\gstapp.dll"
File "/oname=gstasf.dll" "gstreamer-plugins\gstasf.dll"
File "/oname=gstasfmux.dll" "gstreamer-plugins\gstasfmux.dll"
File "/oname=gstasio.dll" "gstreamer-plugins\gstasio.dll"
File "/oname=gstaudioconvert.dll" "gstreamer-plugins\gstaudioconvert.dll"
File "/oname=gstaudiofx.dll" "gstreamer-plugins\gstaudiofx.dll"
File "/oname=gstaudiomixer.dll" "gstreamer-plugins\gstaudiomixer.dll"
File "/oname=gstaudioparsers.dll" "gstreamer-plugins\gstaudioparsers.dll"
File "/oname=gstaudiorate.dll" "gstreamer-plugins\gstaudiorate.dll"
File "/oname=gstaudioresample.dll" "gstreamer-plugins\gstaudioresample.dll"
File "/oname=gstaudiotestsrc.dll" "gstreamer-plugins\gstaudiotestsrc.dll"
File "/oname=gstautodetect.dll" "gstreamer-plugins\gstautodetect.dll"
File "/oname=gstbs2b.dll" "gstreamer-plugins\gstbs2b.dll"
File "/oname=gstcoreelements.dll" "gstreamer-plugins\gstcoreelements.dll"
File "/oname=gstdash.dll" "gstreamer-plugins\gstdash.dll"
File "/oname=gstdirectsound.dll" "gstreamer-plugins\gstdirectsound.dll"
File "/oname=gstdsd.dll" "gstreamer-plugins\gstdsd.dll"
File "/oname=gstequalizer.dll" "gstreamer-plugins\gstequalizer.dll"
File "/oname=gstfaac.dll" "gstreamer-plugins\gstfaac.dll"
File "/oname=gstfaad.dll" "gstreamer-plugins\gstfaad.dll"
@ -720,6 +728,10 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=gstisomp4.dll" "gstreamer-plugins\gstisomp4.dll"
File "/oname=gstlame.dll" "gstreamer-plugins\gstlame.dll"
File "/oname=gstlibav.dll" "gstreamer-plugins\gstlibav.dll"
File "/oname=gstmpegpsdemux.dll" "gstreamer-plugins\gstmpegpsdemux.dll"
File "/oname=gstmpegpsmux.dll" "gstreamer-plugins\gstmpegpsmux.dll"
File "/oname=gstmpegtsdemux.dll" "gstreamer-plugins\gstmpegtsdemux.dll"
File "/oname=gstmpegtsmux.dll" "gstreamer-plugins\gstmpegtsmux.dll"
File "/oname=gstmpg123.dll" "gstreamer-plugins\gstmpg123.dll"
File "/oname=gstmusepack.dll" "gstreamer-plugins\gstmusepack.dll"
File "/oname=gstogg.dll" "gstreamer-plugins\gstogg.dll"
@ -742,8 +754,8 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.dll"
File "/oname=gstwasapi.dll" "gstreamer-plugins\gstwasapi.dll"
; Disable wasapi2 until issue (https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2870) is fixed.
;File "/oname=gstwasapi2.dll" "gstreamer-plugins\gstwasapi2.dll"
File "/oname=gstwasapi2.dll" "gstreamer-plugins\gstwasapi2.dll"
File "/oname=gstwaveform.dll" "gstreamer-plugins\gstwaveform.dll"
File "/oname=gstwavenc.dll" "gstreamer-plugins\gstwavenc.dll"
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
@ -839,8 +851,10 @@ Section "Uninstall"
Delete "$INSTDIR\libgstaudio-1.0-0.dll"
Delete "$INSTDIR\libgstbadaudio-1.0-0.dll"
Delete "$INSTDIR\libgstbase-1.0-0.dll"
Delete "$INSTDIR\libgstcodecparsers-1.0-0.dll"
Delete "$INSTDIR\libgstfft-1.0-0.dll"
Delete "$INSTDIR\libgstisoff-1.0-0.dll"
Delete "$INSTDIR\libgstmpegts-1.0-0.dll"
Delete "$INSTDIR\libgstnet-1.0-0.dll"
Delete "$INSTDIR\libgstpbutils-1.0-0.dll"
Delete "$INSTDIR\libgstreamer-1.0-0.dll"
@ -979,8 +993,10 @@ Section "Uninstall"
Delete "$INSTDIR\gstaudio-1.0-0.dll"
Delete "$INSTDIR\gstbadaudio-1.0-0.dll"
Delete "$INSTDIR\gstbase-1.0-0.dll"
Delete "$INSTDIR\gstcodecparsers-1.0-0.dll"
Delete "$INSTDIR\gstfft-1.0-0.dll"
Delete "$INSTDIR\gstisoff-1.0-0.dll"
Delete "$INSTDIR\gstmpegts-1.0-0.dll"
Delete "$INSTDIR\gstnet-1.0-0.dll"
Delete "$INSTDIR\gstpbutils-1.0-0.dll"
Delete "$INSTDIR\gstreamer-1.0-0.dll"
@ -1116,6 +1132,7 @@ Section "Uninstall"
; MinGW GStreamer plugins
!ifdef mingw
Delete "$INSTDIR\gstreamer-plugins\libgstadaptivedemux2.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaes.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaiff.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstapetag.dll"
@ -1124,16 +1141,14 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstasfmux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioconvert.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiofx.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiomixer.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioparsers.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiorate.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioresample.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiotestsrc.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstautodetect.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstbs2b.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdash.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdsd.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstequalizer.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstfaac.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstfaad.dll"
@ -1148,6 +1163,10 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstisomp4.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstlibav.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpegpsdemux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpegpsmux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpegtsdemux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpegtsmux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmpg123.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstmusepack.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstogg.dll"
@ -1170,6 +1189,7 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwaveform.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavenc.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll"
@ -1179,24 +1199,24 @@ Section "Uninstall"
; MSVC GStreamer plugins
!ifdef msvc
Delete "$INSTDIR\gstreamer-plugins\gstadaptivedemux2.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaes.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaiff.dll"
Delete "$INSTDIR\gstreamer-plugins\gstapetag.dll"
Delete "$INSTDIR\gstreamer-plugins\gstapp.dll"
Delete "$INSTDIR\gstreamer-plugins\gstasf.dll"
Delete "$INSTDIR\gstreamer-plugins\gstasfmux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstasio.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudioconvert.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudiofx.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudiomixer.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudioparsers.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudiorate.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudioresample.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaudiotestsrc.dll"
Delete "$INSTDIR\gstreamer-plugins\gstautodetect.dll"
Delete "$INSTDIR\gstreamer-plugins\gstbs2b.dll"
Delete "$INSTDIR\gstreamer-plugins\gstcoreelements.dll"
Delete "$INSTDIR\gstreamer-plugins\gstdash.dll"
Delete "$INSTDIR\gstreamer-plugins\gstdirectsound.dll"
Delete "$INSTDIR\gstreamer-plugins\gstdsd.dll"
Delete "$INSTDIR\gstreamer-plugins\gstequalizer.dll"
Delete "$INSTDIR\gstreamer-plugins\gstfaac.dll"
Delete "$INSTDIR\gstreamer-plugins\gstfaad.dll"
@ -1211,6 +1231,10 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\gstisomp4.dll"
Delete "$INSTDIR\gstreamer-plugins\gstlame.dll"
Delete "$INSTDIR\gstreamer-plugins\gstlibav.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpegpsdemux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpegpsmux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpegtsdemux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpegtsmux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmpg123.dll"
Delete "$INSTDIR\gstreamer-plugins\gstmusepack.dll"
Delete "$INSTDIR\gstreamer-plugins\gstogg.dll"
@ -1234,6 +1258,7 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwasapi2.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwaveform.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"

View File

@ -188,7 +188,7 @@ set(SOURCES
lyrics/songlyricscomlyricsprovider.cpp
lyrics/azlyricscomlyricsprovider.cpp
lyrics/elyricsnetlyricsprovider.cpp
lyrics/lyricsmodecomlyricsprovider.cpp
lyrics/letraslyricsprovider.cpp
providers/musixmatchprovider.cpp
@ -439,7 +439,7 @@ set(HEADERS
lyrics/songlyricscomlyricsprovider.h
lyrics/azlyricscomlyricsprovider.h
lyrics/elyricsnetlyricsprovider.h
lyrics/lyricsmodecomlyricsprovider.h
lyrics/letraslyricsprovider.h
settings/settingsdialog.h
settings/settingspage.h
@ -867,7 +867,7 @@ optional_source(WIN32
HEADERS
core/windows7thumbbar.h
)
optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp)
optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp engine/asiodevicefinder.cpp)
optional_source(HAVE_SUBSONIC
SOURCES

View File

@ -38,6 +38,9 @@
</item>
<item>
<widget class="QToolButton" name="options">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize">
<size>
<width>16</width>

View File

@ -86,6 +86,7 @@ CollectionModel::CollectionModel(SharedPtr<CollectionBackend> backend, Applicati
app_(app),
dir_model_(new CollectionDirectoryModel(backend, this)),
show_various_artists_(true),
sort_skips_articles_(true),
total_song_count_(0),
total_artist_count_(0),
total_album_count_(0),
@ -163,6 +164,15 @@ void CollectionModel::set_show_dividers(const bool show_dividers) {
}
void CollectionModel::set_sort_skips_articles(const bool sort_skips_articles) {
if (sort_skips_articles != sort_skips_articles_) {
sort_skips_articles_ = sort_skips_articles;
Reset();
}
}
void CollectionModel::ReloadSettings() {
QSettings s;
@ -1242,14 +1252,14 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->metadata.set_albumartist(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.albumartist());
item->sort_text = SortTextForArtist(item->metadata.albumartist());
item->sort_text = SortTextForArtist(item->metadata.albumartist(), sort_skips_articles_);
break;
}
case GroupBy::Artist:{
item->metadata.set_artist(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.artist());
item->sort_text = SortTextForArtist(item->metadata.artist());
item->sort_text = SortTextForArtist(item->metadata.artist(), sort_skips_articles_);
break;
}
case GroupBy::Album:{
@ -1258,7 +1268,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->metadata.set_grouping(row.value(2).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.album());
item->sort_text = SortTextForArtist(item->metadata.album());
item->sort_text = SortTextForArtist(item->metadata.album(), sort_skips_articles_);
break;
}
case GroupBy::AlbumDisc:{
@ -1343,28 +1353,28 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
item->metadata.set_genre(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.genre());
item->sort_text = SortTextForArtist(item->metadata.genre());
item->sort_text = SortTextForArtist(item->metadata.genre(), sort_skips_articles_);
break;
}
case GroupBy::Composer:{
item->metadata.set_composer(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.composer());
item->sort_text = SortTextForArtist(item->metadata.composer());
item->sort_text = SortTextForArtist(item->metadata.composer(), sort_skips_articles_);
break;
}
case GroupBy::Performer:{
item->metadata.set_performer(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.performer());
item->sort_text = SortTextForArtist(item->metadata.performer());
item->sort_text = SortTextForArtist(item->metadata.performer(), sort_skips_articles_);
break;
}
case GroupBy::Grouping:{
item->metadata.set_grouping(row.value(0).toString());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.grouping());
item->sort_text = SortTextForArtist(item->metadata.grouping());
item->sort_text = SortTextForArtist(item->metadata.grouping(), sort_skips_articles_);
break;
}
case GroupBy::FileType:{
@ -1441,14 +1451,14 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->metadata.set_albumartist(s.effective_albumartist());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.effective_albumartist());
item->sort_text = SortTextForArtist(s.effective_albumartist());
item->sort_text = SortTextForArtist(s.effective_albumartist(), sort_skips_articles_);
break;
}
case GroupBy::Artist:{
item->metadata.set_artist(s.artist());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.artist());
item->sort_text = SortTextForArtist(s.artist());
item->sort_text = SortTextForArtist(s.artist(), sort_skips_articles_);
break;
}
case GroupBy::Album:{
@ -1457,7 +1467,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.album());
item->sort_text = SortTextForArtist(s.album());
item->sort_text = SortTextForArtist(s.album(), sort_skips_articles_);
break;
}
case GroupBy::AlbumDisc:{
@ -1542,28 +1552,28 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
item->metadata.set_genre(s.genre());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.genre());
item->sort_text = SortTextForArtist(s.genre());
item->sort_text = SortTextForArtist(s.genre(), sort_skips_articles_);
break;
}
case GroupBy::Composer:{
item->metadata.set_composer(s.composer());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.composer());
item->sort_text = SortTextForArtist(s.composer());
item->sort_text = SortTextForArtist(s.composer(), sort_skips_articles_);
break;
}
case GroupBy::Performer:{
item->metadata.set_performer(s.performer());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.performer());
item->sort_text = SortTextForArtist(s.performer());
item->sort_text = SortTextForArtist(s.performer(), sort_skips_articles_);
break;
}
case GroupBy::Grouping:{
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.grouping());
item->sort_text = SortTextForArtist(s.grouping());
item->sort_text = SortTextForArtist(s.grouping(), sort_skips_articles_);
break;
}
case GroupBy::FileType:{
@ -1681,7 +1691,7 @@ QString CollectionModel::PrettyYearAlbum(const int year, const QString &album) {
QString CollectionModel::PrettyAlbumDisc(const QString &album, const int disc) {
if (disc <= 0 || album.contains(Song::kAlbumRemoveDisc)) return TextOrUnknown(album);
if (disc <= 0 || Song::AlbumContainsDisc(album)) return TextOrUnknown(album);
else return TextOrUnknown(album) + " - (Disc " + QString::number(disc) + ")";
}
@ -1693,7 +1703,7 @@ QString CollectionModel::PrettyYearAlbumDisc(const int year, const QString &albu
if (year <= 0) str = TextOrUnknown(album);
else str = QString::number(year) + " - " + TextOrUnknown(album);
if (!album.contains(Song::kAlbumRemoveDisc) && disc > 0) str += " - (Disc " + QString::number(disc) + ")";
if (!Song::AlbumContainsDisc(album) && disc > 0) str += " - (Disc " + QString::number(disc) + ")";
return str;
@ -1719,15 +1729,17 @@ QString CollectionModel::SortText(QString text) {
}
QString CollectionModel::SortTextForArtist(QString artist) {
QString CollectionModel::SortTextForArtist(QString artist, const bool skip_articles) {
artist = SortText(artist);
for (const auto &i : Song::kArticles) {
if (artist.startsWith(i)) {
qint64 ilen = i.length();
artist = artist.right(artist.length() - ilen) + ", " + i.left(ilen - 1);
break;
if (skip_articles) {
for (const auto &i : Song::kArticles) {
if (artist.startsWith(i)) {
qint64 ilen = i.length();
artist = artist.right(artist.length() - ilen) + ", " + i.left(ilen - 1);
break;
}
}
}

View File

@ -162,6 +162,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
// Whether or not to show letters heading in the collection view
void set_show_dividers(const bool show_dividers);
// Whether to skip articles such as “The” when sorting artist names
void set_sort_skips_articles(const bool sort_skips_articles);
// Reload settings.
void ReloadSettings();
@ -173,7 +176,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
static QString PrettyDisc(const int disc);
static QString SortText(QString text);
static QString SortTextForNumber(const int number);
static QString SortTextForArtist(QString artist);
static QString SortTextForArtist(QString artist, const bool skip_articles);
static QString SortTextForSong(const Song &song);
static QString SortTextForYear(const int year);
static QString SortTextForBitrate(const int bitrate);
@ -278,6 +281,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
Application *app_;
CollectionDirectoryModel *dir_model_;
bool show_various_artists_;
bool sort_skips_articles_;
int total_song_count_;
int total_artist_count_;

View File

@ -232,6 +232,7 @@ void CollectionView::ReloadSettings() {
if (app_) {
app_->collection_model()->set_pretty_covers(settings.value("pretty_covers", true).toBool());
app_->collection_model()->set_show_dividers(settings.value("show_dividers", true).toBool());
app_->collection_model()->set_sort_skips_articles(settings.value("sort_skips_articles", true).toBool());
}
delete_files_ = settings.value("delete_files", false).toBool();
@ -343,6 +344,22 @@ void CollectionView::mouseReleaseEvent(QMouseEvent *e) {
}
void CollectionView::keyPressEvent(QKeyEvent *e) {
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (currentIndex().isValid()) {
AddToPlaylist();
}
e->accept();
break;
}
AutoExpandingTreeView::keyPressEvent(e);
}
void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
if (!context_menu_) {
@ -355,6 +372,10 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
action_add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, &CollectionView::AddToPlaylistEnqueue);
action_add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue to play next"), this, &CollectionView::AddToPlaylistEnqueueNext);
context_menu_->addSeparator();
action_search_for_this_ = context_menu_->addAction(IconLoader::Load("edit-find"), tr("Search for this"), this, &CollectionView::SearchForThis);
context_menu_->addSeparator();
action_organize_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, &CollectionView::Organize);
#ifndef Q_OS_WIN
@ -545,6 +566,100 @@ void CollectionView::OpenInNewPlaylist() {
}
void CollectionView::SearchForThis() {
QModelIndex current = currentIndex();
QVariant type = model()->data(current, CollectionModel::Role_Type);
if (!type.isValid() || (type.toInt() != CollectionItem::Type_Song && type.toInt() != CollectionItem::Type_Container && type.toInt() != CollectionItem::Type_Divider)) {
return;
}
QString search;
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
switch (type.toInt()) {
case CollectionItem::Type_Song:{
SongList songs = app_->collection_model()->GetChildSongs(index);
if (!songs.isEmpty()) {
last_selected_song_ = songs.last();
}
search = QString("title:%1").arg(last_selected_song_.title());
break;
}
case CollectionItem::Type_Divider:{
break;
}
case CollectionItem::Type_Container:{
CollectionItem *item = app_->collection_model()->IndexToItem(index);
int container_level = item->container_level;
CollectionModel::GroupBy container_group_by = app_->collection_model()->GetGroupBy()[container_level];
switch (container_group_by) {
case CollectionModel::GroupBy::AlbumArtist:
search = QString("albumartist:%1").arg(item->metadata.effective_albumartist());
break;
case CollectionModel::GroupBy::Artist:
search = QString("artist:%1").arg(item->metadata.artist());
break;
case CollectionModel::GroupBy::Album:
search = QString("album:%1").arg(item->metadata.album());
break;
case CollectionModel::GroupBy::AlbumDisc:
search = QString("album:%1").arg(item->metadata.album());
break;
case CollectionModel::GroupBy::YearAlbum:
case CollectionModel::GroupBy::YearAlbumDisc:{
search = QString("year:%1 album:%2").arg(item->metadata.year()).arg(item->metadata.album());
break;
}
case CollectionModel::GroupBy::OriginalYearAlbum:
case CollectionModel::GroupBy::OriginalYearAlbumDisc:{
search = QString("year:%1 album:%2").arg(item->metadata.effective_originalyear()).arg(item->metadata.album());
break;
}
case CollectionModel::GroupBy::Year:
search = QString("year:%1").arg(item->metadata.year());
break;
case CollectionModel::GroupBy::OriginalYear:
search = QString("year:%1").arg(item->metadata.effective_originalyear());
break;
case CollectionModel::GroupBy::Genre:
search = QString("genre:%1").arg(item->metadata.genre());
break;
case CollectionModel::GroupBy::Composer:
search = QString("composer:%1").arg(item->metadata.composer());
break;
case CollectionModel::GroupBy::Performer:
search = QString("performer:%1").arg(item->metadata.performer());
break;
case CollectionModel::GroupBy::Grouping:
search = QString("grouping:%1").arg(item->metadata.grouping());
break;
case CollectionModel::GroupBy::Samplerate:
search = QString("samplerate:%1").arg(item->metadata.samplerate());
break;
case CollectionModel::GroupBy::Bitdepth:
search = QString("bitdepth:%1").arg(item->metadata.bitdepth());
break;
case CollectionModel::GroupBy::Bitrate:
search = QString("bitrate:%1").arg(item->metadata.bitrate());
break;
default:
search = model()->data(current, Qt::DisplayRole).toString();
}
break;
}
default:
return;
}
filter_->ShowInCollection(search);
}
void CollectionView::keyboardSearch(const QString &search) {
is_in_keyboard_search_ = true;

View File

@ -93,6 +93,7 @@ class CollectionView : public AutoExpandingTreeView {
protected:
// QWidget
void paintEvent(QPaintEvent *event) override;
void keyPressEvent(QKeyEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
@ -102,6 +103,7 @@ class CollectionView : public AutoExpandingTreeView {
void AddToPlaylistEnqueue();
void AddToPlaylistEnqueueNext();
void OpenInNewPlaylist();
void SearchForThis();
void Organize();
void CopyToDevice();
void EditTracks();
@ -136,6 +138,8 @@ class CollectionView : public AutoExpandingTreeView {
QAction *action_add_to_playlist_enqueue_next_;
QAction *action_open_in_new_playlist_;
QAction *action_organize_;
QAction *action_search_for_this_;
#ifndef Q_OS_WIN
QAction *action_copy_to_device_;
#endif

View File

@ -67,7 +67,7 @@
#include "lyrics/songlyricscomlyricsprovider.h"
#include "lyrics/azlyricscomlyricsprovider.h"
#include "lyrics/elyricsnetlyricsprovider.h"
#include "lyrics/lyricsmodecomlyricsprovider.h"
#include "lyrics/letraslyricsprovider.h"
#include "scrobbler/audioscrobbler.h"
#include "scrobbler/lastfmscrobbler.h"
@ -171,7 +171,7 @@ class ApplicationImpl {
lyrics_providers->AddProvider(new SongLyricsComLyricsProvider(app->network()));
lyrics_providers->AddProvider(new AzLyricsComLyricsProvider(app->network()));
lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(app->network()));
lyrics_providers->AddProvider(new LyricsModeComLyricsProvider(app->network()));
lyrics_providers->AddProvider(new LetrasLyricsProvider(app->network()));
lyrics_providers->ReloadSettings();
return lyrics_providers;
}),

View File

@ -92,7 +92,8 @@ void DeleteFiles::ProcessSomeFiles() {
if (progress_ >= songs_.count()) {
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
storage_->FinishCopy(songs_with_errors_.isEmpty());
QString error_text;
storage_->FinishCopy(songs_with_errors_.isEmpty(), error_text);
task_manager_->SetTaskFinished(task_id_);

View File

@ -36,7 +36,7 @@
FilesystemMusicStorage::FilesystemMusicStorage(const Song::Source source, const QString &root, const std::optional<int> collection_directory_id) : source_(source), root_(root), collection_directory_id_(collection_directory_id) {}
bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job, QString &error_text) {
const QFileInfo src = QFileInfo(job.source_);
const QFileInfo dest = QFileInfo(root_ + "/" + job.destination_);
@ -54,7 +54,8 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
// Create directories as required
QDir dir;
if (!dir.mkpath(dest.absolutePath())) {
qLog(Warning) << "Failed to create directory" << dest.dir().absolutePath();
error_text = QObject::tr("Failed to create directory %1.").arg(dest.dir().absolutePath());
qLog(Error) << error_text;
return false;
}
@ -65,10 +66,12 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
}
// Copy or move
bool result(true);
bool result = true;
if (job.remove_original_) {
if (dest.exists() && !job.overwrite_) {
result = false;
error_text = QObject::tr("Destination file %1 exists, but not allowed to overwrite.").arg(dest.absoluteFilePath());
qLog(Error) << error_text;
}
else {
result = QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath());
@ -86,9 +89,15 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
else {
if (dest.exists() && !job.overwrite_) {
result = false;
error_text = QObject::tr("Destination file %1 exists, but not allowed to overwrite").arg(dest.absoluteFilePath());
qLog(Error) << error_text;
}
else {
result = QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath());
if (!result) {
error_text = QObject::tr("Could not copy file %1 to %2.").arg(src.absoluteFilePath(), dest.absoluteFilePath());
qLog(Error) << error_text;
}
}
if ((!cover_dest.exists() || job.overwrite_) && !cover_src.filePath().isEmpty() && !cover_dest.filePath().isEmpty()) {
QFile::copy(cover_src.absoluteFilePath(), cover_dest.absoluteFilePath());

View File

@ -39,7 +39,7 @@ class FilesystemMusicStorage : public virtual MusicStorage {
QString LocalPath() const override { return root_; }
std::optional<int> collection_directory_id() const override { return collection_directory_id_; }
bool CopyToStorage(const CopyJob &job) override;
bool CopyToStorage(const CopyJob &job, QString &error_text) override;
bool DeleteFromStorage(const DeleteJob &job) override;
private:

View File

@ -73,6 +73,10 @@
#include <QToolButton>
#include <QCheckBox>
#include <QClipboard>
#ifdef HAVE_DBUS
# include <QDBusConnection>
# include <QDBusMessage>
#endif
#include "core/logging.h"
@ -1269,6 +1273,11 @@ void MainWindow::Exit() {
return; // Don't quit the application now: wait for the fadeout finished signal
}
}
#ifdef HAVE_DBUS
UpdateTaskbarProgress(false, 0, 0);
#endif
DoExit();
}
@ -1321,6 +1330,10 @@ void MainWindow::MediaStopped() {
tray_icon_->SetProgress(0);
tray_icon_->SetStopped();
#ifdef HAVE_DBUS
UpdateTaskbarProgress(false, 0, 0);
#endif
song_playing_ = Song();
song_ = Song();
album_cover_ = AlbumCoverImageResult();
@ -1397,6 +1410,10 @@ void MainWindow::SongChanged(const Song &song) {
setWindowTitle(song.PrettyTitleWithArtist());
tray_icon_->SetProgress(0);
#ifdef HAVE_DBUS
UpdateTaskbarProgress(false, 0, 0);
#endif
SendNowPlaying();
const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty();
@ -1686,6 +1703,10 @@ void MainWindow::Seeked(const qint64 microseconds) {
const qint64 length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec;
tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
#ifdef HAVE_DBUS
UpdateTaskbarProgress(true, position, length);
#endif
}
void MainWindow::UpdateTrackPosition() {
@ -1700,6 +1721,10 @@ void MainWindow::UpdateTrackPosition() {
// Update the tray icon every 10 seconds
if (position % 10 == 0) tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
#ifdef HAVE_DBUS
UpdateTaskbarProgress(true, position, length);
#endif
// Send Scrobble
if (app_->scrobbler()->enabled() && item->Metadata().is_metadata_good()) {
Playlist *playlist = app_->playlist_manager()->active();
@ -1726,6 +1751,21 @@ void MainWindow::UpdateTrackSliderPosition() {
}
#ifdef HAVE_DBUS
void MainWindow::UpdateTaskbarProgress(const bool visible, const double position, const double length) {
QVariantMap map;
QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/org/strawberrymusicplayer/strawberry"), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update"));
map.insert(QStringLiteral("progress-visible"), visible);
map.insert(QStringLiteral("progress"), position / length);
msg << QString("application://org.strawberrymusicplayer.strawberry.desktop") << map;
QDBusConnection::sessionBus().send(msg);
}
#endif
void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) {
switch (b) {

View File

@ -290,6 +290,10 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void SetToggleScrobblingIcon(const bool value);
#ifdef HAVE_DBUS
void UpdateTaskbarProgress(const bool visible, const double position, const double length);
#endif
private:
Ui_MainWindow *ui_;
#ifdef Q_OS_WIN

View File

@ -122,6 +122,9 @@
</property>
<item>
<widget class="QToolButton" name="back_button">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
@ -135,6 +138,9 @@
</item>
<item>
<widget class="QToolButton" name="pause_play_button">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
@ -151,6 +157,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
@ -167,6 +176,9 @@
</item>
<item>
<widget class="QToolButton" name="forward_button">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
@ -205,6 +217,9 @@
</item>
<item>
<widget class="QToolButton" name="button_love">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize">
<size>
<width>32</width>

View File

@ -89,12 +89,12 @@ class MusicStorage {
virtual bool GetSupportedFiletypes(QList<Song::FileType> *ret) { Q_UNUSED(ret); return true; }
virtual bool StartCopy(QList<Song::FileType> *supported_types) { Q_UNUSED(supported_types); return true; }
virtual bool CopyToStorage(const CopyJob &job) = 0;
virtual void FinishCopy(bool success) { Q_UNUSED(success); }
virtual bool CopyToStorage(const CopyJob &job, QString &error_text) = 0;
virtual bool FinishCopy(bool success, QString &error_text) { Q_UNUSED(error_text); return success; }
virtual void StartDelete() {}
virtual bool DeleteFromStorage(const DeleteJob &job) = 0;
virtual void FinishDelete(bool success) { Q_UNUSED(success); }
virtual bool FinishDelete(bool success, QString &error_text) { Q_UNUSED(error_text); return success; }
virtual void Eject() {}

View File

@ -136,7 +136,7 @@ const QStringList Song::kColumns = QStringList() << "title"
<< "ebur128_integrated_loudness_lufs"
<< "ebur128_loudness_range_lu"
;
;
const QString Song::kColumnSpec = Song::kColumns.join(", ");
const QString Song::kBindSpec = Utilities::Prepend(":", Song::kColumns).join(", ");
@ -167,9 +167,28 @@ const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(", ");
const QString Song::kFtsBindSpec = Utilities::Prepend(":", Song::kFtsColumns).join(", ");
const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).join(", ");
const QRegularExpression Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const QRegularExpression Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|([0-9]{1,4}) *Remaster|Explicit) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const QRegularExpression Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Remastered Version|([0-9]{1,4}) *Remaster) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const Song::RegularExpressionList Song::kAlbumDisc = Song::RegularExpressionList()
<< QRegularExpression("\\s+-*\\s*(Disc|CD)\\s*([0-9]{1,2})$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\(\\s*(Disc|CD)\\s*([0-9]{1,2})\\)$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\[\\s*(Disc|CD)\\s*([0-9]{1,2})\\]$", QRegularExpression::CaseInsensitiveOption);
const Song::RegularExpressionList Song::kRemastered = Song::RegularExpressionList()
<< QRegularExpression("\\s+-*\\s*(([0-9]{4})*\\s*Remastered|([0-9]{4})*\\s*Remaster)\\s*(Version)*\\s*$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\(\\s*(([0-9]{4})*\\s*Remastered|([0-9]{4})*\\s*Remaster)\\s*(Version)*\\s*\\)\\s*$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\[\\s*(([0-9]{4})*\\s*Remastered|([0-9]{4})*\\s*Remaster)\\s*(Version)*\\s*\\]\\s*$", QRegularExpression::CaseInsensitiveOption);
const Song::RegularExpressionList Song::kExplicit = Song::RegularExpressionList()
<< QRegularExpression("\\s+-*\\s*Explicit\\s*$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\(\\s*Explicit\\s*\\)\\s*$", QRegularExpression::CaseInsensitiveOption)
<< QRegularExpression("\\s+-*\\s*\\[\\s*Explicit\\s*\\]\\s*$", QRegularExpression::CaseInsensitiveOption);
const Song::RegularExpressionList Song::kAlbumMisc = Song::RegularExpressionList()
<< kRemastered
<< kExplicit;
const Song::RegularExpressionList Song::kTitleMisc = Song::RegularExpressionList()
<< kRemastered
<< kExplicit;
const QStringList Song::kArticles = QStringList() << "the " << "a " << "an ";
@ -1843,3 +1862,53 @@ size_t HashSimilar(const Song &song) {
// Should compare the same fields as function IsSimilar
return qHash(song.title().toLower()) ^ qHash(song.artist().toLower()) ^ qHash(song.album().toLower());
}
bool Song::ContainsRegexList(const QString &str, const RegularExpressionList &regex_list) {
for (const QRegularExpression &regex : regex_list) {
if (str.contains(regex)) return true;
}
return false;
}
QString Song::StripRegexList(QString str, const RegularExpressionList &regex_list) {
for (const QRegularExpression &regex : regex_list) {
str = str.remove(regex);
}
return str;
}
bool Song::AlbumContainsDisc(const QString &album) {
return ContainsRegexList(album, kAlbumDisc);
}
QString Song::AlbumRemoveDisc(const QString &album) {
return StripRegexList(album, kAlbumDisc);
}
QString Song::AlbumRemoveMisc(const QString &album) {
return StripRegexList(album, kAlbumMisc);
}
QString Song::AlbumRemoveDiscMisc(const QString &album) {
return StripRegexList(album, RegularExpressionList() << kAlbumDisc << kAlbumMisc);
}
QString Song::TitleRemoveMisc(const QString &title) {
return StripRegexList(title, kTitleMisc);
}

View File

@ -123,9 +123,12 @@ class Song {
static const QString kFtsBindSpec;
static const QString kFtsUpdateSpec;
static const QRegularExpression kAlbumRemoveDisc;
static const QRegularExpression kAlbumRemoveMisc;
static const QRegularExpression kTitleRemoveMisc;
using RegularExpressionList = QList<QRegularExpression>;
static const RegularExpressionList kAlbumDisc;
static const RegularExpressionList kRemastered;
static const RegularExpressionList kExplicit;
static const RegularExpressionList kAlbumMisc;
static const RegularExpressionList kTitleMisc;
static const QStringList kArticles;
@ -443,6 +446,14 @@ class Song {
// It is more efficient to use IsOnSameAlbum, but this function can be used when you need to hash the key to do fast lookups.
QString AlbumKey() const;
static bool ContainsRegexList(const QString &str, const RegularExpressionList &regex_list);
static QString StripRegexList(QString str, const RegularExpressionList &regex_list);
static bool AlbumContainsDisc(const QString &album);
static QString AlbumRemoveDisc(const QString &album);
static QString AlbumRemoveMisc(const QString &album);
static QString AlbumRemoveDiscMisc(const QString &album);
static QString TitleRemoveMisc(const QString &title);
private:
struct Private;

View File

@ -333,7 +333,7 @@ AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) {
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return AlbumCoverImageResult();
QString album = song->effective_album();
album = album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc);
album = Song::AlbumRemoveDiscMisc(album);
// Get something sensible to stick in the search box
return cover_searcher_->Exec(song->effective_albumartist(), album);

View File

@ -66,9 +66,7 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
CoverSearchRequest request;
request.id = ++next_id_;
request.artist = artist;
request.album = album;
request.album = request.album.remove(Song::kAlbumRemoveDisc);
request.album = request.album.remove(Song::kAlbumRemoveMisc);
request.album = Song::AlbumRemoveDiscMisc(album);
request.title = title;
request.search = false;
request.batch = batch;
@ -83,9 +81,7 @@ quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString
CoverSearchRequest request;
request.id = ++next_id_;
request.artist = artist;
request.album = album;
request.album = request.album.remove(Song::kAlbumRemoveDisc);
request.album = request.album.remove(Song::kAlbumRemoveMisc);
request.album = Song::AlbumRemoveDiscMisc(album);
request.title = title;
request.search = true;
request.batch = false;

View File

@ -87,6 +87,9 @@
</item>
<item>
<widget class="QToolButton" name="view">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="text">
<string>View</string>
</property>

View File

@ -265,12 +265,9 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
}
QString album = obj_album["title"].toString();
album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.album = Song::AlbumRemoveDiscMisc(album);
bool have_cover = false;
QList<QPair<QString, QSize>> cover_sizes = QList<QPair<QString, QSize>>() << qMakePair(QString("cover_xl"), QSize(1000, 1000))

View File

@ -262,12 +262,9 @@ void QobuzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
}
QUrl cover_url(obj_image["large"].toString());
album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.album = Song::AlbumRemoveDiscMisc(album);
cover_result.image_url = cover_url;
cover_result.image_size = QSize(600, 600);
results << cover_result;

View File

@ -241,15 +241,11 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
continue;
}
QString album = obj_album["title"].toString();
QString cover = obj_album["cover"].toString();
album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
cover = cover.replace("-", "/");
QString cover = obj_album["cover"].toString().replace("-", "/");
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.album = Song::AlbumRemoveDiscMisc(album);
cover_result.number = ++i;
QList<QPair<QString, QSize>> cover_sizes = QList<QPair<QString, QSize>>() << qMakePair(QString("1280x1280"), QSize(1280, 1280))

View File

@ -51,7 +51,7 @@ class CddaDevice : public ConnectedDevice {
bool Init() override;
void Refresh() override;
bool CopyToStorage(const MusicStorage::CopyJob&) override { return false; }
bool CopyToStorage(const CopyJob&, QString&) override { return false; }
bool DeleteFromStorage(const MusicStorage::DeleteJob&) override { return false; }
static QStringList url_schemes() { return QStringList() << "cdda"; }

View File

@ -132,12 +132,14 @@ void ConnectedDevice::Eject() {
}
void ConnectedDevice::FinishCopy(bool) {
bool ConnectedDevice::FinishCopy(bool success, QString&) {
lister_->UpdateDeviceFreeSpace(unique_id_);
return success;
}
void ConnectedDevice::FinishDelete(bool) {
bool ConnectedDevice::FinishDelete(bool success, QString&) {
lister_->UpdateDeviceFreeSpace(unique_id_);
return success;
}
MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const {

View File

@ -67,8 +67,8 @@ class ConnectedDevice : public QObject, public virtual MusicStorage, public enab
QUrl url() const { return url_; }
qint64 song_count() const { return song_count_; }
void FinishCopy(bool success) override;
void FinishDelete(bool success) override;
bool FinishCopy(bool success, QString &error_text) override;
bool FinishDelete(bool success, QString &error_text) override;
void Eject() override;
virtual void Close();

View File

@ -830,7 +830,7 @@ void DeviceManager::DeviceTaskStarted(const int id) {
for (int i = 0; i < devices_.count(); ++i) {
DeviceInfo *info = devices_[i];
if (&*info->device_ == device) {
if (info->device_ && &*info->device_ == device) {
QModelIndex index = ItemToIndex(info);
if (!index.isValid()) continue;
active_tasks_[id] = index;

View File

@ -185,7 +185,7 @@ void GPodDevice::AddTrackToModel(Itdb_Track *track, const QString &prefix) {
}
bool GPodDevice::CopyToStorage(const CopyJob &job) {
bool GPodDevice::CopyToStorage(const CopyJob &job, QString &error_text) {
Q_ASSERT(db_);
@ -238,9 +238,10 @@ bool GPodDevice::CopyToStorage(const CopyJob &job) {
GError *error = nullptr;
itdb_cp_track_to_ipod(track, QDir::toNativeSeparators(job.source_).toLocal8Bit().constData(), &error);
if (error) {
qLog(Error) << "Copying failed:" << error->message;
app_->AddError(QString::fromUtf8(error->message));
error_text = tr("Could not copy %1 to %2: %3").arg(job.metadata_.url().toLocalFile(), url_.path(), QString::fromUtf8(error->message));
g_error_free(error);
qLog(Error) << error_text;
app_->AddError(error_text);
// Need to remove the track from the db again
itdb_track_remove(track);
@ -272,19 +273,24 @@ bool GPodDevice::CopyToStorage(const CopyJob &job) {
}
bool GPodDevice::WriteDatabase() {
bool GPodDevice::WriteDatabase(QString &error_text) {
// Write the itunes database
GError *error = nullptr;
itdb_write(db_, &error);
const bool success = itdb_write(db_, &error);
cover_files_.clear();
if (error) {
qLog(Error) << "Writing database failed:" << error->message;
app_->AddError(QString::fromUtf8(error->message));
g_error_free(error);
return false;
if (!success) {
if (error) {
error_text = tr("Writing database failed: %1").arg(error->message);
g_error_free(error);
}
else {
error_text = tr("Writing database failed.");
}
app_->AddError(error_text);
}
else return true;
return success;
}
@ -307,11 +313,11 @@ void GPodDevice::Finish(const bool success) {
}
void GPodDevice::FinishCopy(bool success) {
bool GPodDevice::FinishCopy(bool success, QString &error_text) {
if (success) success = WriteDatabase();
if (success) success = WriteDatabase(error_text);
Finish(success);
ConnectedDevice::FinishCopy(success);
return ConnectedDevice::FinishCopy(success, error_text);
}
@ -378,11 +384,11 @@ bool GPodDevice::DeleteFromStorage(const DeleteJob &job) {
}
void GPodDevice::FinishDelete(bool success) {
bool GPodDevice::FinishDelete(bool success, QString &error_text) {
if (success) success = WriteDatabase();
if (success) success = WriteDatabase(error_text);
Finish(success);
ConnectedDevice::FinishDelete(success);
return ConnectedDevice::FinishDelete(success, error_text);
}

View File

@ -64,12 +64,12 @@ class GPodDevice : public ConnectedDevice, public virtual MusicStorage {
bool GetSupportedFiletypes(QList<Song::FileType> *ret) override;
bool StartCopy(QList<Song::FileType> *supported_filetypes) override;
bool CopyToStorage(const CopyJob &job) override;
void FinishCopy(bool success) override;
bool CopyToStorage(const CopyJob &job, QString &error_text) override;
bool FinishCopy(bool success, QString &error_text) override;
void StartDelete() override;
bool DeleteFromStorage(const DeleteJob &job) override;
void FinishDelete(bool success) override;
bool FinishDelete(bool success, QString &error_text) override;
protected slots:
void LoadFinished(Itdb_iTunesDB *db, const bool success);
@ -83,7 +83,7 @@ class GPodDevice : public ConnectedDevice, public virtual MusicStorage {
private:
void Start();
void Finish(const bool success);
bool WriteDatabase();
bool WriteDatabase(QString &error_text);
protected:
GPodLoader *loader_;

View File

@ -54,7 +54,8 @@ MtpConnection::MtpConnection(const QUrl &url, QObject *parent) : QObject(parent)
device_num = url_query.queryItemValue("devnum").toUInt();
}
else {
qLog(Warning) << "Invalid MTP device:" << hostname;
error_text_ = tr("Invalid MTP device: %1").arg(hostname);
qLog(Error) << error_text_;
return;
}
@ -70,15 +71,20 @@ MtpConnection::MtpConnection(const QUrl &url, QObject *parent) : QObject(parent)
raw_device->devnum = device_num;
device_ = LIBMTP_Open_Raw_Device(raw_device); // NOLINT(clang-analyzer-unix.Malloc)
if (!device_) {
error_text_ = tr("Could not open MTP device.");
qLog(Error) << error_text_;
}
return;
}
// Get a list of devices from libmtp and figure out which one is ours
int count = 0;
LIBMTP_raw_device_t *raw_devices = nullptr;
LIBMTP_error_number_t err = LIBMTP_Detect_Raw_Devices(&raw_devices, &count);
if (err != LIBMTP_ERROR_NONE) {
qLog(Warning) << "MTP error:" << err;
LIBMTP_error_number_t error_number = LIBMTP_Detect_Raw_Devices(&raw_devices, &count);
if (error_number != LIBMTP_ERROR_NONE) {
error_text_ = tr("MTP error: %1").arg(ErrorString(error_number));
qLog(Error) << error_text_;
return;
}
@ -91,13 +97,18 @@ MtpConnection::MtpConnection(const QUrl &url, QObject *parent) : QObject(parent)
}
if (!raw_device) {
qLog(Warning) << "MTP device not found";
error_text_ = tr("MTP device not found.");
qLog(Error) << error_text_;
free(raw_devices);
return;
}
// Connect to the device
device_ = LIBMTP_Open_Raw_Device(raw_device);
if (!device_) {
error_text_ = tr("Could not open MTP device.");
qLog(Error) << error_text_;
}
free(raw_devices);
@ -107,6 +118,24 @@ MtpConnection::~MtpConnection() {
if (device_) LIBMTP_Release_Device(device_);
}
QString MtpConnection::ErrorString(const LIBMTP_error_number_t error_number) {
switch(error_number) {
case LIBMTP_ERROR_NO_DEVICE_ATTACHED:
return "No Devices have been found.";
case LIBMTP_ERROR_CONNECTING:
return "There has been an error connecting.";
case LIBMTP_ERROR_MEMORY_ALLOCATION:
return "Memory Allocation Error.";
case LIBMTP_ERROR_GENERAL:
default:
return "Unknown error, please report this to the libmtp developers.";
case LIBMTP_ERROR_NONE:
return "Successfully connected.";
}
}
bool MtpConnection::GetSupportedFiletypes(QList<Song::FileType> *ret) {
if (!device_) return false;

View File

@ -45,13 +45,17 @@ class MtpConnection : public QObject, public enable_shared_from_this<MtpConnecti
~MtpConnection() override;
bool is_valid() const { return device_; }
QString error_text() const { return error_text_; }
LIBMTP_mtpdevice_t *device() const { return device_; }
bool GetSupportedFiletypes(QList<Song::FileType> *ret);
static QString ErrorString(const LIBMTP_error_number_t error_number);
private:
Q_DISABLE_COPY(MtpConnection)
LIBMTP_mtpdevice_t *device_;
QString error_text_;
};
#endif // MTPCONNECTION_H

View File

@ -143,7 +143,8 @@ bool MtpDevice::StartCopy(QList<Song::FileType> *supported_types) {
// Did the caller want a list of supported types?
if (supported_types) {
if (!GetSupportedFiletypes(supported_types, connection_->device())) {
FinishCopy(false);
QString error_text;
FinishCopy(false, error_text);
return false;
}
}
@ -161,7 +162,7 @@ static int ProgressCallback(uint64_t const sent, uint64_t const total, void cons
}
bool MtpDevice::CopyToStorage(const CopyJob &job) {
bool MtpDevice::CopyToStorage(const CopyJob &job, QString &error_text) {
if (!connection_ || !connection_->is_valid()) return false;
@ -171,7 +172,15 @@ bool MtpDevice::CopyToStorage(const CopyJob &job) {
// Send the file
int ret = LIBMTP_Send_Track_From_File(connection_->device(), job.source_.toUtf8().constData(), &track, ProgressCallback, &job);
if (ret != 0) return false;
if (ret != 0) {
LIBMTP_error_struct *error = LIBMTP_Get_Errorstack(connection_->device());
if (error) {
error_text = QString::fromUtf8(error->error_text);
qLog(Error) << error_text;
LIBMTP_Clear_Errorstack(connection_->device());
}
return false;
}
// Add it to our CollectionModel
Song metadata_on_device(Song::Source::Device);
@ -190,7 +199,7 @@ bool MtpDevice::CopyToStorage(const CopyJob &job) {
}
void MtpDevice::FinishCopy(const bool success) {
bool MtpDevice::FinishCopy(const bool success, QString &error_text) {
if (success) {
if (!songs_to_add_.isEmpty()) backend_->AddOrUpdateSongs(songs_to_add_);
@ -205,7 +214,7 @@ void MtpDevice::FinishCopy(const bool success) {
db_busy_.unlock();
ConnectedDevice::FinishCopy(success);
return ConnectedDevice::FinishCopy(success, error_text);
}
@ -234,7 +243,7 @@ bool MtpDevice::DeleteFromStorage(const DeleteJob &job) {
}
void MtpDevice::FinishDelete(const bool success) { FinishCopy(success); }
bool MtpDevice::FinishDelete(const bool success, QString &error_text) { return FinishCopy(success, error_text); }
bool MtpDevice::GetSupportedFiletypes(QList<Song::FileType> *ret) {

View File

@ -63,12 +63,12 @@ class MtpDevice : public ConnectedDevice {
int GetCapacity();
bool StartCopy(QList<Song::FileType> *supported_types) override;
bool CopyToStorage(const CopyJob &job) override;
void FinishCopy(const bool success) override;
bool CopyToStorage(const CopyJob &job, QString &error_text) override;
bool FinishCopy(const bool success, QString &error_text) override;
void StartDelete() override;
bool DeleteFromStorage(const DeleteJob &job) override;
void FinishDelete(const bool success) override;
bool FinishDelete(const bool success, QString &error_text) override;
private slots:
void LoadFinished(bool success, MtpConnection *connection);

View File

@ -68,11 +68,16 @@ bool MtpLoader::TryLoad() {
connection_ = make_unique<MtpConnection>(url_);
if (!connection_ || !connection_->is_valid()) {
if (!connection_) {
emit Error(tr("Error connecting MTP device %1").arg(url_.toString()));
return false;
}
if (!connection_->is_valid()) {
emit Error(tr("Error connecting MTP device %1: %2").arg(url_.toString(), connection_->error_text()));
return false;
}
// Load the list of songs on the device
SongList songs;
LIBMTP_track_t *tracks = LIBMTP_Get_Tracklisting_With_Callback(connection_->device(), nullptr, nullptr);
@ -84,7 +89,7 @@ bool MtpLoader::TryLoad() {
Song song(Song::Source::Device);
song.InitFromMTP(track, url_.host());
if (song.is_valid() && !song.artist().isEmpty() && !song.title().isEmpty()) {
if (song.is_valid() && !song.title().isEmpty()) {
song.set_directory_id(1);
songs << song;
}

View File

@ -635,6 +635,9 @@
</item>
<item>
<widget class="QPushButton" name="tags_art_button">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="text">
<string>Change art</string>
</property>

View File

@ -0,0 +1,92 @@
/*
* Strawberry Music Player
* Copyright 2024, 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 <windows.h>
#include <string.h>
#include <atlconv.h>
#include <winreg.h>
#include <QString>
#include "asiodevicefinder.h"
#include "enginedevice.h"
#include "core/logging.h"
AsioDeviceFinder::AsioDeviceFinder() : DeviceFinder("asio", { "asiosink" }) {}
EngineDeviceList AsioDeviceFinder::ListDevices() {
EngineDeviceList devices;
HKEY reg_key = nullptr;
LSTATUS status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"software\\asio", 0, KEY_READ, &reg_key);
for (DWORD i = 0; status == ERROR_SUCCESS; i++) {
WCHAR key_name[256];
status = RegEnumKeyW(reg_key, i, key_name, sizeof(key_name));
EngineDevice device = GetDevice(reg_key, key_name);
if (device.value.isValid()) {
devices.append(device);
}
}
if (reg_key) {
RegCloseKey(reg_key);
}
return devices;
}
EngineDevice AsioDeviceFinder::GetDevice(HKEY reg_key, LPWSTR key_name) {
HKEY sub_key = nullptr;
const QScopeGuard scopeguard_sub_key = qScopeGuard([sub_key]() {
if (sub_key) {
RegCloseKey(sub_key);
}
});
LSTATUS status = RegOpenKeyExW(reg_key, key_name, 0, KEY_READ, &sub_key);
if (status != ERROR_SUCCESS) {
return EngineDevice();
}
DWORD type = REG_SZ;
WCHAR clsid_data[256]{};
DWORD clsid_data_size = sizeof(clsid_data);
status = RegQueryValueExW(sub_key, L"clsid", 0, &type, (LPBYTE)clsid_data, &clsid_data_size);
if (status != ERROR_SUCCESS) {
return EngineDevice();
}
EngineDevice device;
device.value = QString::fromStdWString(clsid_data);
device.description = QString::fromStdWString(key_name);
WCHAR desc_data[256]{};
DWORD desc_data_size = sizeof(desc_data);
status = RegQueryValueExW(sub_key, L"description", 0, &type, (LPBYTE)desc_data, &desc_data_size);
if (status == ERROR_SUCCESS) {
device.description = QString::fromStdWString(desc_data);
}
return device;
}

View File

@ -0,0 +1,41 @@
/*
* Strawberry Music Player
* Copyright 2024, 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 ASIODEVICEFINDER_H
#define ASIODEVICEFINDER_H
#include "config.h"
#include <windows.h>
#include "devicefinder.h"
#include "enginedevice.h"
class AsioDeviceFinder : public DeviceFinder {
public:
explicit AsioDeviceFinder();
virtual bool Initialize() { return true; }
virtual EngineDeviceList ListDevices();
private:
EngineDevice GetDevice(HKEY reg_key, LPWSTR key_name);
};
#endif // ASIODEVICEFINDER_H

View File

@ -45,6 +45,7 @@
# include "mmdevicefinder.h"
# ifdef _MSC_VER
# include "uwpdevicefinder.h"
# include "asiodevicefinder.h"
# endif // _MSC_VER
#endif // Q_OS_WIN32
@ -73,6 +74,7 @@ void DeviceFinders::Init() {
device_finders.append(new MMDeviceFinder);
# ifdef _MSC_VER
device_finders.append(new UWPDeviceFinder);
device_finders.append(new AsioDeviceFinder);
# endif // _MSC_VER
#endif // Q_OS_WIN32

View File

@ -35,7 +35,7 @@
#include "enginedevice.h"
#include "core/logging.h"
DirectSoundDeviceFinder::DirectSoundDeviceFinder() : DeviceFinder("directsound", { "directsound", "dsound", "directsoundsink", "directx", "directx2" }) {}
DirectSoundDeviceFinder::DirectSoundDeviceFinder() : DeviceFinder("directsound", { "directsound", "dsound", "directsoundsink", "directx", "directx2", "waveformsink" }) {}
EngineDeviceList DirectSoundDeviceFinder::ListDevices() {
State state;

View File

@ -39,6 +39,7 @@
EngineBase::EngineBase(QObject *parent)
: QObject(parent),
exclusive_mode_(false),
volume_control_(true),
volume_(100),
beginning_nanosec_(0),
@ -167,6 +168,8 @@ void EngineBase::ReloadSettings() {
output_ = s.value("output").toString();
device_ = s.value("device");
exclusive_mode_ = s.value("exclusive_mode", false).toBool();
volume_control_ = s.value("volume_control", true).toBool();
channels_enabled_ = s.value("channels_enabled", false).toBool();

View File

@ -129,6 +129,7 @@ class EngineBase : public QObject {
virtual QString DefaultOutput() = 0;
virtual bool CustomDeviceSupport(const QString &output) = 0;
virtual bool ALSADeviceSupport(const QString &output) = 0;
virtual bool ExclusiveModeSupport(const QString &output) = 0;
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
@ -188,6 +189,7 @@ class EngineBase : public QObject {
void VolumeChanged(const uint volume);
protected:
bool exclusive_mode_;
bool volume_control_;
uint volume_;
quint64 beginning_nanosec_;

View File

@ -73,6 +73,7 @@ const char *GstEngine::kAVDTPSink = "avdtpsink";
const char *GstEngine::InterAudiosink = "interaudiosink";
const char *GstEngine::kDirectSoundSink = "directsoundsink";
const char *GstEngine::kOSXAudioSink = "osxaudiosink";
const char *GstEngine::kWASAPISink = "wasapisink";
const int GstEngine::kDiscoveryTimeoutS = 10;
const qint64 GstEngine::kTimerIntervalNanosec = 1000 * kNsecPerMsec; // 1s
const qint64 GstEngine::kPreloadGapNanosec = 8000 * kNsecPerMsec; // 8s
@ -413,14 +414,19 @@ EngineBase::OutputDetailsList GstEngine::GetOutputsList() const {
GList *const features = gst_registry_get_feature_list(registry, GST_TYPE_ELEMENT_FACTORY);
for (GList *future = features; future; future = g_list_next(future)) {
GstElementFactory *factory = GST_ELEMENT_FACTORY(future->data);
const gchar *metadata = gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_KLASS);
if (QString(metadata).startsWith("Sink/Audio", Qt::CaseInsensitive)) {
OutputDetails output;
output.name = QString::fromUtf8(gst_plugin_feature_get_name(future->data));
output.description = QString::fromUtf8(gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_DESCRIPTION));
if (output.name == "wasapi2sink" && output.description == "Stream audio to an audio capture device through WASAPI") {
output.description.append("2");
const QString metadata = QString::fromUtf8(gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_KLASS));
const QString name = QString::fromUtf8(gst_plugin_feature_get_name(future->data));
if (metadata.startsWith("Sink/Audio", Qt::CaseInsensitive) || name == "pipewiresink" || (metadata.startsWith("Source/Audio", Qt::CaseInsensitive) && name.contains("sink"))) {
QString description = QString::fromUtf8(gst_element_factory_get_metadata(factory, GST_ELEMENT_METADATA_DESCRIPTION));
if (name == "wasapi2sink" && description == "Stream audio to an audio capture device through WASAPI") {
description.append("2");
}
else if (name == "pipewiresink" && description == "Send video to PipeWire") {
description = "Send audio to PipeWire";
}
OutputDetails output;
output.name = name;
output.description = description;
if (output.name == kAutoSink) output.iconname = "soundcard";
else if (output.name == kALSASink || output.name == kOSS4Sink) output.iconname = "alsa";
else if (output.name == kJackAudioSink) output.iconname = "jack";
@ -454,6 +460,10 @@ bool GstEngine::ALSADeviceSupport(const QString &output) {
return (output == kALSASink);
}
bool GstEngine::ExclusiveModeSupport(const QString &output) {
return output == kWASAPISink;
}
void GstEngine::ReloadSettings() {
EngineBase::ReloadSettings();
@ -789,6 +799,7 @@ SharedPtr<GstEnginePipeline> GstEngine::CreatePipeline() {
SharedPtr<GstEnginePipeline> ret = make_shared<GstEnginePipeline>();
ret->set_output_device(output_, device_);
ret->set_exclusive_mode(exclusive_mode_);
ret->set_volume_enabled(volume_control_);
ret->set_stereo_balancer_enabled(stereo_balancer_enabled_);
ret->set_equalizer_enabled(equalizer_enabled_);

View File

@ -81,6 +81,7 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
QString DefaultOutput() override { return kAutoSink; }
bool CustomDeviceSupport(const QString &output) override;
bool ALSADeviceSupport(const QString &output) override;
bool ExclusiveModeSupport(const QString &output) override;
void SetStartup(GstStartup *gst_startup) { gst_startup_ = gst_startup; }
void EnsureInitialized() { gst_startup_->EnsureInitialized(); }
@ -152,6 +153,7 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
static const char *InterAudiosink;
static const char *kDirectSoundSink;
static const char *kOSXAudioSink;
static const char *kWASAPISink;
static const int kDiscoveryTimeoutS;
static const qint64 kTimerIntervalNanosec;
static const qint64 kPreloadGapNanosec;

View File

@ -74,6 +74,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
: QObject(parent),
id_(sId++),
valid_(false),
exclusive_mode_(false),
volume_enabled_(true),
stereo_balancer_enabled_(false),
eq_enabled_(false),
@ -221,6 +222,10 @@ void GstEnginePipeline::set_output_device(const QString &output, const QVariant
}
void GstEnginePipeline::set_exclusive_mode(const bool exclusive_mode) {
exclusive_mode_ = exclusive_mode;
}
void GstEnginePipeline::set_volume_enabled(const bool enabled) {
volume_enabled_ = enabled;
}
@ -335,7 +340,7 @@ bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_ur
guint version_major = 0, version_minor = 0, version_micro = 0, version_nano = 0;
gst_plugins_base_version(&version_major, &version_minor, &version_micro, &version_nano);
if (QVersionNumber::compare(QVersionNumber(static_cast<int>(version_major), static_cast<int>(version_minor), static_cast<int>(version_micro)), QVersionNumber(1, 22, 0)) >= 0) {
if (QVersionNumber::compare(QVersionNumber(static_cast<int>(version_major), static_cast<int>(version_minor)), QVersionNumber(1, 22)) >= 0) {
pipeline_ = CreateElement("playbin3", "pipeline", nullptr, error);
}
else {
@ -448,7 +453,41 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
break;
}
}
else if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device-clsid")) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
switch (device_.metaType().id()) {
#else
switch (device_.type()) {
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
case QMetaType::QString:{
#else
case QVariant::String:{
#endif
QString device = device_.toString();
if (!device.isEmpty()) {
qLog(Debug) << "Setting device-clsid" << device << "for" << output_;
g_object_set(G_OBJECT(audiosink_), "device-clsid", device.toUtf8().constData(), nullptr);
}
break;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
case QMetaType::QByteArray:{
#else
case QVariant::ByteArray:{
#endif
QByteArray device = device_.toByteArray();
if (!device.isEmpty()) {
qLog(Debug) << "Setting device-clsid" << device_ << "for" << output_;
g_object_set(G_OBJECT(audiosink_), "device-clsid", device.constData(), nullptr);
}
break;
}
default:
qLog(Warning) << "Unknown device clsid" << device_;
break;
}
}
else if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "port-pattern")) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
switch (device_.metaType().id()) {
@ -489,6 +528,13 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
}
if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "exclusive")) {
if (exclusive_mode_) {
qLog(Debug) << "Setting exclusive mode for" << output_;
}
g_object_set(G_OBJECT(audiosink_), "exclusive", exclusive_mode_, nullptr);
}
#ifndef Q_OS_WIN32
if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "volume")) {
qLog(Debug) << output_ << "has volume, enabling volume synchronization.";

View File

@ -62,6 +62,7 @@ class GstEnginePipeline : public QObject {
// Call these setters before Init
void set_output_device(const QString &output, const QVariant &device);
void set_exclusive_mode(const bool exclusive_mode);
void set_volume_enabled(const bool enabled);
void set_stereo_balancer_enabled(const bool enabled);
void set_equalizer_enabled(const bool enabled);
@ -202,6 +203,7 @@ class GstEnginePipeline : public QObject {
bool valid_;
QString output_;
QVariant device_;
bool exclusive_mode_;
bool volume_enabled_;
bool stereo_balancer_enabled_;
bool eq_enabled_;

View File

@ -264,6 +264,11 @@ bool VLCEngine::ALSADeviceSupport(const QString &output) {
return (output == "alsa");
}
bool VLCEngine::ExclusiveModeSupport(const QString &output) {
Q_UNUSED(output);
return false;
}
uint VLCEngine::position() const {
if (!Initialized() || !libvlc_media_player_is_playing(player_)) return 0;

View File

@ -70,6 +70,7 @@ class VLCEngine : public EngineBase {
QString DefaultOutput() override { return ""; }
bool CustomDeviceSupport(const QString &output) override;
bool ALSADeviceSupport(const QString &output) override;
bool ExclusiveModeSupport(const QString &output) override;
private:
libvlc_instance_t *instance_;

View File

@ -223,7 +223,7 @@ static const QMap<Qt::Key, quint32> keymapper_x11_ = { // clazy:exclude=non-pod
{ Qt::Key_MediaPrevious, XF86XK_AudioPrev },
{ Qt::Key_MediaNext, XF86XK_AudioNext },
{ Qt::Key_MediaRecord, XF86XK_AudioRecord },
{ Qt::Key_MediaPause, XF86XK_AudioPause },
{ Qt::Key_MediaPause, XF86XK_AudioPause },
{ Qt::Key_HomePage, XF86XK_HomePage },
{ Qt::Key_Favorites, XF86XK_Favorites },
{ Qt::Key_Search, XF86XK_Search },

View File

@ -89,6 +89,7 @@ void InternetCollectionView::Init(Application *app, SharedPtr<CollectionBackend>
collection_model_->set_pretty_covers(true);
collection_model_->set_show_dividers(true);
collection_model_->set_sort_skips_articles(true);
ReloadSettings();

View File

@ -95,7 +95,7 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
}
else {
display_text = CollectionModel::TextOrUnknown(s.effective_albumartist());
sort_text = CollectionModel::SortTextForArtist(s.effective_albumartist());
sort_text = CollectionModel::SortTextForArtist(s.effective_albumartist(), true);
}
has_artist_icon = true;
break;
@ -107,14 +107,14 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
}
else {
display_text = CollectionModel::TextOrUnknown(s.artist());
sort_text = CollectionModel::SortTextForArtist(s.artist());
sort_text = CollectionModel::SortTextForArtist(s.artist(), true);
}
has_artist_icon = true;
break;
case CollectionModel::GroupBy::Album:
display_text = CollectionModel::TextOrUnknown(s.album());
sort_text = CollectionModel::SortTextForArtist(s.album());
sort_text = CollectionModel::SortTextForArtist(s.album(), true);
unique_tag = s.album_id();
has_album_icon = true;
break;
@ -168,7 +168,7 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
case CollectionModel::GroupBy::Disc:
display_text = CollectionModel::PrettyDisc(s.disc());
sort_text = CollectionModel::SortTextForArtist(display_text);
sort_text = CollectionModel::SortTextForArtist(display_text, true);
has_album_icon = true;
break;
@ -188,25 +188,25 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
case CollectionModel::GroupBy::Genre:
display_text = CollectionModel::TextOrUnknown(s.genre());
sort_text = CollectionModel::SortTextForArtist(s.genre());
sort_text = CollectionModel::SortTextForArtist(s.genre(), true);
has_album_icon = true;
break;
case CollectionModel::GroupBy::Composer:
display_text = CollectionModel::TextOrUnknown(s.composer());
sort_text = CollectionModel::SortTextForArtist(s.composer());
sort_text = CollectionModel::SortTextForArtist(s.composer(), true);
has_album_icon = true;
break;
case CollectionModel::GroupBy::Performer:
display_text = CollectionModel::TextOrUnknown(s.performer());
sort_text = CollectionModel::SortTextForArtist(s.performer());
sort_text = CollectionModel::SortTextForArtist(s.performer(), true);
has_album_icon = true;
break;
case CollectionModel::GroupBy::Grouping:
display_text = CollectionModel::TextOrUnknown(s.grouping());
sort_text = CollectionModel::SortTextForArtist(s.grouping());
sort_text = CollectionModel::SortTextForArtist(s.grouping(), true);
has_album_icon = true;
break;

View File

@ -176,7 +176,7 @@ void InternetSearchView::Init(Application *app, InternetServicePtr service) {
QMenu *settings_menu = new QMenu(this);
settings_menu->addActions(group_by_actions_->actions());
settings_menu->addSeparator();
settings_menu->addAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(service_->source())), this, &InternetSearchView::OpenSettingsDialog);
settings_menu->addAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::DescriptionForSource(service_->source())), this, &InternetSearchView::OpenSettingsDialog);
ui_->settings->setMenu(settings_menu);
swap_models_timer_->setSingleShot(true);

View File

@ -54,6 +54,9 @@
<height>0</height>
</size>
</property>
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="iconSize">
<size>
<width>16</width>

View File

@ -56,7 +56,7 @@ InternetSongsView::InternetSongsView(Application *app, InternetServicePtr servic
ui_->filter_widget->SetSettingsGroup(settings_group);
ui_->filter_widget->Init(service_->songs_collection_model());
QAction *action_configure = new QAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::TextForSource(service_->source())), this);
QAction *action_configure = new QAction(IconLoader::Load("configure"), tr("Configure %1...").arg(Song::DescriptionForSource(service_->source())), this);
QObject::connect(action_configure, &QAction::triggered, this, &InternetSongsView::OpenSettingsDialog);
ui_->filter_widget->AddMenuAction(action_configure);

View File

@ -18,8 +18,6 @@
*/
#include <QObject>
#include <QByteArray>
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QRegularExpression>

View File

@ -22,8 +22,6 @@
#include <QtGlobal>
#include <QObject>
#include <QList>
#include <QVariant>
#include <QString>
#include <QUrl>

View File

@ -18,8 +18,6 @@
*/
#include <QObject>
#include <QByteArray>
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QRegularExpression>

View File

@ -22,8 +22,6 @@
#include <QtGlobal>
#include <QObject>
#include <QList>
#include <QVariant>
#include <QString>
#include <QUrl>

View File

@ -56,6 +56,7 @@ bool HtmlLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &re
QUrl url(Url(request));
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
req.setRawHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0");
QNetworkReply *reply = network_->get(req);
replies_ << reply;
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, request]() { HandleLyricsReply(reply, id, request); });
@ -162,6 +163,7 @@ QString HtmlLyricsProvider::ParseLyricsFromHTML(const QString &content, const QR
.remove(QRegularExpression("<script>[^>]*</script>"))
.remove(QRegularExpression("<div [^>]*>×</div>"))
.replace(QRegularExpression("<br[^>]*>"), "\n")
.replace(QRegularExpression("</p>"), "\n\n")
.remove(QRegularExpression("<[^>]*>"))
.trimmed());
}

View File

@ -0,0 +1,56 @@
/*
* Strawberry Music Player
* Copyright 2024, 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 <QObject>
#include <QString>
#include <QUrl>
#include <QRegularExpression>
#include "core/shared_ptr.h"
#include "core/networkaccessmanager.h"
#include "core/logging.h"
#include "utilities/transliterate.h"
#include "lyricssearchrequest.h"
#include "letraslyricsprovider.h"
const char LetrasLyricsProvider::kUrl[] = "https://www.letras.mus.br/winamp.php";
const char LetrasLyricsProvider::kStartTag[] = "<div[^>]*>";
const char LetrasLyricsProvider::kEndTag[] = "<\\/div>";
const char LetrasLyricsProvider::kLyricsStart[] = "<div id=\"letra-cnt\">";
LetrasLyricsProvider::LetrasLyricsProvider(SharedPtr<NetworkAccessManager> network, QObject *parent)
: HtmlLyricsProvider("letras.mus.br", true, kStartTag, kEndTag, kLyricsStart, false, network, parent) {}
QUrl LetrasLyricsProvider::Url(const LyricsSearchRequest &request) {
return QUrl(QString(kUrl) + QStringLiteral("?musica=") + StringFixup(request.artist) + "&artista=" + StringFixup(request.title));
}
QString LetrasLyricsProvider::StringFixup(const QString &text) {
return QUrl::toPercentEncoding(Utilities::Transliterate(text)
.replace(QRegularExpression("[^\\w0-9_,&\\-\\(\\) ]"), "_")
.replace(QRegularExpression(" {2,}"), " ")
.simplified()
.replace(' ', '-')
.toLower()
);
}

View File

@ -1,6 +1,6 @@
/*
* Strawberry Music Player
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2024, 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
@ -17,13 +17,11 @@
*
*/
#ifndef LYRICSMODECOMLYRICSPROVIDER_H
#define LYRICSMODECOMLYRICSPROVIDER_H
#ifndef LETRASLYRICSPROVIDER_H
#define LETRASLYRICSPROVIDER_H
#include <QtGlobal>
#include <QObject>
#include <QList>
#include <QVariant>
#include <QString>
#include <QUrl>
@ -32,17 +30,17 @@
#include "htmllyricsprovider.h"
#include "lyricssearchrequest.h"
class LyricsModeComLyricsProvider : public HtmlLyricsProvider {
class LetrasLyricsProvider : public HtmlLyricsProvider {
Q_OBJECT
public:
explicit LyricsModeComLyricsProvider(SharedPtr<NetworkAccessManager> network, QObject *parent = nullptr);
explicit LetrasLyricsProvider(SharedPtr<NetworkAccessManager> network, QObject *parent = nullptr);
protected:
QUrl Url(const LyricsSearchRequest &request) override;
private:
QString StringFixup(QString text);
QString StringFixup(const QString &text);
private:
static const char kUrl[];
@ -51,4 +49,4 @@ class LyricsModeComLyricsProvider : public HtmlLyricsProvider {
static const char kLyricsStart[];
};
#endif // LYRICSMODECOMLYRICSPROVIDER_H
#endif // LETRASLYRICSPROVIDER_H

View File

@ -53,10 +53,8 @@ quint64 LyricsFetcher::Search(const QString &effective_albumartist, const QStrin
LyricsSearchRequest search_request;
search_request.albumartist = effective_albumartist;
search_request.artist = artist;
search_request.album = album;
search_request.album.remove(Song::kAlbumRemoveMisc);
search_request.title = title;
search_request.title.remove(Song::kTitleRemoveMisc);
search_request.album = Song::AlbumRemoveDiscMisc(album);
search_request.title = Song::TitleRemoveMisc(title);
Request request;
request.id = ++next_id_;

View File

@ -1,56 +0,0 @@
/*
* Strawberry Music Player
* Copyright 2023, 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 <QObject>
#include <QByteArray>
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QRegularExpression>
#include "core/shared_ptr.h"
#include "core/networkaccessmanager.h"
#include "lyricssearchrequest.h"
#include "lyricsmodecomlyricsprovider.h"
const char LyricsModeComLyricsProvider::kUrl[] = "https://www.lyricsmode.com/lyrics/";
const char LyricsModeComLyricsProvider::kStartTag[] = "<div[^>]*>";
const char LyricsModeComLyricsProvider::kEndTag[] = "<\\/div>";
const char LyricsModeComLyricsProvider::kLyricsStart[] = "<div id=\"lyrics_text\" [^>]*>";
LyricsModeComLyricsProvider::LyricsModeComLyricsProvider(SharedPtr<NetworkAccessManager> network, QObject *parent)
: HtmlLyricsProvider("lyricsmode.com", true, kStartTag, kEndTag, kLyricsStart, false, network, parent) {}
QUrl LyricsModeComLyricsProvider::Url(const LyricsSearchRequest &request) {
return QUrl(kUrl + request.artist[0].toLower() + "/" + StringFixup(request.artist) + "/" + StringFixup(request.title) + ".html");
}
QString LyricsModeComLyricsProvider::StringFixup(QString text) {
return text
.remove(QRegularExpression("[^\\w0-9_\\- ]"))
.replace(QRegularExpression(" {2,}"), " ")
.simplified()
.replace(' ', '_')
.replace('-', '_')
.toLower();
}

View File

@ -18,8 +18,6 @@
*/
#include <QObject>
#include <QByteArray>
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QRegularExpression>

View File

@ -22,8 +22,6 @@
#include <QtGlobal>
#include <QObject>
#include <QList>
#include <QVariant>
#include <QString>
#include <QUrl>

View File

@ -146,7 +146,10 @@ void Organize::ProcessSomeFiles() {
UpdateProgress();
destination_->FinishCopy(files_with_errors_.isEmpty());
QString error_text;
if (!destination_->FinishCopy(files_with_errors_.isEmpty(), error_text) && !error_text.isEmpty()) {
log_ << error_text;
}
if (eject_after_) destination_->Eject();
task_manager_->SetTaskFinished(task_id_);
@ -250,7 +253,8 @@ void Organize::ProcessSomeFiles() {
job.progress_ = std::bind(&Organize::SetSongProgress, this, std::placeholders::_1, !task.transcoded_filename_.isEmpty());
if (destination_->CopyToStorage(job)) {
QString error_text;
if (destination_->CopyToStorage(job, error_text)) {
if (job.remove_original_ && song.is_collection_song() && destination_->source() == Song::Source::Collection) {
// Notify other aspects of system that song has been invalidated
QString root = destination_->LocalPath();
@ -260,6 +264,9 @@ void Organize::ProcessSomeFiles() {
}
else {
files_with_errors_ << task.song_info_.song_.basefilename();
if (!error_text.isEmpty()) {
log_ << error_text;
}
}
// Clean up the temporary transcoded file

View File

@ -12,17 +12,17 @@
</property>
<property name="styleSheet">
<string notr="true">#container {
background: rgba(200, 200, 200, 50%);
background: rgba(200, 200, 200, 50%);
border-radius: 10px;
border: 1px solid rgba(200, 200, 200, 75%);
}
#label1 {
font-weight: bold;
font-weight: bold;
}
#label2 {
font-size: 7.5pt;
font-size: 7.5pt;
}</string>
</property>
<layout class="QVBoxLayout" name="layout_dynamic_playlist_controls">

View File

@ -269,6 +269,10 @@ bool Playlist::set_column_value(Song &song, Playlist::Column column, const QVari
QVariant Playlist::data(const QModelIndex &idx, int role) const {
if (!idx.isValid()) {
return QVariant();
}
switch (role) {
case Role_IsCurrent:
return current_item_index_.isValid() && idx.row() == current_item_index_.row();
@ -1022,44 +1026,6 @@ void Playlist::InsertItems(const PlaylistItemPtrList &itemsIn, const int pos, co
PlaylistItemPtrList items = itemsIn;
// Exercise vetoes
SongList songs;
songs.reserve(items.count());
for (PlaylistItemPtr item : items) { // clazy:exclude=range-loop-reference
songs << item->Metadata();
}
const qint64 song_count = songs.length();
QSet<Song> vetoed;
for (SongInsertVetoListener *listener : veto_listeners_) {
for (const Song &song : listener->AboutToInsertSongs(GetAllSongs(), songs)) {
// Avoid veto-ing a song multiple times
vetoed.insert(song);
}
if (vetoed.count() == song_count) {
// All songs were vetoed and there's nothing more to do (there's no need for an undo step)
return;
}
}
if (!vetoed.isEmpty()) {
QMutableListIterator<PlaylistItemPtr> it(items);
while (it.hasNext()) {
PlaylistItemPtr item = it.next();
const Song &current = item->Metadata();
if (vetoed.contains(current)) {
vetoed.remove(current);
it.remove();
}
}
// Check for empty items once again after veto
if (items.isEmpty()) {
return;
}
}
const int start = pos == -1 ? static_cast<int>(items_.count()) : pos;
if (items.count() > kUndoItemLimit) {
@ -1303,7 +1269,7 @@ bool Playlist::CompareItems(const int column, const Qt::SortOrder order, Playlis
case Column_Track: cmp(track);
case Column_Disc: cmp(disc);
case Column_Year: cmp(year);
case Column_OriginalYear: cmp(originalyear);
case Column_OriginalYear: cmp(effective_originalyear);
case Column_Genre: strcmp(genre);
case Column_AlbumArtist: strcmp(playlist_albumartist_sortable);
case Column_Composer: strcmp(composer);
@ -1901,20 +1867,6 @@ void Playlist::ReloadItemsBlocking(const QList<int> &rows) {
}
void Playlist::AddSongInsertVetoListener(SongInsertVetoListener *listener) {
veto_listeners_.append(listener);
QObject::connect(listener, &SongInsertVetoListener::destroyed, this, &Playlist::SongInsertVetoListenerDestroyed);
}
void Playlist::RemoveSongInsertVetoListener(SongInsertVetoListener *listener) {
QObject::disconnect(listener, &SongInsertVetoListener::destroyed, this, &Playlist::SongInsertVetoListenerDestroyed);
veto_listeners_.removeAll(listener);
}
void Playlist::SongInsertVetoListenerDestroyed() {
veto_listeners_.removeAll(qobject_cast<SongInsertVetoListener*>(sender()));
}
void Playlist::Shuffle() {
PlaylistItemPtrList new_items(items_);

View File

@ -74,18 +74,6 @@ using ColumnAlignmentMap = QMap<int, Qt::Alignment>;
Q_DECLARE_METATYPE(Qt::Alignment)
Q_DECLARE_METATYPE(ColumnAlignmentMap)
// Objects that may prevent a song being added to the playlist.
// When there is something about to be inserted into it,
// Playlist notifies all of its listeners about the fact and every one of them picks 'invalid' songs.
class SongInsertVetoListener : public QObject {
Q_OBJECT
public:
// Listener returns a list of 'invalid' songs.
// 'old_songs' are songs that are currently in the playlist and 'new_songs' are the songs about to be added if nobody exercises a veto.
virtual SongList AboutToInsertSongs(const SongList &old_songs, const SongList &new_songs) = 0;
};
class Playlist : public QAbstractListModel {
Q_OBJECT
@ -258,11 +246,6 @@ class Playlist : public QAbstractListModel {
void ReloadItemsBlocking(const QList<int> &rows);
void InformOfCurrentSongChange(const AutoScroll autoscroll, const bool minor);
// Registers an object which will get notifications when new songs are about to be inserted into this playlist.
void AddSongInsertVetoListener(SongInsertVetoListener *listener);
// Unregisters a SongInsertVetoListener object.
void RemoveSongInsertVetoListener(SongInsertVetoListener *listener);
// Just emits the dataChanged() signal so the mood column is repainted.
#ifdef HAVE_MOODBAR
void MoodbarUpdated(const QModelIndex &idx);
@ -376,7 +359,6 @@ class Playlist : public QAbstractListModel {
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata);
void ItemReloadComplete(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit);
void ItemsLoaded();
void SongInsertVetoListenerDestroyed();
void ScheduleSave();
void Save();
@ -420,8 +402,6 @@ class Playlist : public QAbstractListModel {
ColumnAlignmentMap column_alignments_;
QList<SongInsertVetoListener*> veto_listeners_;
QString special_type_;
// Cancel async restore if songs are already replaced

View File

@ -171,7 +171,7 @@ void PlaylistSequence::SetRepeatMode(const RepeatMode mode) {
case RepeatMode::Album: ui_->action_repeat_album->setChecked(true); break;
case RepeatMode::Playlist: ui_->action_repeat_playlist->setChecked(true); break;
case RepeatMode::OneByOne: ui_->action_repeat_onebyone->setChecked(true); break;
case RepeatMode::Intro: ui_->action_repeat_intro->setChecked(true); break;
case RepeatMode::Intro: ui_->action_repeat_intro->setChecked(true); break;
}
@ -234,11 +234,11 @@ void PlaylistSequence::CycleRepeatMode() {
RepeatMode mode = RepeatMode::Off;
//we cycle through the repeat modes
switch (repeat_mode()) {
case RepeatMode::Off: mode = RepeatMode::Track; break;
case RepeatMode::Track: mode = RepeatMode::Album; break;
case RepeatMode::Album: mode = RepeatMode::Playlist; break;
case RepeatMode::Playlist: mode = RepeatMode::OneByOne; break;
case RepeatMode::OneByOne: mode = RepeatMode::Intro; break;
case RepeatMode::Off: mode = RepeatMode::Track; break;
case RepeatMode::Track: mode = RepeatMode::Album; break;
case RepeatMode::Album: mode = RepeatMode::Playlist; break;
case RepeatMode::Playlist: mode = RepeatMode::OneByOne; break;
case RepeatMode::OneByOne: mode = RepeatMode::Intro; break;
case RepeatMode::Intro:
break;
}

View File

@ -1189,7 +1189,7 @@ void QobuzRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti
url.setScheme(url_handler_->scheme());
url.setPath(song_id);
title.remove(Song::kTitleRemoveMisc);
title = Song::TitleRemoveMisc(title);
//qLog(Debug) << "id" << song_id << "track" << track << "title" << title << "album" << album << "album artist" << album_artist << cover_url << streamable << url;

View File

@ -1,6 +1,6 @@
/*
* Strawberry Music Player
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2021-2024, 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
@ -19,28 +19,107 @@
#include <QObject>
#include <QUrl>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonValue>
#include <QJsonObject>
#include <QJsonArray>
#include "core/application.h"
#include "core/networkaccessmanager.h"
#include "core/taskmanager.h"
#include "core/iconloader.h"
#include "radioparadiseservice.h"
#include "radiochannel.h"
const char *RadioParadiseService::kApiChannelsUrl = "https://api.radioparadise.com/api/list_streams";
RadioParadiseService::RadioParadiseService(Application *app, SharedPtr<NetworkAccessManager> network, QObject *parent)
: RadioService(Song::Source::RadioParadise, "Radio Paradise", IconLoader::Load("radioparadise"), app, network, parent) {}
QUrl RadioParadiseService::Homepage() { return QUrl("https://radioparadise.com/"); }
QUrl RadioParadiseService::Donate() { return QUrl("https://payments.radioparadise.com/rp2s-content.php?name=Support&file=support"); }
void RadioParadiseService::GetChannels() {
void RadioParadiseService::Abort() {
emit NewChannels(RadioChannelList()
<< RadioChannel(source_, "Main Mix 320k AAC", QUrl("https://stream.radioparadise.com/aac-320"))
<< RadioChannel(source_, "Mellow Mix 320k AAC", QUrl("https://stream.radioparadise.com/mellow-320"))
<< RadioChannel(source_, "Rock Mix 320k AAC", QUrl("https://stream.radioparadise.com/rock-320"))
<< RadioChannel(source_, "World/Etc Mix 320k AAC", QUrl("https://stream.radioparadise.com/world-etc-320"))
<< RadioChannel(source_, "Main Mix FLAC", QUrl("https://stream.radioparadise.com/flacm"))
<< RadioChannel(source_, "Mellow Mix FLAC", QUrl("https://stream.radioparadise.com/mellow-flacm"))
<< RadioChannel(source_, "Rock Mix FLAC", QUrl("https://stream.radioparadise.com/rock-flacm"))
<< RadioChannel(source_, "World/Etc Mix FLAC", QUrl("https://stream.radioparadise.com/world-etc-flacm"))
);
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
QObject::disconnect(reply, nullptr, this, nullptr);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}
channels_.clear();
}
void RadioParadiseService::GetChannels() {
Abort();
QUrl url(kApiChannelsUrl);
QNetworkRequest req(url);
QNetworkReply *reply = network_->get(req);
replies_ << reply;
const int task_id = app_->task_manager()->StartTask(tr("Getting %1 channels").arg(name_));
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, task_id]() { GetChannelsReply(reply, task_id); });
}
void RadioParadiseService::GetChannelsReply(QNetworkReply *reply, const int task_id) {
if (replies_.contains(reply)) replies_.removeAll(reply);
reply->deleteLater();
QJsonObject object = ExtractJsonObj(reply);
if (object.isEmpty()) {
app_->task_manager()->SetTaskFinished(task_id);
emit NewChannels();
return;
}
if (!object.contains("channels") || !object["channels"].isArray()) {
Error("Missing JSON channels array.", object);
app_->task_manager()->SetTaskFinished(task_id);
emit NewChannels();
return;
}
QJsonArray array_channels = object["channels"].toArray();
RadioChannelList channels;
for (const QJsonValueRef value_channel : array_channels) {
if (!value_channel.isObject()) continue;
QJsonObject obj_channel = value_channel.toObject();
if (!obj_channel.contains("chan_name") || !obj_channel.contains("streams")) {
continue;
}
QString name = obj_channel["chan_name"].toString();
QJsonValue value_streams = obj_channel["streams"];
if (!value_streams.isArray()) {
continue;
}
QJsonArray array_streams = obj_channel["streams"].toArray();
for (const QJsonValueRef value_stream : array_streams) {
if (!value_stream.isObject()) continue;
QJsonObject obj_stream = value_stream.toObject();
if (!obj_stream.contains("label") || !obj_stream.contains("url")) {
continue;
}
QString label = obj_stream["label"].toString();
QString url = obj_stream["url"].toString();
if (!url.contains(QRegularExpression("^[0-9a-zA-Z]*:\\/\\/", QRegularExpression::CaseInsensitiveOption))) {
url.prepend("https://");
}
RadioChannel channel;
channel.source = source_;
channel.name = name + " - " + label;
channel.url.setUrl(url);
channels << channel;
}
}
app_->task_manager()->SetTaskFinished(task_id);
emit NewChannels(channels);
}

View File

@ -1,6 +1,6 @@
/*
* Strawberry Music Player
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2021-2024, 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
@ -24,9 +24,11 @@
#include <QUrl>
#include "radioservice.h"
#include "radiochannel.h"
class Application;
class NetworkAccessManager;
class QNetworkReply;
class RadioParadiseService : public RadioService {
Q_OBJECT
@ -37,8 +39,18 @@ class RadioParadiseService : public RadioService {
QUrl Homepage() override;
QUrl Donate() override;
void Abort();
public slots:
void GetChannels() override;
private slots:
void GetChannelsReply(QNetworkReply *reply, const int task_id);
private:
static const char *kApiChannelsUrl;
QList<QNetworkReply*> replies_;
RadioChannelList channels_;
};
#endif // RADIOPARADISESERVICE_H

View File

@ -50,14 +50,14 @@ bool ScrobblerService::ExtractJsonObj(const QByteArray &data, QJsonObject &json_
}
QString ScrobblerService::StripAlbum(QString album) const {
QString ScrobblerService::StripAlbum(const QString &album) const {
return album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc);
return Song::AlbumRemoveDisc(album);
}
QString ScrobblerService::StripTitle(QString title) const {
QString ScrobblerService::StripTitle(const QString &title) const {
return title.remove(Song::kTitleRemoveMisc);
return Song::TitleRemoveMisc(title);
}

View File

@ -61,8 +61,8 @@ class ScrobblerService : public QObject {
bool ExtractJsonObj(const QByteArray &data, QJsonObject &json_obj, QString &error_description);
QString StripAlbum(QString album) const;
QString StripTitle(QString title) const;
QString StripAlbum(const QString &album) const;
QString StripTitle(const QString &title) const;
public slots:
virtual void Submit() = 0;

View File

@ -174,13 +174,10 @@ ScrobblingAPI20::ReplyResult ScrobblingAPI20::GetJsonObject(QNetworkReply *reply
reply_error_type = ReplyResult::APIError;
}
const ScrobbleErrorCode lastfm_error_code = static_cast<ScrobbleErrorCode>(error_code);
if (reply->error() == QNetworkReply::ContentAccessDenied ||
reply->error() == QNetworkReply::ContentOperationNotPermittedError ||
reply->error() == QNetworkReply::AuthenticationRequiredError ||
if (reply->error() == QNetworkReply::AuthenticationRequiredError ||
lastfm_error_code == ScrobbleErrorCode::InvalidSessionKey ||
lastfm_error_code == ScrobbleErrorCode::UnauthorizedToken ||
lastfm_error_code == ScrobbleErrorCode::LoginRequired ||
lastfm_error_code == ScrobbleErrorCode::AuthenticationFailed ||
lastfm_error_code == ScrobbleErrorCode::APIKeySuspended
) {
// Session is probably expired

View File

@ -96,6 +96,12 @@ BackendSettingsPage::BackendSettingsPage(SettingsDialog *dialog, QWidget *parent
QObject::connect(ui_->checkbox_channels, &QCheckBox::toggled, ui_->widget_channels, &QSpinBox::setEnabled);
QObject::connect(ui_->button_buffer_defaults, &QPushButton::clicked, this, &BackendSettingsPage::BufferDefaults);
#ifdef Q_OS_WIN32
ui_->widget_exclusive_mode->show();
#else
ui_->widget_exclusive_mode->hide();
#endif
}
BackendSettingsPage::~BackendSettingsPage() {
@ -149,6 +155,10 @@ void BackendSettingsPage::Load() {
ui_->widget_alsa_plugin->hide();
#endif
#ifdef Q_OS_WIN32
ui_->checkbox_exclusive_mode->setChecked(s.value("exclusive_mode", false).toBool());
#endif
if (EngineInitialized()) Load_Engine(enginetype);
ui_->checkbox_volume_control->setChecked(s.value("volume_control", true).toBool());
@ -331,6 +341,10 @@ void BackendSettingsPage::Load_Output(QString output, QVariant device) {
ui_->groupbox_ebur128->setEnabled(false);
}
#ifdef Q_OS_WIN32
ui_->widget_exclusive_mode->setEnabled(engine()->ExclusiveModeSupport(output));
#endif
if (ui_->combobox_output->count() >= 1) Load_Device(output, device);
FadingOptionsChanged();
@ -481,6 +495,10 @@ void BackendSettingsPage::Save() {
else s.remove("alsaplugin");
#endif
#ifdef Q_OS_WIN32
s.setValue("exclusive_mode", ui_->checkbox_exclusive_mode->isChecked());
#endif
s.setValue("volume_control", ui_->checkbox_volume_control->isChecked());
s.setValue("channels_enabled", ui_->checkbox_channels->isChecked());
@ -550,6 +568,11 @@ void BackendSettingsPage::OutputChanged(const int index) {
if (!configloaded_ || !EngineInitialized()) return;
EngineBase::OutputDetails output = ui_->combobox_output->itemData(index).value<EngineBase::OutputDetails>();
#ifdef Q_OS_WIN32
ui_->widget_exclusive_mode->setEnabled(engine()->ExclusiveModeSupport(output.name));
#endif
Load_Device(output.name, QVariant());
}

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>717</width>
<height>1245</height>
<height>1259</height>
</rect>
</property>
<property name="windowTitle">
@ -153,6 +153,47 @@
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_exclusive_mode" native="true">
<layout class="QHBoxLayout" name="layout_exclusive_mode">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="checkbox_exclusive_mode">
<property name="text">
<string>Exclusive mode (Experimental)</string>
</property>
</widget>
</item>
<item>
<spacer name="spacer_exclusive_mode">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -179,6 +179,7 @@ void CollectionSettingsPage::Load() {
ui_->auto_open->setChecked(s.value("auto_open", true).toBool());
ui_->pretty_covers->setChecked(s.value("pretty_covers", true).toBool());
ui_->show_dividers->setChecked(s.value("show_dividers", true).toBool());
ui_->sort_skips_articles->setChecked(s.value("sort_skips_articles", true).toBool());
ui_->startup_scan->setChecked(s.value("startup_scan", true).toBool());
ui_->monitor->setChecked(s.value("monitor", true).toBool());
ui_->song_tracking->setChecked(s.value("song_tracking", false).toBool());
@ -226,6 +227,7 @@ void CollectionSettingsPage::Save() {
s.setValue("auto_open", ui_->auto_open->isChecked());
s.setValue("pretty_covers", ui_->pretty_covers->isChecked());
s.setValue("show_dividers", ui_->show_dividers->isChecked());
s.setValue("sort_skips_articles", ui_->sort_skips_articles->isChecked());
s.setValue("startup_scan", ui_->startup_scan->isChecked());
s.setValue("monitor", ui_->monitor->isChecked());
s.setValue("song_tracking", ui_->song_tracking->isChecked());

View File

@ -225,6 +225,13 @@ If there are no matches then it will use the largest image in the directory.</st
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="sort_skips_articles">
<property name="text">
<string>Skip leading articles (&quot;the&quot;, &quot;a&quot;, &quot;an&quot;) when sorting artist names</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -506,6 +513,7 @@ If there are no matches then it will use the largest image in the directory.</st
<tabstop>auto_open</tabstop>
<tabstop>pretty_covers</tabstop>
<tabstop>show_dividers</tabstop>
<tabstop>sort_skips_articles</tabstop>
<tabstop>spinbox_cache_size</tabstop>
<tabstop>combobox_cache_size</tabstop>
<tabstop>checkbox_disk_cache</tabstop>

View File

@ -74,6 +74,9 @@
</item>
<item row="0" column="2">
<widget class="QToolButton" name="context_exp_chooser1">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="text">
<string/>
</property>
@ -102,6 +105,9 @@
</item>
<item row="1" column="2">
<widget class="QToolButton" name="context_exp_chooser2">
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="text">
<string/>
</property>

View File

@ -236,6 +236,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="text">
<string/>
</property>
@ -267,6 +270,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="accessibleName">
<string>MenuPopupToolButton</string>
</property>
<property name="text">
<string/>
</property>

View File

@ -23,6 +23,7 @@
#include <QCheckBox>
#include <QComboBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QRadioButton>
#include <QSlider>
#include <QLineEdit>
@ -63,15 +64,26 @@ void SettingsPage::Init(QWidget *ui_widget) {
radiobuttons_ << qMakePair(radiobutton, radiobutton->isChecked());
}
else if (QComboBox *combobox = qobject_cast<QComboBox*>(w)) {
combobox->setFocusPolicy(Qt::StrongFocus);
combobox->installEventFilter(this);
comboboxes_ << qMakePair(combobox, combobox->currentText());
}
else if (QSpinBox *spinbox = qobject_cast<QSpinBox*>(w)) {
spinbox->setFocusPolicy(Qt::StrongFocus);
spinbox->installEventFilter(this);
spinboxes_ << qMakePair(spinbox, spinbox->value());
}
else if (QDoubleSpinBox *double_spinbox = qobject_cast<QDoubleSpinBox*>(w)) {
double_spinbox->setFocusPolicy(Qt::StrongFocus);
double_spinbox->installEventFilter(this);
double_spinboxes_ << qMakePair(double_spinbox, double_spinbox->value());
}
else if (QLineEdit *lineedit = qobject_cast<QLineEdit*>(w)) {
lineedits_ << qMakePair(lineedit, lineedit->text());
}
else if (QSlider *slider = qobject_cast<QSlider*>(w)) {
slider->setFocusPolicy(Qt::StrongFocus);
slider->installEventFilter(this);
sliders_ << qMakePair(slider, slider->value());
}
}
@ -114,6 +126,11 @@ void SettingsPage::Apply() {
changed_ = true;
qLog(Info) << spinbox.first->objectName() << "is changed for" << windowTitle() << "settings.";
}
for (QPair<QDoubleSpinBox*, int> &double_spinbox : double_spinboxes_) {
if (double_spinbox.first->value() == double_spinbox.second) continue;
changed_ = true;
qLog(Info) << double_spinbox.first->objectName() << "is changed for" << windowTitle() << "settings.";
}
for (QPair<QLineEdit*, QString> &lineedit : lineedits_) {
if (lineedit.first->text() == lineedit.second) continue;
changed_ = true;
@ -161,3 +178,32 @@ void SettingsPage::ComboBoxLoadFromSettingsByIndex(const QSettings &s, QComboBox
}
bool SettingsPage::eventFilter(QObject *obj, QEvent *e) {
if (e->type() == QEvent::Wheel) {
if (QComboBox *combobox = qobject_cast<QComboBox*>(obj)) {
if (!combobox->hasFocus()) {
return event(e);
}
}
else if (QSpinBox *spinbox = qobject_cast<QSpinBox*>(obj)) {
if (!spinbox->hasFocus()) {
return event(e);
}
}
else if (QDoubleSpinBox *double_spinbox = qobject_cast<QDoubleSpinBox*>(obj)) {
if (!double_spinbox->hasFocus()) {
return event(e);
}
}
else if (QSlider *slider = qobject_cast<QSlider*>(obj)) {
if (!slider->hasFocus()) {
return event(e);
}
}
}
return false;
}

View File

@ -39,6 +39,7 @@ class QCheckBox;
class QComboBox;
class QRadioButton;
class QSpinBox;
class QDoubleSpinBox;
class QSlider;
class QLineEdit;
class QShowEvent;
@ -75,6 +76,9 @@ class SettingsPage : public QWidget {
virtual void Save() = 0;
virtual void Cancel() {}
protected:
bool eventFilter(QObject *obj, QEvent *e) override;
signals:
void NotificationPreview(const OSDBase::Behaviour, const QString&, const QString&);
@ -86,6 +90,7 @@ class SettingsPage : public QWidget {
QList<QPair<QRadioButton*, bool>> radiobuttons_;
QList<QPair<QComboBox*, QString>> comboboxes_;
QList<QPair<QSpinBox*, int>> spinboxes_;
QList<QPair<QDoubleSpinBox*, int>> double_spinboxes_;
QList<QPair<QSlider*, int>> sliders_;
QList<QPair<QLineEdit*, QString>> lineedits_;
};

View File

@ -124,7 +124,7 @@
<item>
<widget class="QRadioButton" name="auth_method_md5">
<property name="text">
<string>MD5 token</string>
<string>MD5 token (Recommended)</string>
</property>
</widget>
</item>

View File

@ -1129,7 +1129,7 @@ void TidalRequest::ParseSong(Song &song, const QJsonObject &json_obj, const Arti
}
}
title.remove(Song::kTitleRemoveMisc);
title = Song::TitleRemoveMisc(title);
//qLog(Debug) << "id" << song_id << "track" << track << "disc" << disc << "title" << title << "album" << album << "album artist" << album_artist << "artist" << artist << cover << allow_streaming << url;

View File

@ -742,7 +742,7 @@ msgstr "Després de copiar…"
msgid "Albu&m cover"
msgstr ""
#: collection/savedgroupingmanager.cpp:98 playlist/playlist.cpp:1365
#: collection/savedgroupingmanager.cpp:98 playlist/playlist.cpp:1369
#: organize/organizedialog.cpp:105 ../build/src/ui_groupbydialog.h:194
#: ../build/src/ui_groupbydialog.h:216 ../build/src/ui_groupbydialog.h:238
#: ../build/src/ui_albumcoversearcher.h:108
@ -762,7 +762,7 @@ msgstr "Àlbum (volum ideal per a totes les peces)"
msgid "Album - Disc"
msgstr ""
#: playlist/playlist.cpp:1372
#: playlist/playlist.cpp:1376
msgid "Album Artist"
msgstr ""
@ -851,7 +851,7 @@ msgstr "Comença sempre la reproducció"
msgid "An error occurred loading the iTunes database"
msgstr "Sha produït un error en carregar la base de dades de liTunes"
#: playlist/playlist.cpp:426 dialogs/edittagdialog.cpp:1450
#: playlist/playlist.cpp:430 dialogs/edittagdialog.cpp:1450
#, qt-format
msgid "An error occurred writing metadata to '%1'"
msgstr "Sha produït un error en escriure les metadades a «%1»"
@ -880,7 +880,7 @@ msgstr ""
msgid "Append files/URLs to the playlist"
msgstr "Afegeix fitxers/URL a la llista de reproducció"
#: collection/collectionview.cpp:350
#: collection/collectionview.cpp:366
#: smartplaylists/smartplaylistsviewcontainer.cpp:65
#: widgets/fileviewlist.cpp:42 internet/internetsearchview.cpp:316
#: internet/internetcollectionview.cpp:315 radios/radioview.cpp:69
@ -931,7 +931,7 @@ msgstr ""
msgid "Art Unset"
msgstr ""
#: collection/savedgroupingmanager.cpp:95 playlist/playlist.cpp:1364
#: collection/savedgroupingmanager.cpp:95 playlist/playlist.cpp:1368
#: organize/organizedialog.cpp:106 ../build/src/ui_groupbydialog.h:192
#: ../build/src/ui_groupbydialog.h:214 ../build/src/ui_groupbydialog.h:236
#: ../build/src/ui_albumcoversearcher.h:104
@ -1093,7 +1093,7 @@ msgstr "Comportament"
msgid "Best"
msgstr "Millor"
#: playlist/playlist.cpp:1382
#: playlist/playlist.cpp:1386
msgid "Bit Depth"
msgstr ""
@ -1109,7 +1109,7 @@ msgid "Bit rate"
msgstr "Taxa de bits"
#: context/contextview.cpp:166 collection/savedgroupingmanager.cpp:149
#: playlist/playlist.cpp:1383 ../build/src/ui_groupbydialog.h:210
#: playlist/playlist.cpp:1387 ../build/src/ui_groupbydialog.h:210
#: ../build/src/ui_groupbydialog.h:232 ../build/src/ui_groupbydialog.h:254
#: ../build/src/ui_transcoderoptionsaac.h:132
#: ../build/src/ui_transcoderoptionsopus.h:76
@ -1180,7 +1180,7 @@ msgstr ""
msgid "CDDA"
msgstr "CDDA"
#: playlist/playlist.cpp:1396
#: playlist/playlist.cpp:1400
msgid "CUE"
msgstr ""
@ -1338,7 +1338,7 @@ msgstr "Cerca a la col·lecció"
msgid "Comma separated list of class:level, level is 0-3"
msgstr "Llista separada per comes de classe:nivell, el nivell és 0-3"
#: playlist/playlist.cpp:1392 smartplaylists/smartplaylistsearchterm.cpp:391
#: playlist/playlist.cpp:1396 smartplaylists/smartplaylistsearchterm.cpp:391
#: organize/organizedialog.cpp:117 ../build/src/ui_edittagdialog.h:918
msgid "Comment"
msgstr "Comentari"
@ -1355,7 +1355,7 @@ msgstr "Completa les etiquetes automàticament"
msgid "Complete tags automatically..."
msgstr "Completa les etiquetes automàticament…"
#: collection/savedgroupingmanager.cpp:128 playlist/playlist.cpp:1373
#: collection/savedgroupingmanager.cpp:128 playlist/playlist.cpp:1377
#: organize/organizedialog.cpp:109 ../build/src/ui_groupbydialog.h:204
#: ../build/src/ui_groupbydialog.h:226 ../build/src/ui_groupbydialog.h:248
#: ../build/src/ui_edittagdialog.h:915
@ -1436,7 +1436,7 @@ msgstr "Copia a la col·lecció…"
msgid "Copy to device"
msgstr ""
#: core/mainwindow.cpp:758 collection/collectionview.cpp:361
#: core/mainwindow.cpp:758 collection/collectionview.cpp:381
#: playlist/playlistlistcontainer.cpp:92 widgets/fileviewlist.cpp:48
msgid "Copy to device..."
msgstr "Copia al dispositiu…"
@ -1447,6 +1447,16 @@ msgid ""
"avoid losing configuration before you uninstall the snap:"
msgstr ""
#: device/gpoddevice.cpp:241
#, qt-format
msgid "Could not copy %1 to %2: %3"
msgstr ""
#: core/filesystemmusicstorage.cpp:98
#, qt-format
msgid "Could not copy file %1 to %2."
msgstr ""
#: transcoder/transcoder.cpp:72
#, qt-format
msgid ""
@ -1461,6 +1471,10 @@ msgstr ""
msgid "Could not open CUE file %1 for reading: %2"
msgstr ""
#: device/mtpconnection.cpp:75 device/mtpconnection.cpp:109
msgid "Could not open MTP device."
msgstr ""
#: scrobbler/scrobblingapi20.cpp:223
msgid "Could not open URL. Please open this URL in your browser"
msgstr ""
@ -1692,11 +1706,11 @@ msgstr "Dance"
msgid "Database corruption detected."
msgstr ""
#: playlist/playlist.cpp:1390
#: playlist/playlist.cpp:1394
msgid "Date Created"
msgstr ""
#: playlist/playlist.cpp:1389
#: playlist/playlist.cpp:1393
msgid "Date Modified"
msgstr ""
@ -1749,7 +1763,7 @@ msgstr "Suprimeix els fitxers"
msgid "Delete from device..."
msgstr "Suprimeix del dispositiu…"
#: core/mainwindow.cpp:760 collection/collectionview.cpp:363
#: core/mainwindow.cpp:760 collection/collectionview.cpp:383
#: widgets/fileviewlist.cpp:49
msgid "Delete from disk..."
msgstr "Suprimeix del disc…"
@ -1787,6 +1801,16 @@ msgstr "Treu de la cua la peça"
msgid "Destination"
msgstr "Destí"
#: core/filesystemmusicstorage.cpp:92
#, qt-format
msgid "Destination file %1 exists, but not allowed to overwrite"
msgstr ""
#: core/filesystemmusicstorage.cpp:73
#, qt-format
msgid "Destination file %1 exists, but not allowed to overwrite."
msgstr ""
#: ../build/src/ui_transcodedialog.h:235
msgid "Details..."
msgstr "Detalls…"
@ -1833,7 +1857,7 @@ msgctxt "Refers to a disabled notification type in Notification settings."
msgid "Disabled"
msgstr "Inhabilitat"
#: collection/savedgroupingmanager.cpp:116 playlist/playlist.cpp:1367
#: collection/savedgroupingmanager.cpp:116 playlist/playlist.cpp:1371
#: organize/organizedialog.cpp:113 ../build/src/ui_groupbydialog.h:196
#: ../build/src/ui_groupbydialog.h:218 ../build/src/ui_groupbydialog.h:240
#: ../build/src/ui_edittagdialog.h:908
@ -1877,7 +1901,7 @@ msgstr ""
msgid "Don't repeat"
msgstr "Sense repetició"
#: collection/collectionview.cpp:376
#: collection/collectionview.cpp:396
msgid "Don't show in various artists"
msgstr "No ho mostris a Artistes diversos"
@ -1974,12 +1998,12 @@ msgstr "Edita letiqueta…"
msgid "Edit track information"
msgstr "Edita la informació de la peça"
#: collection/collectionview.cpp:366 widgets/fileviewlist.cpp:52
#: collection/collectionview.cpp:386 widgets/fileviewlist.cpp:52
#: ../build/src/ui_mainwindow.h:642
msgid "Edit track information..."
msgstr "Edita la informació de la peça…"
#: collection/collectionview.cpp:367
#: collection/collectionview.cpp:387
msgid "Edit tracks information..."
msgstr "Edita la informació de les peces..."
@ -2130,7 +2154,7 @@ msgid "Equivalent to --log-levels *:3"
msgstr "Equivalent a --log-levels *:3"
#: core/mainwindow.cpp:2625 core/mainwindow.cpp:2776
#: collection/collectionview.cpp:587
#: collection/collectionview.cpp:701
msgid "Error"
msgstr "Error"
@ -2139,6 +2163,11 @@ msgstr "Error"
msgid "Error connecting MTP device %1"
msgstr ""
#: device/mtploader.cpp:77
#, qt-format
msgid "Error connecting MTP device %1: %2"
msgstr ""
#: organize/organizeerrordialog.cpp:71
msgid "Error copying songs"
msgstr "Sha produït un error en copiar les cançons"
@ -2265,6 +2294,11 @@ msgstr "Durada de lesvaïment"
msgid "Failed SQL query: %1"
msgstr ""
#: core/filesystemmusicstorage.cpp:57
#, qt-format
msgid "Failed to create directory %1."
msgstr ""
#: covermanager/albumcoverchoicecontroller.cpp:379
#: covermanager/albumcoverchoicecontroller.cpp:396
#, qt-format
@ -2341,19 +2375,19 @@ msgstr ""
msgid "File %1 is not recognized as a valid audio file."
msgstr ""
#: playlist/playlist.cpp:1385
#: playlist/playlist.cpp:1389
msgid "File Name"
msgstr ""
#: playlist/playlist.cpp:1386
#: playlist/playlist.cpp:1390
msgid "File Name (without path)"
msgstr ""
#: playlist/playlist.cpp:1387
#: playlist/playlist.cpp:1391
msgid "File Size"
msgstr ""
#: playlist/playlist.cpp:1388
#: playlist/playlist.cpp:1392
msgid "File Type"
msgstr ""
@ -2532,7 +2566,7 @@ msgstr "Configuració general"
msgid "Genius Authentication"
msgstr "Autenticació amb el Genius"
#: collection/savedgroupingmanager.cpp:125 playlist/playlist.cpp:1371
#: collection/savedgroupingmanager.cpp:125 playlist/playlist.cpp:1375
#: organize/organizedialog.cpp:116 ../build/src/ui_groupbydialog.h:198
#: ../build/src/ui_groupbydialog.h:220 ../build/src/ui_groupbydialog.h:242
#: ../build/src/ui_edittagdialog.h:917
@ -2640,7 +2674,7 @@ msgstr ""
msgid "Group by Genre/Artist/Album"
msgstr "Agrupa per gènere/artista/àlbum"
#: collection/savedgroupingmanager.cpp:134 playlist/playlist.cpp:1375
#: collection/savedgroupingmanager.cpp:134 playlist/playlist.cpp:1379
#: organize/organizedialog.cpp:111 ../build/src/ui_groupbydialog.h:206
#: ../build/src/ui_groupbydialog.h:228 ../build/src/ui_groupbydialog.h:250
#: ../build/src/ui_edittagdialog.h:909
@ -2813,7 +2847,7 @@ msgstr "Insereix…"
msgid "Install strawberry through PPA:"
msgstr ""
#: playlist/playlist.cpp:1398
#: playlist/playlist.cpp:1402
msgid "Integrated Loudness"
msgstr ""
@ -2833,6 +2867,11 @@ msgstr ""
msgid "Intro tracks"
msgstr "Peces dintroducció"
#: device/mtpconnection.cpp:57
#, qt-format
msgid "Invalid MTP device: %1"
msgstr ""
#: scrobbler/scrobblingapi20.cpp:257
msgid "Invalid reply from web browser. Missing token."
msgstr ""
@ -2892,7 +2931,7 @@ msgstr "Coberta dàlbum gran"
msgid "Large sidebar"
msgstr "Barra lateral gran"
#: playlist/playlist.cpp:1379
#: playlist/playlist.cpp:1383
msgid "Last Played"
msgstr ""
@ -2923,7 +2962,7 @@ msgstr ""
msgid "Left"
msgstr "Esquerra"
#: context/contextview.cpp:163 playlist/playlist.cpp:1368
#: context/contextview.cpp:163 playlist/playlist.cpp:1372
#: organize/organizedialog.cpp:118 ../build/src/ui_edittagdialog.h:888
msgid "Length"
msgstr "Durada"
@ -3030,7 +3069,7 @@ msgstr "Entra"
msgid "Long term prediction profile (LTP)"
msgstr "Perfil de predicció a llarg termini (LTP)"
#: playlist/playlist.cpp:1399
#: playlist/playlist.cpp:1403
msgid "Loudness Range"
msgstr ""
@ -3065,13 +3104,22 @@ msgid "Lyrics providers"
msgstr ""
#: ../build/src/ui_subsonicsettingspage.h:251
msgid "MD5 token"
msgid "MD5 token (Recommended)"
msgstr ""
#: ../build/src/ui_transcodersettingspage.h:194
msgid "MP3"
msgstr "MP3"
#: device/mtpconnection.cpp:100
msgid "MTP device not found."
msgstr ""
#: device/mtpconnection.cpp:86
#, qt-format
msgid "MTP error: %1"
msgstr ""
#: ../build/src/ui_transcoderoptionsaac.h:135
msgid "Main profile (MAIN)"
msgstr "Perfil principal (MAIN)"
@ -3223,7 +3271,7 @@ msgstr "Monitoritza els canvis a la col·lecció"
msgid "Months"
msgstr "Mesos"
#: playlist/playlist.cpp:1394
#: playlist/playlist.cpp:1398
msgid "Mood"
msgstr ""
@ -3401,7 +3449,7 @@ msgid "None"
msgstr "Cap"
#: core/mainwindow.cpp:2625 core/mainwindow.cpp:2776
#: collection/collectionview.cpp:587
#: collection/collectionview.cpp:701
msgid "None of the selected songs were suitable for copying to a device"
msgstr ""
"Cap de les cançons seleccionades són adequades per copiar-les a un "
@ -3502,7 +3550,7 @@ msgstr "Obrir dispositiu"
msgid "Open homepage"
msgstr ""
#: collection/collectionview.cpp:352
#: collection/collectionview.cpp:368
#: smartplaylists/smartplaylistsviewcontainer.cpp:67
#: widgets/fileviewlist.cpp:44 internet/internetsearchview.cpp:318
#: internet/internetcollectionview.cpp:317 radios/radioview.cpp:77
@ -3540,7 +3588,7 @@ msgstr "Opus"
msgid "Organize Files"
msgstr ""
#: core/mainwindow.cpp:754 collection/collectionview.cpp:359
#: core/mainwindow.cpp:754 collection/collectionview.cpp:379
msgid "Organize files..."
msgstr ""
@ -3548,7 +3596,7 @@ msgstr ""
msgid "Organizing files"
msgstr ""
#: playlist/playlist.cpp:1370
#: playlist/playlist.cpp:1374
msgid "Original Year"
msgstr ""
@ -3655,7 +3703,7 @@ msgstr ""
msgid "Perform track loudness normalization"
msgstr ""
#: collection/savedgroupingmanager.cpp:131 playlist/playlist.cpp:1374
#: collection/savedgroupingmanager.cpp:131 playlist/playlist.cpp:1378
#: organize/organizedialog.cpp:110 ../build/src/ui_groupbydialog.h:205
#: ../build/src/ui_groupbydialog.h:227 ../build/src/ui_groupbydialog.h:249
#: ../build/src/ui_edittagdialog.h:919
@ -3676,7 +3724,7 @@ msgstr "Barra lateral senzilla"
msgid "Play"
msgstr "Reprodueix"
#: playlist/playlist.cpp:1377
#: playlist/playlist.cpp:1381
msgid "Play Count"
msgstr ""
@ -3936,12 +3984,12 @@ msgstr "Afegeix les peces seleccionades a la cua"
msgid "Queue selected tracks to play next"
msgstr ""
#: core/mainwindow.cpp:1961 collection/collectionview.cpp:356
#: core/mainwindow.cpp:1961 collection/collectionview.cpp:372
#: internet/internetcollectionview.cpp:321
msgid "Queue to play next"
msgstr ""
#: core/mainwindow.cpp:1953 collection/collectionview.cpp:355
#: core/mainwindow.cpp:1953 collection/collectionview.cpp:371
#: smartplaylists/smartplaylistsviewcontainer.cpp:70
#: internet/internetsearchview.cpp:321 internet/internetcollectionview.cpp:320
msgid "Queue track"
@ -3967,7 +4015,7 @@ msgstr ""
msgid "Random"
msgstr ""
#: playlist/playlist.cpp:1395 ../build/src/ui_edittagdialog.h:922
#: playlist/playlist.cpp:1399 ../build/src/ui_edittagdialog.h:922
msgid "Rating"
msgstr ""
@ -4167,7 +4215,7 @@ msgstr "Repeteix la llista de reproducció"
msgid "Repeat track"
msgstr "Repeteix la peça"
#: collection/collectionview.cpp:351
#: collection/collectionview.cpp:367
#: smartplaylists/smartplaylistsviewcontainer.cpp:66
#: widgets/fileviewlist.cpp:43 internet/internetsearchview.cpp:317
#: internet/internetcollectionview.cpp:316 radios/radioview.cpp:73
@ -4203,7 +4251,7 @@ msgstr ""
msgid "Repopulate"
msgstr ""
#: collection/collectionview.cpp:372
#: collection/collectionview.cpp:392
msgid "Rescan song(s)"
msgstr ""
@ -4306,7 +4354,7 @@ msgstr "Treure el dispositiu amb seguretat"
msgid "Safely remove the device after copying"
msgstr "Treure el dispositiu amb seguretat després de copiar"
#: playlist/playlist.cpp:1381
#: playlist/playlist.cpp:1385
msgid "Sample Rate"
msgstr ""
@ -4440,7 +4488,7 @@ msgstr ""
msgid "Search for album covers..."
msgstr "Cerca cobertes dels àlbums…"
#: internet/internetsearchview.cpp:339
#: collection/collectionview.cpp:376 internet/internetsearchview.cpp:339
msgid "Search for this"
msgstr "Cerca-ho"
@ -4672,12 +4720,12 @@ msgstr "Mostra a la col·lecció…"
msgid "Show in file browser"
msgstr ""
#: core/mainwindow.cpp:753 collection/collectionview.cpp:368
#: core/mainwindow.cpp:753 collection/collectionview.cpp:388
#: widgets/fileviewlist.cpp:53
msgid "Show in file browser..."
msgstr "Mostra al gestor de fitxers"
#: collection/collectionview.cpp:375
#: collection/collectionview.cpp:395
msgid "Show in various artists"
msgstr "Mostra en Artistes diversos"
@ -4778,7 +4826,7 @@ msgstr "Mida:"
msgid "Ska"
msgstr "Ska"
#: playlist/playlist.cpp:1378
#: playlist/playlist.cpp:1382
msgid "Skip Count"
msgstr ""
@ -4874,7 +4922,7 @@ msgstr ""
msgid "Sorting"
msgstr ""
#: playlist/playlist.cpp:1393
#: playlist/playlist.cpp:1397
msgid "Source"
msgstr "Font"
@ -5184,7 +5232,7 @@ msgstr ""
"La versió de Strawberry a la que us acabeu dactualitzar necessita tornar a "
"analitzar tota la col·lecció perquè incorpora les següents funcions noves:"
#: collection/collectionview.cpp:483
#: collection/collectionview.cpp:503
msgid "There are other songs in this album"
msgstr "Hi ha altres cançons en aquest àlbum"
@ -5300,7 +5348,7 @@ msgstr ""
msgid "Time step"
msgstr "Salt en el temps"
#: playlist/playlist.cpp:1363 organize/organizedialog.cpp:104
#: playlist/playlist.cpp:1367 organize/organizedialog.cpp:104
#: ../build/src/ui_contextsettingspage.h:422
#: ../build/src/ui_edittagdialog.h:913
#: ../build/src/ui_trackselectiondialog.h:210
@ -5351,7 +5399,7 @@ msgstr "Bytes totals transferits"
msgid "Total network requests made"
msgstr "Total de sol·licituds de xarxa fetes"
#: playlist/playlist.cpp:1366 organize/organizedialog.cpp:112
#: playlist/playlist.cpp:1370 organize/organizedialog.cpp:112
#: ../build/src/ui_edittagdialog.h:921
#: ../build/src/ui_trackselectiondialog.h:212
msgid "Track"
@ -5697,7 +5745,7 @@ msgstr "Sense coberta:"
msgid "Work in offline mode (Only cache scrobbles)"
msgstr ""
#: collection/collectionview.cpp:483
#: collection/collectionview.cpp:503
msgid ""
"Would you like to move the other songs on this album to Various Artists as "
"well?"
@ -5715,7 +5763,16 @@ msgstr ""
msgid "Write metadata when saving playlists"
msgstr ""
#: collection/savedgroupingmanager.cpp:119 playlist/playlist.cpp:1369
#: device/gpoddevice.cpp:288
msgid "Writing database failed."
msgstr ""
#: device/gpoddevice.cpp:284
#, qt-format
msgid "Writing database failed: %1"
msgstr ""
#: collection/savedgroupingmanager.cpp:119 playlist/playlist.cpp:1373
#: organize/organizedialog.cpp:114 ../build/src/ui_groupbydialog.h:199
#: ../build/src/ui_groupbydialog.h:221 ../build/src/ui_groupbydialog.h:243
#: ../build/src/ui_edittagdialog.h:912

View File

@ -748,7 +748,7 @@ msgstr "Po zkopírování..."
msgid "Albu&m cover"
msgstr "Obal alb&a"
#: collection/savedgroupingmanager.cpp:98 playlist/playlist.cpp:1365
#: collection/savedgroupingmanager.cpp:98 playlist/playlist.cpp:1369
#: organize/organizedialog.cpp:105 ../build/src/ui_groupbydialog.h:194
#: ../build/src/ui_groupbydialog.h:216 ../build/src/ui_groupbydialog.h:238
#: ../build/src/ui_albumcoversearcher.h:108
@ -768,7 +768,7 @@ msgstr "Album (ideální hlasitost pro všechny skladby)"
msgid "Album - Disc"
msgstr "Album - Disk"
#: playlist/playlist.cpp:1372
#: playlist/playlist.cpp:1376
msgid "Album Artist"
msgstr ""
@ -857,7 +857,7 @@ msgstr "Vždy začít přehrávat"
msgid "An error occurred loading the iTunes database"
msgstr "Při nahrávání databáze iTunes nastala chyba"
#: playlist/playlist.cpp:426 dialogs/edittagdialog.cpp:1450
#: playlist/playlist.cpp:430 dialogs/edittagdialog.cpp:1450
#, qt-format
msgid "An error occurred writing metadata to '%1'"
msgstr "Při zápisu údajů do '%1' se vyskytla chyba"
@ -886,7 +886,7 @@ msgstr ""
msgid "Append files/URLs to the playlist"
msgstr "Přidat soubory/adresy do seznamu skladeb"
#: collection/collectionview.cpp:350
#: collection/collectionview.cpp:366
#: smartplaylists/smartplaylistsviewcontainer.cpp:65
#: widgets/fileviewlist.cpp:42 internet/internetsearchview.cpp:316
#: internet/internetcollectionview.cpp:315 radios/radioview.cpp:69
@ -937,7 +937,7 @@ msgstr ""
msgid "Art Unset"
msgstr ""
#: collection/savedgroupingmanager.cpp:95 playlist/playlist.cpp:1364
#: collection/savedgroupingmanager.cpp:95 playlist/playlist.cpp:1368
#: organize/organizedialog.cpp:106 ../build/src/ui_groupbydialog.h:192
#: ../build/src/ui_groupbydialog.h:214 ../build/src/ui_groupbydialog.h:236
#: ../build/src/ui_albumcoversearcher.h:104
@ -1098,7 +1098,7 @@ msgstr "Chování"
msgid "Best"
msgstr "Nejlepší"
#: playlist/playlist.cpp:1382
#: playlist/playlist.cpp:1386
msgid "Bit Depth"
msgstr ""
@ -1114,7 +1114,7 @@ msgid "Bit rate"
msgstr "Datový tok"
#: context/contextview.cpp:166 collection/savedgroupingmanager.cpp:149
#: playlist/playlist.cpp:1383 ../build/src/ui_groupbydialog.h:210
#: playlist/playlist.cpp:1387 ../build/src/ui_groupbydialog.h:210
#: ../build/src/ui_groupbydialog.h:232 ../build/src/ui_groupbydialog.h:254
#: ../build/src/ui_transcoderoptionsaac.h:132
#: ../build/src/ui_transcoderoptionsopus.h:76
@ -1186,7 +1186,7 @@ msgstr "Přehrávání disků CD je možné pouze za použití GStreamer enginu.
msgid "CDDA"
msgstr "CDDA"
#: playlist/playlist.cpp:1396
#: playlist/playlist.cpp:1400
msgid "CUE"
msgstr ""
@ -1342,7 +1342,7 @@ msgstr "Hledání v kolekci"
msgid "Comma separated list of class:level, level is 0-3"
msgstr "Čárkou oddělený seznam class:level, level je 0-3"
#: playlist/playlist.cpp:1392 smartplaylists/smartplaylistsearchterm.cpp:391
#: playlist/playlist.cpp:1396 smartplaylists/smartplaylistsearchterm.cpp:391
#: organize/organizedialog.cpp:117 ../build/src/ui_edittagdialog.h:918
msgid "Comment"
msgstr "Poznámka"
@ -1359,7 +1359,7 @@ msgstr "Doplnit značky automaticky"
msgid "Complete tags automatically..."
msgstr "Doplnit značky automaticky..."
#: collection/savedgroupingmanager.cpp:128 playlist/playlist.cpp:1373
#: collection/savedgroupingmanager.cpp:128 playlist/playlist.cpp:1377
#: organize/organizedialog.cpp:109 ../build/src/ui_groupbydialog.h:204
#: ../build/src/ui_groupbydialog.h:226 ../build/src/ui_groupbydialog.h:248
#: ../build/src/ui_edittagdialog.h:915
@ -1440,7 +1440,7 @@ msgstr "Zkopírovat do sbírky..."
msgid "Copy to device"
msgstr "Zkopírovat na zařízení"
#: core/mainwindow.cpp:758 collection/collectionview.cpp:361
#: core/mainwindow.cpp:758 collection/collectionview.cpp:381
#: playlist/playlistlistcontainer.cpp:92 widgets/fileviewlist.cpp:48
msgid "Copy to device..."
msgstr "Zkopírovat do zařízení..."
@ -1451,6 +1451,16 @@ msgid ""
"avoid losing configuration before you uninstall the snap:"
msgstr ""
#: device/gpoddevice.cpp:241
#, qt-format
msgid "Could not copy %1 to %2: %3"
msgstr ""
#: core/filesystemmusicstorage.cpp:98
#, qt-format
msgid "Could not copy file %1 to %2."
msgstr ""
#: transcoder/transcoder.cpp:72
#, qt-format
msgid ""
@ -1465,6 +1475,10 @@ msgstr ""
msgid "Could not open CUE file %1 for reading: %2"
msgstr ""
#: device/mtpconnection.cpp:75 device/mtpconnection.cpp:109
msgid "Could not open MTP device."
msgstr ""
#: scrobbler/scrobblingapi20.cpp:223
msgid "Could not open URL. Please open this URL in your browser"
msgstr ""
@ -1697,11 +1711,11 @@ msgstr "Taneční hudba"
msgid "Database corruption detected."
msgstr "Databáze je poškozená."
#: playlist/playlist.cpp:1390
#: playlist/playlist.cpp:1394
msgid "Date Created"
msgstr ""
#: playlist/playlist.cpp:1389
#: playlist/playlist.cpp:1393
msgid "Date Modified"
msgstr ""
@ -1754,7 +1768,7 @@ msgstr "Smazat soubory"
msgid "Delete from device..."
msgstr "Smazat ze zařízení..."
#: core/mainwindow.cpp:760 collection/collectionview.cpp:363
#: core/mainwindow.cpp:760 collection/collectionview.cpp:383
#: widgets/fileviewlist.cpp:49
msgid "Delete from disk..."
msgstr "Smazat z disku..."
@ -1792,6 +1806,16 @@ msgstr "Odstranit skladbu z řady"
msgid "Destination"
msgstr "Cíl"
#: core/filesystemmusicstorage.cpp:92
#, qt-format
msgid "Destination file %1 exists, but not allowed to overwrite"
msgstr ""
#: core/filesystemmusicstorage.cpp:73
#, qt-format
msgid "Destination file %1 exists, but not allowed to overwrite."
msgstr ""
#: ../build/src/ui_transcodedialog.h:235
msgid "Details..."
msgstr "Podrobnosti..."
@ -1838,7 +1862,7 @@ msgctxt "Refers to a disabled notification type in Notification settings."
msgid "Disabled"
msgstr "Zakázáno"
#: collection/savedgroupingmanager.cpp:116 playlist/playlist.cpp:1367
#: collection/savedgroupingmanager.cpp:116 playlist/playlist.cpp:1371
#: organize/organizedialog.cpp:113 ../build/src/ui_groupbydialog.h:196
#: ../build/src/ui_groupbydialog.h:218 ../build/src/ui_groupbydialog.h:240
#: ../build/src/ui_edittagdialog.h:908
@ -1882,7 +1906,7 @@ msgstr "Tuto zprávu již nezobrazovat."
msgid "Don't repeat"
msgstr "Neopakovat"
#: collection/collectionview.cpp:376
#: collection/collectionview.cpp:396
msgid "Don't show in various artists"
msgstr "Nezobrazovat pod různými umělci"
@ -1979,12 +2003,12 @@ msgstr "Upravit značku..."
msgid "Edit track information"
msgstr "Upravit informace o skladbě"
#: collection/collectionview.cpp:366 widgets/fileviewlist.cpp:52
#: collection/collectionview.cpp:386 widgets/fileviewlist.cpp:52
#: ../build/src/ui_mainwindow.h:642
msgid "Edit track information..."
msgstr "Upravit informace o skladbě..."
#: collection/collectionview.cpp:367
#: collection/collectionview.cpp:387
msgid "Edit tracks information..."
msgstr "Upravit informace o skladbách..."
@ -2134,7 +2158,7 @@ msgid "Equivalent to --log-levels *:3"
msgstr "Rovnocenné s --log-levels *:3"
#: core/mainwindow.cpp:2625 core/mainwindow.cpp:2776
#: collection/collectionview.cpp:587
#: collection/collectionview.cpp:701
msgid "Error"
msgstr "Chyba"
@ -2143,6 +2167,11 @@ msgstr "Chyba"
msgid "Error connecting MTP device %1"
msgstr "Chyba při připojování k MTP zařízení %1"
#: device/mtploader.cpp:77
#, qt-format
msgid "Error connecting MTP device %1: %2"
msgstr ""
#: organize/organizeerrordialog.cpp:71
msgid "Error copying songs"
msgstr "Chyba při kopírování písní"
@ -2269,6 +2298,11 @@ msgstr "Doba slábnutí"
msgid "Failed SQL query: %1"
msgstr ""
#: core/filesystemmusicstorage.cpp:57
#, qt-format
msgid "Failed to create directory %1."
msgstr ""
#: covermanager/albumcoverchoicecontroller.cpp:379
#: covermanager/albumcoverchoicecontroller.cpp:396
#, qt-format
@ -2345,19 +2379,19 @@ msgstr ""
msgid "File %1 is not recognized as a valid audio file."
msgstr "Soubor %1 nebyl rozpoznán jako platný zvukový soubor."
#: playlist/playlist.cpp:1385
#: playlist/playlist.cpp:1389
msgid "File Name"
msgstr ""
#: playlist/playlist.cpp:1386
#: playlist/playlist.cpp:1390
msgid "File Name (without path)"
msgstr ""
#: playlist/playlist.cpp:1387
#: playlist/playlist.cpp:1391
msgid "File Size"
msgstr ""
#: playlist/playlist.cpp:1388
#: playlist/playlist.cpp:1392
msgid "File Type"
msgstr ""
@ -2538,7 +2572,7 @@ msgstr "Obecná nastavení"
msgid "Genius Authentication"
msgstr "Ověření Genius"
#: collection/savedgroupingmanager.cpp:125 playlist/playlist.cpp:1371
#: collection/savedgroupingmanager.cpp:125 playlist/playlist.cpp:1375
#: organize/organizedialog.cpp:116 ../build/src/ui_groupbydialog.h:198
#: ../build/src/ui_groupbydialog.h:220 ../build/src/ui_groupbydialog.h:242
#: ../build/src/ui_edittagdialog.h:917
@ -2646,7 +2680,7 @@ msgstr "Seskupit jako Žánr/Autor alba/Album"
msgid "Group by Genre/Artist/Album"
msgstr "Seskupovat podle žánru/umělce/alba"
#: collection/savedgroupingmanager.cpp:134 playlist/playlist.cpp:1375
#: collection/savedgroupingmanager.cpp:134 playlist/playlist.cpp:1379
#: organize/organizedialog.cpp:111 ../build/src/ui_groupbydialog.h:206
#: ../build/src/ui_groupbydialog.h:228 ../build/src/ui_groupbydialog.h:250
#: ../build/src/ui_edittagdialog.h:909
@ -2822,7 +2856,7 @@ msgstr "Vložit..."
msgid "Install strawberry through PPA:"
msgstr ""
#: playlist/playlist.cpp:1398
#: playlist/playlist.cpp:1402
msgid "Integrated Loudness"
msgstr ""
@ -2842,6 +2876,11 @@ msgstr "Zobrazení karet Internetu"
msgid "Intro tracks"
msgstr "Skladby úvodu"
#: device/mtpconnection.cpp:57
#, qt-format
msgid "Invalid MTP device: %1"
msgstr ""
#: scrobbler/scrobblingapi20.cpp:257
msgid "Invalid reply from web browser. Missing token."
msgstr "Špatná odpověď od prohlížeče. Token chybí."
@ -2902,7 +2941,7 @@ msgstr "Velký obal alba"
msgid "Large sidebar"
msgstr "Velký postranní panel"
#: playlist/playlist.cpp:1379
#: playlist/playlist.cpp:1383
msgid "Last Played"
msgstr ""
@ -2933,7 +2972,7 @@ msgstr "Nejméně oblíbené skladby"
msgid "Left"
msgstr "Vlevo"
#: context/contextview.cpp:163 playlist/playlist.cpp:1368
#: context/contextview.cpp:163 playlist/playlist.cpp:1372
#: organize/organizedialog.cpp:118 ../build/src/ui_edittagdialog.h:888
msgid "Length"
msgstr "Délka"
@ -3040,7 +3079,7 @@ msgstr "Přihlášení"
msgid "Long term prediction profile (LTP)"
msgstr "Dlouhodobý předpověďní profil"
#: playlist/playlist.cpp:1399
#: playlist/playlist.cpp:1403
msgid "Loudness Range"
msgstr ""
@ -3076,13 +3115,22 @@ msgid "Lyrics providers"
msgstr "Poskytovatelé textů"
#: ../build/src/ui_subsonicsettingspage.h:251
msgid "MD5 token"
msgid "MD5 token (Recommended)"
msgstr ""
#: ../build/src/ui_transcodersettingspage.h:194
msgid "MP3"
msgstr "MP3"
#: device/mtpconnection.cpp:100
msgid "MTP device not found."
msgstr ""
#: device/mtpconnection.cpp:86
#, qt-format
msgid "MTP error: %1"
msgstr ""
#: ../build/src/ui_transcoderoptionsaac.h:135
msgid "Main profile (MAIN)"
msgstr "Hlavní profil"
@ -3234,7 +3282,7 @@ msgstr "Sledovat změny ve sbírce"
msgid "Months"
msgstr "Měsíce"
#: playlist/playlist.cpp:1394
#: playlist/playlist.cpp:1398
msgid "Mood"
msgstr "Nálada"
@ -3412,7 +3460,7 @@ msgid "None"
msgstr "Žádná"
#: core/mainwindow.cpp:2625 core/mainwindow.cpp:2776
#: collection/collectionview.cpp:587
#: collection/collectionview.cpp:701
msgid "None of the selected songs were suitable for copying to a device"
msgstr "Žádná z vybraných písní nebyla vhodná ke zkopírování do zařízení"
@ -3514,7 +3562,7 @@ msgstr "Otevřít zařízení"
msgid "Open homepage"
msgstr ""
#: collection/collectionview.cpp:352
#: collection/collectionview.cpp:368
#: smartplaylists/smartplaylistsviewcontainer.cpp:67
#: widgets/fileviewlist.cpp:44 internet/internetsearchview.cpp:318
#: internet/internetcollectionview.cpp:317 radios/radioview.cpp:77
@ -3552,7 +3600,7 @@ msgstr "Opus"
msgid "Organize Files"
msgstr "Uspořádat Soubory"
#: core/mainwindow.cpp:754 collection/collectionview.cpp:359
#: core/mainwindow.cpp:754 collection/collectionview.cpp:379
msgid "Organize files..."
msgstr "Uspořádat soubory..."
@ -3560,7 +3608,7 @@ msgstr "Uspořádat soubory..."
msgid "Organizing files"
msgstr "Organizace souborů"
#: playlist/playlist.cpp:1370
#: playlist/playlist.cpp:1374
msgid "Original Year"
msgstr ""
@ -3667,7 +3715,7 @@ msgstr ""
msgid "Perform track loudness normalization"
msgstr ""
#: collection/savedgroupingmanager.cpp:131 playlist/playlist.cpp:1374
#: collection/savedgroupingmanager.cpp:131 playlist/playlist.cpp:1378
#: organize/organizedialog.cpp:110 ../build/src/ui_groupbydialog.h:205
#: ../build/src/ui_groupbydialog.h:227 ../build/src/ui_groupbydialog.h:249
#: ../build/src/ui_edittagdialog.h:919
@ -3688,7 +3736,7 @@ msgstr "Prostý postranní panel"
msgid "Play"
msgstr "Přehrát"
#: playlist/playlist.cpp:1377
#: playlist/playlist.cpp:1381
msgid "Play Count"
msgstr ""
@ -3950,12 +3998,12 @@ msgstr "Přidat vybrané skladby do řady"
msgid "Queue selected tracks to play next"
msgstr "Přidat vybrané skladby do fronty"
#: core/mainwindow.cpp:1961 collection/collectionview.cpp:356
#: core/mainwindow.cpp:1961 collection/collectionview.cpp:372
#: internet/internetcollectionview.cpp:321
msgid "Queue to play next"
msgstr "Do fronty jako další"
#: core/mainwindow.cpp:1953 collection/collectionview.cpp:355
#: core/mainwindow.cpp:1953 collection/collectionview.cpp:371
#: smartplaylists/smartplaylistsviewcontainer.cpp:70
#: internet/internetsearchview.cpp:321 internet/internetcollectionview.cpp:320
msgid "Queue track"
@ -3981,7 +4029,7 @@ msgstr ""
msgid "Random"
msgstr ""
#: playlist/playlist.cpp:1395 ../build/src/ui_edittagdialog.h:922
#: playlist/playlist.cpp:1399 ../build/src/ui_edittagdialog.h:922
msgid "Rating"
msgstr "Hodnocení"
@ -4181,7 +4229,7 @@ msgstr "Opakovat seznam skladeb"
msgid "Repeat track"
msgstr "Opakovat skladbu"
#: collection/collectionview.cpp:351
#: collection/collectionview.cpp:367
#: smartplaylists/smartplaylistsviewcontainer.cpp:66
#: widgets/fileviewlist.cpp:43 internet/internetsearchview.cpp:317
#: internet/internetcollectionview.cpp:316 radios/radioview.cpp:73
@ -4217,7 +4265,7 @@ msgstr "Odpověď od Tidal neobsahuje dotazové položky."
msgid "Repopulate"
msgstr ""
#: collection/collectionview.cpp:372
#: collection/collectionview.cpp:392
msgid "Rescan song(s)"
msgstr "Prohledat skladbu"
@ -4322,7 +4370,7 @@ msgstr "Bezpečně odebrat zařízení"
msgid "Safely remove the device after copying"
msgstr "Po dokončení kopírování bezpečně odebrat zařízení"
#: playlist/playlist.cpp:1381
#: playlist/playlist.cpp:1385
msgid "Sample Rate"
msgstr ""
@ -4456,7 +4504,7 @@ msgstr "Zpožděné vyhledávání"
msgid "Search for album covers..."
msgstr "Hledat obaly alb..."
#: internet/internetsearchview.cpp:339
#: collection/collectionview.cpp:376 internet/internetsearchview.cpp:339
msgid "Search for this"
msgstr "Hledat toto"
@ -4689,12 +4737,12 @@ msgstr "Ukazovat ve sbírce..."
msgid "Show in file browser"
msgstr "Zobrazit v průzkumníku souborů"
#: core/mainwindow.cpp:753 collection/collectionview.cpp:368
#: core/mainwindow.cpp:753 collection/collectionview.cpp:388
#: widgets/fileviewlist.cpp:53
msgid "Show in file browser..."
msgstr "Ukázat v prohlížeči souborů..."
#: collection/collectionview.cpp:375
#: collection/collectionview.cpp:395
msgid "Show in various artists"
msgstr "Ukázat pod různými umělci"
@ -4795,7 +4843,7 @@ msgstr "Velikost:"
msgid "Ska"
msgstr "Ska"
#: playlist/playlist.cpp:1378
#: playlist/playlist.cpp:1382
msgid "Skip Count"
msgstr ""
@ -4895,7 +4943,7 @@ msgstr ""
msgid "Sorting"
msgstr ""
#: playlist/playlist.cpp:1393
#: playlist/playlist.cpp:1397
msgid "Source"
msgstr "Zdroj"
@ -5206,7 +5254,7 @@ msgstr ""
"Verze Strawberry, na kterou jste právě povýšili, vyžaduje z důvodu nových "
"vlastností vypsaných níže úplné nové prohledání sbírky:"
#: collection/collectionview.cpp:483
#: collection/collectionview.cpp:503
msgid "There are other songs in this album"
msgstr "Na tomto albu jsou další písně"
@ -5317,7 +5365,7 @@ msgstr ""
msgid "Time step"
msgstr "Časový krok"
#: playlist/playlist.cpp:1363 organize/organizedialog.cpp:104
#: playlist/playlist.cpp:1367 organize/organizedialog.cpp:104
#: ../build/src/ui_contextsettingspage.h:422
#: ../build/src/ui_edittagdialog.h:913
#: ../build/src/ui_trackselectiondialog.h:210
@ -5368,7 +5416,7 @@ msgstr "Celkem přeneseno bajtů"
msgid "Total network requests made"
msgstr "Celkem uskutečněno síťových požadavků"
#: playlist/playlist.cpp:1366 organize/organizedialog.cpp:112
#: playlist/playlist.cpp:1370 organize/organizedialog.cpp:112
#: ../build/src/ui_edittagdialog.h:921
#: ../build/src/ui_trackselectiondialog.h:212
msgid "Track"
@ -5716,7 +5764,7 @@ msgstr "Bez obalu:"
msgid "Work in offline mode (Only cache scrobbles)"
msgstr "Pracovat v režimu offline (pouze ukládat přehrané skladby)"
#: collection/collectionview.cpp:483
#: collection/collectionview.cpp:503
msgid ""
"Would you like to move the other songs on this album to Various Artists as "
"well?"
@ -5734,7 +5782,16 @@ msgstr ""
msgid "Write metadata when saving playlists"
msgstr "Zapisovat metadata při ukládání seznamů skladeb"
#: collection/savedgroupingmanager.cpp:119 playlist/playlist.cpp:1369
#: device/gpoddevice.cpp:288
msgid "Writing database failed."
msgstr ""
#: device/gpoddevice.cpp:284
#, qt-format
msgid "Writing database failed: %1"
msgstr ""
#: collection/savedgroupingmanager.cpp:119 playlist/playlist.cpp:1373
#: organize/organizedialog.cpp:114 ../build/src/ui_groupbydialog.h:199
#: ../build/src/ui_groupbydialog.h:221 ../build/src/ui_groupbydialog.h:243
#: ../build/src/ui_edittagdialog.h:912

Some files were not shown because too many files have changed in this diff Show More