Merge branch 'master' of github.com:poldi171254/strawberry
This commit is contained in:
commit
992a16769a
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
|
30
Changelog
30
Changelog
|
@ -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:
|
||||
|
|
14
README.md
14
README.md
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}),
|
||||
|
|
|
@ -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_);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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() {}
|
||||
|
||||
|
|
|
@ -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 ®ex_list) {
|
||||
|
||||
for (const QRegularExpression ®ex : regex_list) {
|
||||
if (str.contains(regex)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
QString Song::StripRegexList(QString str, const RegularExpressionList ®ex_list) {
|
||||
|
||||
for (const QRegularExpression ®ex : 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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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 ®ex_list);
|
||||
static QString StripRegexList(QString str, const RegularExpressionList ®ex_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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"; }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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, ®_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;
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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_);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.";
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -54,6 +54,9 @@
|
|||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>MenuPopupToolButton</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QRegularExpression>
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QRegularExpression>
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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_;
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -18,8 +18,6 @@
|
|||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QRegularExpression>
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 ¤t = 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_);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 ("the", "a", "an") 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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 "S’ha produït un error en carregar la base de dades de l’iTunes"
|
||||
|
||||
#: 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 "S’ha 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 l’etiqueta…"
|
|||
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 "S’ha produït un error en copiar les cançons"
|
||||
|
@ -2265,6 +2294,11 @@ msgstr "Durada de l’esvaï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 d’introducció"
|
||||
|
||||
#: 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 d’actualitzar 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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue