Compare commits
384 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c87b56adcb | ||
|
|
8ddd309d5d | ||
|
|
8c8acbb546 | ||
|
|
fe30f27af3 | ||
|
|
d5d2eaba8a | ||
|
|
e8f64bfe8f | ||
|
|
79b03993b0 | ||
|
|
e3b8f9cb8c | ||
|
|
819463a865 | ||
|
|
c69777ca39 | ||
|
|
40f3e828aa | ||
|
|
dc8520ebec | ||
|
|
7ffbedf0dd | ||
|
|
826fad1ad4 | ||
|
|
1c4d3aebad | ||
|
|
ff6e93fc15 | ||
|
|
17e88bb97d | ||
|
|
d3dd26c596 | ||
|
|
c2a7509e0e | ||
|
|
079040b721 | ||
|
|
b80d239820 | ||
|
|
c3ff43dac2 | ||
|
|
4ac51afd8f | ||
|
|
26eab51698 | ||
|
|
388dcf2a1f | ||
|
|
9bfd14d067 | ||
|
|
061e0562d1 | ||
|
|
2ba20b013d | ||
|
|
dc36f87a6e | ||
|
|
e82ecb48b8 | ||
|
|
c56a134179 | ||
|
|
11028456ad | ||
|
|
81fcb02974 | ||
|
|
5ef4976c53 | ||
|
|
160356072f | ||
|
|
c10df5b634 | ||
|
|
70fdd5b0b3 | ||
|
|
6c1ec51fab | ||
|
|
66b1c22174 | ||
|
|
97138fd6b9 | ||
|
|
03c69be421 | ||
|
|
3a9a952cb0 | ||
|
|
3bd0331aa3 | ||
|
|
3ecf224d91 | ||
|
|
37743606a2 | ||
|
|
58587f4a9a | ||
|
|
464fde1851 | ||
|
|
2639498642 | ||
|
|
49f074737c | ||
|
|
3d53a8b434 | ||
|
|
e260433c7a | ||
|
|
88ef8bff0b | ||
|
|
fbdac36f6f | ||
|
|
da3876bd83 | ||
|
|
d303e700ae | ||
|
|
92a1173b9e | ||
|
|
9f7ebb1ac7 | ||
|
|
1a8690e1f2 | ||
|
|
6543e4c5da | ||
|
|
dd904fe3c2 | ||
|
|
c14cc6bf0b | ||
|
|
95c265ffd3 | ||
|
|
31c1ae68df | ||
|
|
f2eb0c3b6b | ||
|
|
32be33847c | ||
|
|
3100b0c044 | ||
|
|
f4ec3ab379 | ||
|
|
cdd7faa9bb | ||
|
|
e7b35aeaf7 | ||
|
|
696256eb5b | ||
|
|
8ad560ce0e | ||
|
|
1c71506f62 | ||
|
|
8bea6ec5b0 | ||
|
|
e8144487ee | ||
|
|
41d9d15dda | ||
|
|
124b97c024 | ||
|
|
98e0b45403 | ||
|
|
1f2b8d8bf6 | ||
|
|
8327751b91 | ||
|
|
6417f89596 | ||
|
|
2e53656f44 | ||
|
|
822cf0ad07 | ||
|
|
67f04a81b3 | ||
|
|
9232ad0125 | ||
|
|
0de87b3e1e | ||
|
|
74b8cd6156 | ||
|
|
ac959387fe | ||
|
|
ffd8ce9281 | ||
|
|
d8052b295f | ||
|
|
625929133c | ||
|
|
79c28e7e1d | ||
|
|
01f4a79f07 | ||
|
|
47c5a2215e | ||
|
|
bb6e38630f | ||
|
|
85acf69d4f | ||
|
|
69ba56ebef | ||
|
|
527a61f909 | ||
|
|
a15ebcde24 | ||
|
|
e80629cc40 | ||
|
|
d956ae9726 | ||
|
|
0435d6d7b9 | ||
|
|
59752e6fe9 | ||
|
|
d3ee0bfdf4 | ||
|
|
463df825f0 | ||
|
|
480559ef0b | ||
|
|
2a4fd346f9 | ||
|
|
6200fed224 | ||
|
|
e1b4585dc7 | ||
|
|
c5eb72fa9f | ||
|
|
d2e19ef4c3 | ||
|
|
4509c43b81 | ||
|
|
e2ffe716e7 | ||
|
|
0d9ededea9 | ||
|
|
2edc4369d2 | ||
|
|
32baa95500 | ||
|
|
ad9f3ce078 | ||
|
|
c1f66b1885 | ||
|
|
e0be15cf01 | ||
|
|
93660bfc81 | ||
|
|
6446942e73 | ||
|
|
0038cf8c4e | ||
|
|
7f177aef08 | ||
|
|
a7a42ea5ec | ||
|
|
14cddfd42f | ||
|
|
ae0ce65674 | ||
|
|
9c9926d5a7 | ||
|
|
95a3c41baa | ||
|
|
f2518baef9 | ||
|
|
4be9265546 | ||
|
|
9f9c46e370 | ||
|
|
5816d0bb12 | ||
|
|
70c2b99771 | ||
|
|
6177d4a2c4 | ||
|
|
05f012e590 | ||
|
|
cc0506490f | ||
|
|
06114c9835 | ||
|
|
2518e4d47d | ||
|
|
ceea805196 | ||
|
|
ae7e515945 | ||
|
|
b275f91a58 | ||
|
|
b8ef96028c | ||
|
|
6ba1fdb744 | ||
|
|
dcef38427b | ||
|
|
20d7ae7144 | ||
|
|
d576777d94 | ||
|
|
1f7344ca1b | ||
|
|
87c69f7456 | ||
|
|
a684b35203 | ||
|
|
37855fe836 | ||
|
|
f596695f61 | ||
|
|
076d065f7c | ||
|
|
70a7a7bbdd | ||
|
|
5f540a4c08 | ||
|
|
f33b30fe79 | ||
|
|
2f546f214d | ||
|
|
7ba4fda346 | ||
|
|
299415a889 | ||
|
|
718af984ab | ||
|
|
5d51657f32 | ||
|
|
a2958ba808 | ||
|
|
79c2130152 | ||
|
|
98d3cc2637 | ||
|
|
8339aa0934 | ||
|
|
5451c110b1 | ||
|
|
20595a11bc | ||
|
|
c92a1b516c | ||
|
|
a8f1a881ff | ||
|
|
ec21a55271 | ||
|
|
89990624ec | ||
|
|
6caf7f356b | ||
|
|
241a6c5818 | ||
|
|
57fb52e8f0 | ||
|
|
7b00385155 | ||
|
|
2b4aa1d6b2 | ||
|
|
4ba5113842 | ||
|
|
a36bf2df65 | ||
|
|
f5002cae36 | ||
|
|
cb8022c55d | ||
|
|
2a65e00988 | ||
|
|
05358cdfe4 | ||
|
|
7b43a94055 | ||
|
|
36e19e82e7 | ||
|
|
c52a802b83 | ||
|
|
b233600b8c | ||
|
|
93df859aa4 | ||
|
|
f1f79fb961 | ||
|
|
92fa75b6c2 | ||
|
|
1d5f3a0486 | ||
|
|
b89c200076 | ||
|
|
597a8cd6c8 | ||
|
|
e477449cd4 | ||
|
|
ea1e4541c0 | ||
|
|
50572ac40f | ||
|
|
fd81036909 | ||
|
|
0e27886e28 | ||
|
|
45bad3be04 | ||
|
|
30b268dc3a | ||
|
|
ef99f0ef36 | ||
|
|
e357ba0125 | ||
|
|
36b75a5928 | ||
|
|
64d3ea2804 | ||
|
|
0a93affeef | ||
|
|
402d13a322 | ||
|
|
adf0efc859 | ||
|
|
d1c65fd273 | ||
|
|
8a27c6a52f | ||
|
|
d7cc52bc99 | ||
|
|
f0f5300891 | ||
|
|
6e90e72b4a | ||
|
|
c655963483 | ||
|
|
9f2e4ac312 | ||
|
|
9e25366f85 | ||
|
|
c102d8731a | ||
|
|
0983ba1339 | ||
|
|
0a99eca7cd | ||
|
|
116bbec73e | ||
|
|
bf19540f8d | ||
|
|
dff3ae7410 | ||
|
|
76614bcde0 | ||
|
|
2953f9eefc | ||
|
|
4a24605361 | ||
|
|
decabe8d47 | ||
|
|
51adcf0f1e | ||
|
|
2a6a07fef6 | ||
|
|
315cf63118 | ||
|
|
e28d362aad | ||
|
|
e0d9b8f715 | ||
|
|
eff6b75c43 | ||
|
|
e8be0adf37 | ||
|
|
9f4a82bb62 | ||
|
|
8d7e14f21d | ||
|
|
d03d3622aa | ||
|
|
821c32992d | ||
|
|
b52cf9f3cd | ||
|
|
ab8e687f96 | ||
|
|
90703703aa | ||
|
|
176984afe0 | ||
|
|
542efa17ff | ||
|
|
6a2c2dbba1 | ||
|
|
426de61525 | ||
|
|
24c8d06d41 | ||
|
|
227f5e5176 | ||
|
|
92e39a3e21 | ||
|
|
fb2300e2fa | ||
|
|
78becae5e9 | ||
|
|
d10eb4370e | ||
|
|
9c92ef941f | ||
|
|
7aefe3d71b | ||
|
|
398db964b8 | ||
|
|
579349b104 | ||
|
|
5f9a83871d | ||
|
|
da0c5e67c5 | ||
|
|
a0cfde18c3 | ||
|
|
0ad4889936 | ||
|
|
36e809d530 | ||
|
|
78096658e2 | ||
|
|
3edd218e3a | ||
|
|
569bf6335b | ||
|
|
7b8919d706 | ||
|
|
147fd87d8c | ||
|
|
ac0926d40b | ||
|
|
105d472f5d | ||
|
|
c1a49da385 | ||
|
|
c3f596e64e | ||
|
|
adfda84c41 | ||
|
|
345cc118a3 | ||
|
|
df070ac0cf | ||
|
|
7b88be2635 | ||
|
|
c30a39d29a | ||
|
|
36db41a1f0 | ||
|
|
8b249dc06a | ||
|
|
0c6872b352 | ||
|
|
58944993b8 | ||
|
|
3cfffa5fbb | ||
|
|
4873b8b413 | ||
|
|
0b85f5192c | ||
|
|
3e9a1776a1 | ||
|
|
e1fbe9ae54 | ||
|
|
f48d1a8017 | ||
|
|
0debc90695 | ||
|
|
8d42ea7cfd | ||
|
|
1dae80a633 | ||
|
|
d398c86b0c | ||
|
|
70809e0647 | ||
|
|
4c1a5168f0 | ||
|
|
f9acfbc224 | ||
|
|
5f78e1a983 | ||
|
|
7bc5579fb7 | ||
|
|
57750efcb2 | ||
|
|
a33ee1cda9 | ||
|
|
cd20a0679a | ||
|
|
20e546e02b | ||
|
|
f5547f093e | ||
|
|
c00d95242d | ||
|
|
05c4d23df6 | ||
|
|
06fa17f33f | ||
|
|
194285289c | ||
|
|
a61fa61330 | ||
|
|
68c922ee12 | ||
|
|
d1042b276b | ||
|
|
9bbffe150f | ||
|
|
b95be526d3 | ||
|
|
165f9d769b | ||
|
|
a0ea75b74e | ||
|
|
4075f92eec | ||
|
|
035aff5454 | ||
|
|
52dc7ad259 | ||
|
|
c3c83f608c | ||
|
|
ffba351a16 | ||
|
|
a12623e142 | ||
|
|
1a691a103e | ||
|
|
5e725e0bbe | ||
|
|
93c2fa4c73 | ||
|
|
f412fb21d6 | ||
|
|
bd4b6c1f01 | ||
|
|
d1839d87e7 | ||
|
|
1fc163eb5f | ||
|
|
cd2b3cb73e | ||
|
|
88b5cf2461 | ||
|
|
2ccb0af75e | ||
|
|
27ee6e7643 | ||
|
|
a3207a5703 | ||
|
|
72ff64a7f8 | ||
|
|
9c06d1d0ae | ||
|
|
f11afd4414 | ||
|
|
2aa70b6ab8 | ||
|
|
4626a6f609 | ||
|
|
9152f8559f | ||
|
|
7f4c61b15a | ||
|
|
b365131363 | ||
|
|
a6ea4dd0d7 | ||
|
|
9c6649f077 | ||
|
|
04ba202e12 | ||
|
|
352a6c5691 | ||
|
|
72bccad82d | ||
|
|
6d52a2b409 | ||
|
|
9b1035a5f2 | ||
|
|
ce7c3e8039 | ||
|
|
9baec288c3 | ||
|
|
82894b94f4 | ||
|
|
fb00d68aa7 | ||
|
|
12288a2622 | ||
|
|
f84ce3f1d1 | ||
|
|
306b3f72d8 | ||
|
|
593a04d047 | ||
|
|
667548f3ed | ||
|
|
8f89bf6402 | ||
|
|
ff28e7c86e | ||
|
|
67cc69179b | ||
|
|
06fac2b7a3 | ||
|
|
a354f6bdc5 | ||
|
|
cb44c71733 | ||
|
|
6b1c14f875 | ||
|
|
7770aba877 | ||
|
|
05381096aa | ||
|
|
6bdd9ad4dd | ||
|
|
19836e8898 | ||
|
|
5e4b193260 | ||
|
|
923d0f2b7a | ||
|
|
56f1a93c4e | ||
|
|
0168182af5 | ||
|
|
679f0e1cd8 | ||
|
|
dd6b9bb38d | ||
|
|
5bd8f35dc0 | ||
|
|
53fc939e35 | ||
|
|
42d6c79710 | ||
|
|
882869255b | ||
|
|
19903751c1 | ||
|
|
836074fb60 | ||
|
|
5865a70c2e | ||
|
|
fa057ee9d3 | ||
|
|
2d88fcb249 | ||
|
|
920bb04b00 | ||
|
|
a915e62e2c | ||
|
|
226a6c50e0 | ||
|
|
269f13de76 | ||
|
|
3ee796a663 | ||
|
|
d24af2f4db | ||
|
|
f86c3cfd91 | ||
|
|
11061bdd07 | ||
|
|
f901f802bb | ||
|
|
a98c209101 | ||
|
|
6a459682ca | ||
|
|
1a7194b3a9 |
539
.github/workflows/build.yml
vendored
@@ -1,5 +1,10 @@
|
|||||||
name: Build
|
name: Build
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@@ -10,7 +15,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
opensuse_version: [ 'tumbleweed', 'leap:15.4', 'leap:15.5' ]
|
opensuse_version: [ 'tumbleweed', 'leap:15.6' ]
|
||||||
qt_version: [ '5', '6' ]
|
qt_version: [ '5', '6' ]
|
||||||
container:
|
container:
|
||||||
image: opensuse/${{matrix.opensuse_version}}
|
image: opensuse/${{matrix.opensuse_version}}
|
||||||
@@ -23,12 +28,15 @@ jobs:
|
|||||||
- name: Upgrade packages
|
- name: Upgrade packages
|
||||||
if: matrix.opensuse_version == 'tumbleweed'
|
if: matrix.opensuse_version == 'tumbleweed'
|
||||||
run: zypper -n --gpg-auto-import-keys dup
|
run: zypper -n --gpg-auto-import-keys dup
|
||||||
|
- name: Upgrade packages
|
||||||
|
if: matrix.opensuse_version != 'tumbleweed'
|
||||||
|
run: zypper -n --gpg-auto-import-keys up
|
||||||
- name: Install gcc
|
- name: Install gcc
|
||||||
if: matrix.opensuse_version == 'tumbleweed'
|
if: matrix.opensuse_version == 'tumbleweed'
|
||||||
run: zypper -n --gpg-auto-import-keys in gcc gcc-c++
|
run: zypper -n --gpg-auto-import-keys in gcc gcc-c++
|
||||||
- name: Install gcc10
|
- name: Install gcc 13
|
||||||
if: matrix.opensuse_version != 'tumbleweed'
|
if: matrix.opensuse_version != 'tumbleweed'
|
||||||
run: zypper -n --gpg-auto-import-keys in gcc10 gcc10-c++
|
run: zypper -n --gpg-auto-import-keys in gcc13 gcc13-c++
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: >
|
run: >
|
||||||
zypper -n --gpg-auto-import-keys in
|
zypper -n --gpg-auto-import-keys in
|
||||||
@@ -40,7 +48,6 @@ jobs:
|
|||||||
cmake
|
cmake
|
||||||
gettext-tools
|
gettext-tools
|
||||||
openssh-clients
|
openssh-clients
|
||||||
rsync
|
|
||||||
glibc-devel
|
glibc-devel
|
||||||
libboost_headers-devel
|
libboost_headers-devel
|
||||||
boost-devel
|
boost-devel
|
||||||
@@ -134,35 +141,27 @@ jobs:
|
|||||||
if: matrix.opensuse_version != 'tumbleweed'
|
if: matrix.opensuse_version != 'tumbleweed'
|
||||||
working-directory: build
|
working-directory: build
|
||||||
env:
|
env:
|
||||||
CC: gcc-10
|
CC: gcc-13
|
||||||
CXX: g++-10
|
CXX: g++-13
|
||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||||
- name: Set opensuse subdir
|
- name: Set subdir
|
||||||
run: echo "opensuse_subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_ENV
|
id: set-subdir
|
||||||
- name: Upload artifacts
|
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
|
||||||
if: matrix.opensuse_version != 'tumbleweed'
|
- name: Upload source
|
||||||
|
if: matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: opensuse-${{env.opensuse_subdir}}-qt${{matrix.qt_version}}
|
name: source
|
||||||
path: |
|
path: |
|
||||||
/usr/src/packages/SOURCES/*.xz
|
/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/SRPMS/*.rpm
|
||||||
/usr/src/packages/RPMS/x86_64/*.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:
|
build-fedora:
|
||||||
@@ -172,7 +171,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
fedora_version: [ '38', '39', '40' ]
|
fedora_version: [ '39', '40', '41' ]
|
||||||
container:
|
container:
|
||||||
image: fedora:${{matrix.fedora_version}}
|
image: fedora:${{matrix.fedora_version}}
|
||||||
steps:
|
steps:
|
||||||
@@ -256,23 +255,11 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
/github/home/rpmbuild/SRPMS/*.rpm
|
/github/home/rpmbuild/SRPMS/*.rpm
|
||||||
/github/home/rpmbuild/RPMS/x86_64/*.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:
|
build-openmandriva:
|
||||||
name: Build OpenMandriva
|
name: Build OpenMandriva
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -298,7 +285,6 @@ jobs:
|
|||||||
lsb-release
|
lsb-release
|
||||||
rpmdevtools
|
rpmdevtools
|
||||||
rpm-build
|
rpm-build
|
||||||
rsync
|
|
||||||
glibc-devel
|
glibc-devel
|
||||||
boost-devel
|
boost-devel
|
||||||
dbus-devel
|
dbus-devel
|
||||||
@@ -360,29 +346,18 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
|
if: matrix.openmandriva_version != 'cooker'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: openmandriva-${{matrix.openmandriva_version}}
|
name: openmandriva-${{matrix.openmandriva_version}}
|
||||||
path: |
|
path: |
|
||||||
/github/home/rpmbuild/SRPMS/*.rpm
|
/github/home/rpmbuild/SRPMS/*.rpm
|
||||||
/github/home/rpmbuild/RPMS/x86_64/*.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:
|
build-mageia:
|
||||||
name: Build Mageia
|
name: Build Mageia
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && false
|
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -391,8 +366,14 @@ jobs:
|
|||||||
container:
|
container:
|
||||||
image: mageia:${{matrix.mageia_version}}
|
image: mageia:${{matrix.mageia_version}}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Set media
|
||||||
|
run: |
|
||||||
|
urpmi.removemedia "Core Release"
|
||||||
|
urpmi.removemedia "Core Updates"
|
||||||
|
urpmi.addmedia "Core Release" "https://mirrors.kernel.org/mageia/distrib/${{matrix.mageia_version}}/x86_64/media/core/release/"
|
||||||
|
urpmi.addmedia "Core Updates" "https://mirrors.kernel.org/mageia/distrib/${{matrix.mageia_version}}/x86_64/media/core/updates/"
|
||||||
- name: Update repositories
|
- name: Update repositories
|
||||||
run: urpmi.update --auto -a
|
run: urpmi.update -a
|
||||||
- name: Upgrade packages
|
- name: Upgrade packages
|
||||||
run: urpmi --auto --auto-update
|
run: urpmi --auto --auto-update
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -408,7 +389,6 @@ jobs:
|
|||||||
tar
|
tar
|
||||||
rpmdevtools
|
rpmdevtools
|
||||||
gettext
|
gettext
|
||||||
rsync
|
|
||||||
lib64boost-devel
|
lib64boost-devel
|
||||||
lib64protobuf-devel
|
lib64protobuf-devel
|
||||||
lib64sqlite3-devel
|
lib64sqlite3-devel
|
||||||
@@ -426,16 +406,19 @@ jobs:
|
|||||||
lib64fftw-devel
|
lib64fftw-devel
|
||||||
lib64dbus-devel
|
lib64dbus-devel
|
||||||
lib64appstream-devel
|
lib64appstream-devel
|
||||||
|
lib64qt6core-devel
|
||||||
|
lib64qt6gui-devel
|
||||||
|
lib64qt6widgets-devel
|
||||||
|
lib64qt6network-devel
|
||||||
|
lib64qt6concurrent-devel
|
||||||
|
lib64qt6sql-devel
|
||||||
|
lib64qt6dbus-devel
|
||||||
|
lib64qt6help-devel
|
||||||
|
lib64qt6test-devel
|
||||||
protobuf-compiler
|
protobuf-compiler
|
||||||
desktop-file-utils
|
desktop-file-utils
|
||||||
appstream-util
|
appstream-util
|
||||||
hicolor-icon-theme
|
hicolor-icon-theme
|
||||||
- name: Install Qt 5
|
|
||||||
if: matrix.mageia_version == '8'
|
|
||||||
run: urpmi --auto --force urpmi-debuginfo-install lib64qt5core-devel lib64qt5gui-devel lib64qt5widgets-devel lib64qt5network-devel lib64qt5concurrent-devel lib64qt5sql-devel lib64qt5dbus-devel lib64qt5help-devel lib64qt5test-devel lib64qt5x11extras-devel
|
|
||||||
- name: Install Qt 6
|
|
||||||
if: matrix.mageia_version == 'cauldron'
|
|
||||||
run: urpmi --auto --force urpmi-debuginfo-install lib64qt6core-devel lib64qt6gui-devel lib64qt6widgets-devel lib64qt6network-devel lib64qt6concurrent-devel lib64qt6sql-devel lib64qt6dbus-devel lib64qt6help-devel lib64qt6test-devel
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -468,18 +451,6 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
/github/home/rpmbuild/SRPMS/*.rpm
|
/github/home/rpmbuild/SRPMS/*.rpm
|
||||||
/github/home/rpmbuild/RPMS/x86_64/*.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:
|
build-debian:
|
||||||
@@ -493,8 +464,10 @@ jobs:
|
|||||||
container:
|
container:
|
||||||
image: debian:${{matrix.debian_version}}
|
image: debian:${{matrix.debian_version}}
|
||||||
steps:
|
steps:
|
||||||
- name: Update repositories
|
- name: Update packages
|
||||||
run: apt update -y
|
run: apt update -y
|
||||||
|
- name: Upgrade packages
|
||||||
|
run: apt upgrade -y
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
@@ -502,7 +475,6 @@ jobs:
|
|||||||
apt install -y
|
apt install -y
|
||||||
build-essential
|
build-essential
|
||||||
dh-make
|
dh-make
|
||||||
ssh
|
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
cmake
|
cmake
|
||||||
@@ -512,7 +484,6 @@ jobs:
|
|||||||
fakeroot
|
fakeroot
|
||||||
gettext
|
gettext
|
||||||
lsb-release
|
lsb-release
|
||||||
rsync
|
|
||||||
dpkg-dev
|
dpkg-dev
|
||||||
libglib2.0-dev
|
libglib2.0-dev
|
||||||
libdbus-1-dev
|
libdbus-1-dev
|
||||||
@@ -564,18 +535,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: debian-${{matrix.debian_version}}
|
name: debian-${{matrix.debian_version}}
|
||||||
path: "*.deb"
|
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:
|
build-ubuntu:
|
||||||
@@ -585,12 +544,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
ubuntu_version: [ 'focal', 'jammy', 'lunar', 'mantic' ]
|
ubuntu_version: [ 'focal', 'jammy', 'noble', 'oracular' ]
|
||||||
container:
|
container:
|
||||||
image: ubuntu:${{matrix.ubuntu_version}}
|
image: ubuntu:${{matrix.ubuntu_version}}
|
||||||
steps:
|
steps:
|
||||||
- name: Update repositories
|
- name: Update packages
|
||||||
run: apt update -y
|
run: apt update -y
|
||||||
|
- name: Upgrade packages
|
||||||
|
run: apt upgrade -y
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
@@ -598,7 +559,6 @@ jobs:
|
|||||||
apt install -y
|
apt install -y
|
||||||
build-essential
|
build-essential
|
||||||
dh-make
|
dh-make
|
||||||
ssh
|
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
cmake
|
cmake
|
||||||
@@ -610,7 +570,6 @@ jobs:
|
|||||||
curl
|
curl
|
||||||
gettext
|
gettext
|
||||||
lsb-release
|
lsb-release
|
||||||
rsync
|
|
||||||
dpkg-dev
|
dpkg-dev
|
||||||
libglib2.0-dev
|
libglib2.0-dev
|
||||||
libboost-dev
|
libboost-dev
|
||||||
@@ -665,40 +624,29 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
*.deb
|
*.deb
|
||||||
*.ddeb
|
*.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:
|
upload-ubuntu-ppa:
|
||||||
name: Upload Ubuntu PPA
|
name: Upload Ubuntu PPA
|
||||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.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
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
ubuntu_version: [ 'focal', 'jammy', 'lunar', 'mantic' ]
|
ubuntu_version: [ 'focal', 'jammy', 'noble', 'oracular' ]
|
||||||
container:
|
container:
|
||||||
image: ubuntu:${{matrix.ubuntu_version}}
|
image: ubuntu:${{matrix.ubuntu_version}}
|
||||||
steps:
|
steps:
|
||||||
- name: Update repositories
|
- name: Update packages
|
||||||
run: apt update -y
|
run: apt update -y
|
||||||
|
- name: Upgrade packages
|
||||||
|
run: apt upgrade -y
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: >
|
run: >
|
||||||
apt install -y
|
apt install -y
|
||||||
git
|
git
|
||||||
ssh
|
|
||||||
build-essential
|
build-essential
|
||||||
dh-make
|
dh-make
|
||||||
make
|
make
|
||||||
@@ -733,6 +681,11 @@ jobs:
|
|||||||
gstreamer1.0-alsa
|
gstreamer1.0-alsa
|
||||||
gstreamer1.0-pulseaudio
|
gstreamer1.0-pulseaudio
|
||||||
protobuf-compiler
|
protobuf-compiler
|
||||||
|
- name: Install keyboxd
|
||||||
|
if: matrix.ubuntu_version == 'noble'
|
||||||
|
env:
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
run: apt install -y keyboxd
|
||||||
- name: Install Qt 5
|
- name: Install Qt 5
|
||||||
if: matrix.ubuntu_version == 'focal'
|
if: matrix.ubuntu_version == 'focal'
|
||||||
env:
|
env:
|
||||||
@@ -762,15 +715,11 @@ jobs:
|
|||||||
gpg_private_key: ${{secrets.UBUNTU_PPA_GPG_PRIVATE_KEY}}
|
gpg_private_key: ${{secrets.UBUNTU_PPA_GPG_PRIVATE_KEY}}
|
||||||
- name: dpkg-buildpackage
|
- name: dpkg-buildpackage
|
||||||
run: dpkg-buildpackage -S -d -k573D197B5EA20EDF
|
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
|
- 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
|
run: dput ppa:jonaski/strawberry-unstable ../*_source.changes
|
||||||
- name: Upload Stable PPA
|
- name: Upload Stable PPA
|
||||||
if: env.is_release == '1' && env.release_version != ''
|
if: github.event_name == 'release'
|
||||||
run: dput ppa:jonaski/strawberry ../*_source.changes
|
run: dput ppa:jonaski/strawberry ../*_source.changes
|
||||||
|
|
||||||
|
|
||||||
@@ -781,13 +730,27 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
runner: [ 'macos-11' ]
|
runner: [ 'macos-13', 'macos-14' ]
|
||||||
buildtype: [ 'release' ]
|
buildtype: [ 'release' ]
|
||||||
|
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
- name: Set MACOSX_DEPLOYMENT_TARGET
|
||||||
|
run: |
|
||||||
|
for i in 12 13 14 15; do
|
||||||
|
if [ -d "/Library/Developer/CommandLineTools/SDKs/MacOSX${i}.sdk" ]; then
|
||||||
|
echo "Using macOS SDK ${i}"
|
||||||
|
echo "MACOSX_DEPLOYMENT_TARGET=${i}.0" >> $GITHUB_ENV
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Verify MACOSX_DEPLOYMENT_TARGET
|
||||||
|
run: |
|
||||||
|
test "${MACOSX_DEPLOYMENT_TARGET}" = "" && false || echo "MACOSX_DEPLOYMENT_TARGET: ${MACOSX_DEPLOYMENT_TARGET}"
|
||||||
|
|
||||||
- name: Set arch
|
- name: Set arch
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "arch=$(uname -m)" >> $GITHUB_ENV
|
run: echo "arch=$(uname -m)" >> $GITHUB_ENV
|
||||||
@@ -813,7 +776,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Import certificate file
|
- name: Import certificate file
|
||||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
|
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
|
||||||
uses: apple-actions/import-codesign-certs@v2
|
uses: apple-actions/import-codesign-certs@v3
|
||||||
with:
|
with:
|
||||||
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }}
|
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }}
|
||||||
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
|
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
|
||||||
@@ -835,7 +798,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: 11.0
|
|
||||||
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
|
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
|
||||||
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
|
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
|
||||||
run: >
|
run: >
|
||||||
@@ -852,6 +814,7 @@ jobs:
|
|||||||
-DICU_ROOT="${{env.prefix_path}}"
|
-DICU_ROOT="${{env.prefix_path}}"
|
||||||
-DFFTW3_DIR="${{env.prefix_path}}"
|
-DFFTW3_DIR="${{env.prefix_path}}"
|
||||||
-DAPPLE_DEVELOPER_ID=$(test '${{github.repository}}' = 'strawberrymusicplayer/strawberry' && test '${{github.event.pull_request.base.repo.full_name}}' = '${{github.event.pull_request.head.repo.full_name}}' && echo "383J84DVB6" || echo "")
|
-DAPPLE_DEVELOPER_ID=$(test '${{github.repository}}' = 'strawberrymusicplayer/strawberry' && test '${{github.event.pull_request.base.repo.full_name}}' = '${{github.event.pull_request.head.repo.full_name}}' && echo "383J84DVB6" || echo "")
|
||||||
|
-DENABLE_SPOTIFY=$(test -f "${{env.prefix_path}}/lib/gstreamer-1.0/libgstspotify.dylib" && echo "ON" || echo "OFF")
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --config Release --parallel 4
|
run: cmake --build build --config Release --parallel 4
|
||||||
@@ -869,10 +832,10 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: make deploy
|
run: make deploy
|
||||||
|
|
||||||
- name: Codesign libsoup
|
- name: Manually Codesign
|
||||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
|
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
|
||||||
working-directory: build
|
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-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libzstd.1.dylib,libfreetype.6.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
|
||||||
|
|
||||||
- name: Deploy check
|
- name: Deploy check
|
||||||
working-directory: build
|
working-directory: build
|
||||||
@@ -887,35 +850,30 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: make dmg
|
run: make dmg
|
||||||
|
|
||||||
- name: SSH key setup
|
- name: SSH 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
|
uses: shimataro/ssh-key-action@v2
|
||||||
with:
|
with:
|
||||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||||
key: ${{ secrets.SSH_KEY }}
|
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
|
- 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: |
|
run: |
|
||||||
if [ "${{env.is_release}}" = "1" ] && ! [ "${{env.release_version}}" = "" ]; then
|
if [ "${{github.event_name}}" = "release" ]; then
|
||||||
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV
|
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_ENV
|
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Create server path
|
- 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')
|
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 ${{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
|
- 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')
|
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}}:${{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-macos-private:
|
build-macos-private:
|
||||||
@@ -932,6 +890,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
- name: Set MACOSX_DEPLOYMENT_TARGET
|
||||||
|
run: |
|
||||||
|
for i in 12 13 14 15; do
|
||||||
|
if [ -d "/Library/Developer/CommandLineTools/SDKs/MacOSX${i}.sdk" ]; then
|
||||||
|
echo "Using macOS SDK ${i}"
|
||||||
|
echo "MACOSX_DEPLOYMENT_TARGET=${i}.0" >> $GITHUB_ENV
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Verify MACOSX_DEPLOYMENT_TARGET
|
||||||
|
run: |
|
||||||
|
test "${MACOSX_DEPLOYMENT_TARGET}" = "" && false || echo "MACOSX_DEPLOYMENT_TARGET: ${MACOSX_DEPLOYMENT_TARGET}"
|
||||||
|
|
||||||
- name: Set arch
|
- name: Set arch
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "arch=$(uname -m)" >> $GITHUB_ENV
|
run: echo "arch=$(uname -m)" >> $GITHUB_ENV
|
||||||
@@ -962,7 +934,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: 11.0
|
|
||||||
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
|
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
|
||||||
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
|
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
|
||||||
run: >
|
run: >
|
||||||
@@ -979,6 +950,7 @@ jobs:
|
|||||||
-DICU_ROOT="${{env.prefix_path}}"
|
-DICU_ROOT="${{env.prefix_path}}"
|
||||||
-DFFTW3_DIR="${{env.prefix_path}}"
|
-DFFTW3_DIR="${{env.prefix_path}}"
|
||||||
-DAPPLE_DEVELOPER_ID="383J84DVB6"
|
-DAPPLE_DEVELOPER_ID="383J84DVB6"
|
||||||
|
-DENABLE_SPOTIFY=$(test -f "${{env.prefix_path}}/lib/gstreamer-1.0/libgstspotify.dylib" && echo "ON" || echo "OFF")
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --config Release --parallel 4
|
run: cmake --build build --config Release --parallel 4
|
||||||
@@ -996,6 +968,10 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: make deploy
|
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
|
- name: Deploy check
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: make deploycheck
|
run: make deploycheck
|
||||||
@@ -1008,22 +984,20 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: make dmg
|
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
|
- name: Set Upload path
|
||||||
|
id: set-upload-path
|
||||||
run: |
|
run: |
|
||||||
if [ "${{env.is_release}}" = "1" ]; then
|
if [ "${{github.event_name}}" = "release" ]; then
|
||||||
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV
|
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_ENV
|
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Create server path
|
- 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
|
- 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:
|
build-windows-mingw:
|
||||||
@@ -1033,7 +1007,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: [ 'i686', 'x86_64' ]
|
arch: [ 'x86_64' ]
|
||||||
buildtype: [ 'debug', 'release' ]
|
buildtype: [ 'debug', 'release' ]
|
||||||
container:
|
container:
|
||||||
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.buildtype}}
|
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.buildtype}}
|
||||||
@@ -1076,6 +1050,7 @@ jobs:
|
|||||||
-DENABLE_LIBGPOD=OFF
|
-DENABLE_LIBGPOD=OFF
|
||||||
-DENABLE_LIBMTP=OFF
|
-DENABLE_LIBMTP=OFF
|
||||||
-DENABLE_AUDIOCD=OFF
|
-DENABLE_AUDIOCD=OFF
|
||||||
|
-DENABLE_SPOTIFY=OFF
|
||||||
-DProtobuf_PROTOC_EXECUTABLE="/strawberry-mxe/usr/x86_64-pc-linux-gnu/bin/protoc"
|
-DProtobuf_PROTOC_EXECUTABLE="/strawberry-mxe/usr/x86_64-pc-linux-gnu/bin/protoc"
|
||||||
|
|
||||||
- name: Run Make
|
- name: Run Make
|
||||||
@@ -1095,7 +1070,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Copy Qt styles
|
- name: Copy Qt styles
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6/plugins/styles/qwindowsvistastyle.dll ${GITHUB_WORKSPACE}/build/styles/
|
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6/plugins/styles/qmodernwindowsstyle.dll ${GITHUB_WORKSPACE}/build/styles/
|
||||||
|
|
||||||
- name: Copy Qt TLS plugins
|
- name: Copy Qt TLS plugins
|
||||||
working-directory: build
|
working-directory: build
|
||||||
@@ -1111,7 +1086,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Copy gstreamer plugins
|
- name: Copy gstreamer plugins
|
||||||
working-directory: build
|
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
|
- name: Copy extra binaries
|
||||||
working-directory: build
|
working-directory: build
|
||||||
@@ -1190,26 +1165,30 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: makensis strawberry.nsi
|
run: makensis strawberry.nsi
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Set Upload path
|
||||||
uses: actions/upload-artifact@v4
|
id: set-upload-path
|
||||||
with:
|
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/windows')))
|
||||||
name: windows-mingw-${{matrix.arch}}-${{matrix.buildtype}}
|
run: |
|
||||||
path: build/StrawberrySetup*.exe
|
if [ "${{github.event_name}}" = "release" ]; then
|
||||||
|
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/windows/mingw" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/windows/mingw" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- name: SSH key setup
|
- name: SSH Setup
|
||||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
if: steps.set-upload-path.outputs.upload_path != ''
|
||||||
uses: shimataro/ssh-key-action@v2
|
uses: shimataro/ssh-key-action@v2
|
||||||
with:
|
with:
|
||||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||||
key: ${{ secrets.SSH_KEY }}
|
key: ${{ secrets.SSH_KEY }}
|
||||||
|
|
||||||
- name: Create server path
|
- 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')
|
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 ${{secrets.BUILDS_PATH}}/windows/mingw
|
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
|
- 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')
|
if: steps.set-upload-path.outputs.upload_path != ''
|
||||||
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/
|
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/StrawberrySetup*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{steps.set-upload-path.outputs.upload_path}}/
|
||||||
|
|
||||||
|
|
||||||
build-windows-msvc:
|
build-windows-msvc:
|
||||||
@@ -1219,8 +1198,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: [ 'x86', 'x86_64' ]
|
arch: [ 'x86_64' ]
|
||||||
buildtype: [ 'debug', 'release' ]
|
buildtype: [ 'release' ]
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set prefix path
|
- name: Set prefix path
|
||||||
@@ -1234,6 +1213,15 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
|
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install rsync
|
||||||
|
shell: cmd
|
||||||
|
run: choco install --no-progress rsync
|
||||||
|
|
||||||
|
- name: Cleanup PATH
|
||||||
|
uses: egor-tensin/cleanup-path@v4
|
||||||
|
with:
|
||||||
|
dirs: ${{env.prefix_path_backslash}}\bin;C:\Windows;C:\Windows\system32;C:\Program Files\Git\bin;C:\Program Files\CMake\bin;C:\Program Files\GitHub CLI;C:\ProgramData\Chocolatey\bin;C:\Program Files (x86)\NSIS
|
||||||
|
|
||||||
- name: Create downloads directory
|
- name: Create downloads directory
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: mkdir downloads
|
run: mkdir downloads
|
||||||
@@ -1257,13 +1245,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Delete conflicting files
|
- name: Delete conflicting files
|
||||||
shell: bash
|
shell: bash
|
||||||
run: rm -rf /c/msys64 /c/mingw32 /c/mingw64 /c/strawberry/c "/c/program files/OpenSSL" "/c/program files/postgresql"
|
run: rm -rf /c/{msys64,mingw32,mingw64} /c/strawberry/c "/c/program files/OpenSSL"
|
||||||
|
|
||||||
- name: Delete conflicting icu
|
- name: Delete conflicting icu
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: find "/c/program files (x86)/windows kits/" -type f \( -iname 'icu*.lib' -o -iname 'icu*.h' \) -print -delete
|
||||||
find "/c/program files (x86)/windows kits/" -iname 'icu*.lib' -delete
|
|
||||||
find "/c/program files (x86)/windows kits/" -iname 'icu*.h' -delete
|
|
||||||
|
|
||||||
- name: Download NSIS LockedList plugin
|
- name: Download NSIS LockedList plugin
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -1310,8 +1296,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
arch: ${{matrix.arch}}
|
arch: ${{matrix.arch}}
|
||||||
sdk: 10.0.20348.0
|
sdk: 10.0.20348.0
|
||||||
vsversion: 17
|
vsversion: 2022
|
||||||
toolset: 14.3
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -1328,12 +1313,12 @@ jobs:
|
|||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
|
|
||||||
- name: Set ENABLE_WIN32_CONSOLE (debug)
|
- name: Set ENABLE_WIN32_CONSOLE (debug)
|
||||||
if: matrix.build_type == 'debug'
|
if: matrix.buildtype == 'debug'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "win32_console=ON" >> $GITHUB_ENV
|
run: echo "win32_console=ON" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set ENABLE_WIN32_CONSOLE (release)
|
- name: Set ENABLE_WIN32_CONSOLE (release)
|
||||||
if: matrix.build_type == 'release'
|
if: matrix.buildtype == 'release'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "win32_console=OFF" >> $GITHUB_ENV
|
run: echo "win32_console=OFF" >> $GITHUB_ENV
|
||||||
|
|
||||||
@@ -1352,6 +1337,8 @@ jobs:
|
|||||||
-DUSE_TAGLIB=ON
|
-DUSE_TAGLIB=ON
|
||||||
-DPKG_CONFIG_EXECUTABLE="${{env.prefix_path_forwardslash}}/bin/pkg-config.exe"
|
-DPKG_CONFIG_EXECUTABLE="${{env.prefix_path_forwardslash}}/bin/pkg-config.exe"
|
||||||
-DICU_ROOT="${{env.prefix_path_forwardslash}}"
|
-DICU_ROOT="${{env.prefix_path_forwardslash}}"
|
||||||
|
-DFFTW3_DIR="${{env.prefix_path_forwardslash}}"
|
||||||
|
-DBoost_INCLUDE_DIR="${{env.prefix_path_forwardslash}}/include"
|
||||||
|
|
||||||
- name: Run Make
|
- name: Run Make
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -1397,7 +1384,7 @@ jobs:
|
|||||||
- name: Copy Qt styles
|
- name: Copy Qt styles
|
||||||
shell: cmd
|
shell: cmd
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: copy ${{env.prefix_path_backslash}}\plugins\styles\qwindowsvistastyle*.dll .\styles\
|
run: copy ${{env.prefix_path_backslash}}\plugins\styles\qmodernwindowsstyle*.dll .\styles\
|
||||||
|
|
||||||
- name: Copy Qt TLS plugins
|
- name: Copy Qt TLS plugins
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -1417,66 +1404,7 @@ jobs:
|
|||||||
- name: Copy gstreamer plugins
|
- name: Copy gstreamer plugins
|
||||||
shell: cmd
|
shell: cmd
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: |
|
run: copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\*.dll .\gstreamer-plugins\
|
||||||
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\
|
|
||||||
|
|
||||||
- name: Download copydlldeps.sh
|
- name: Download copydlldeps.sh
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -1550,103 +1478,118 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: makensis strawberry.nsi
|
run: makensis strawberry.nsi
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Set Upload path
|
||||||
uses: actions/upload-artifact@v4
|
id: set-upload-path
|
||||||
with:
|
shell: bash
|
||||||
name: windows-msvc-${{matrix.arch}}-${{matrix.buildtype}}
|
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/windows')))
|
||||||
path: build/StrawberrySetup*.exe
|
run: |
|
||||||
|
if [ "${{github.event_name}}" = "release" ]; then
|
||||||
|
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/windows/msvc" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/windows/msvc" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: SSH Setup
|
||||||
|
if: steps.set-upload-path.outputs.upload_path != ''
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{secrets.SSH_KNOWN_HOSTS}}" > ~/.ssh/known_hosts
|
||||||
|
echo "${{secrets.SSH_KEY}}" > ~/.ssh/id_rsa
|
||||||
|
|
||||||
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
|
- name: Create server path
|
||||||
|
if: steps.set-upload-path.outputs.upload_path != ''
|
||||||
shell: bash
|
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 ${{steps.set-upload-path.outputs.upload_path}}
|
||||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/windows/msvc
|
|
||||||
- name: rsync
|
- name: rsync
|
||||||
|
if: steps.set-upload-path.outputs.upload_path != ''
|
||||||
shell: bash
|
shell: bash
|
||||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
run: rsync -e "/c/ProgramData/chocolatey/lib/rsync/tools/bin/ssh.exe -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/StrawberrySetup*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{steps.set-upload-path.outputs.upload_path}}/
|
||||||
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:
|
upload:
|
||||||
name: Upload release
|
name: Upload
|
||||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
if: (success() || failure()) && 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
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- build-opensuse
|
- build-opensuse
|
||||||
- build-fedora
|
- build-fedora
|
||||||
|
- build-mageia
|
||||||
- build-debian
|
- build-debian
|
||||||
- build-ubuntu
|
- build-ubuntu
|
||||||
- build-windows-mingw
|
|
||||||
- build-windows-msvc
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: sudo apt install -y git rsync hub
|
run: sudo apt install -y git rsync
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- name: Download artifacts
|
||||||
if: env.release_version != ''
|
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
- name: SSH Setup
|
||||||
- name: SSH key setup
|
|
||||||
if: env.release_version != ''
|
|
||||||
uses: shimataro/ssh-key-action@v2
|
uses: shimataro/ssh-key-action@v2
|
||||||
with:
|
with:
|
||||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||||
key: ${{secrets.SSH_KEY}}
|
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: (success() || failure()) && github.event_name == 'release'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build-opensuse
|
||||||
|
- build-fedora
|
||||||
|
- build-mageia
|
||||||
|
- build-debian
|
||||||
|
- build-ubuntu
|
||||||
|
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
|
- name: Add artifacts to release
|
||||||
if: env.is_release == '1' && env.release_version != ''
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
run: |
|
run: |
|
||||||
echo "Release version: ${{env.release_version}}"
|
echo "Release version: ${{github.event.release.tag_name}}"
|
||||||
filenames=()
|
filenames=()
|
||||||
files=()
|
files=()
|
||||||
a_files=()
|
|
||||||
for i in $(find artifacts -type f); do
|
for i in $(find artifacts -type f); do
|
||||||
filename=$(basename $i)
|
filename=$(basename $i)
|
||||||
if [[ ${filenames[@]} =~ ${filename} ]]; then
|
if [[ ${filenames[@]} =~ ${filename} ]]; then
|
||||||
@@ -1654,22 +1597,16 @@ jobs:
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
filenames+=("${filename}")
|
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
|
if [ "${existing_asset}" = "" ]; then
|
||||||
echo "Adding file: ${filename}"
|
echo "Adding file: ${filename}"
|
||||||
files+=("$i")
|
files+=("${i}")
|
||||||
a_files+=("-a" "${i}")
|
|
||||||
else
|
else
|
||||||
echo "Release already has file: ${filename}"
|
echo "Release already has file: ${filename}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
files_list="${files[@]}"
|
files_list="${files[@]}"
|
||||||
a_files_list="${a_files[@]}"
|
|
||||||
if ! [ "${files_list}" = "" ]; then
|
if ! [ "${files_list}" = "" ]; then
|
||||||
echo "Uploading files: ${files_list}"
|
echo "Adding files to GitHub release"
|
||||||
rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var ${files_list} ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.RELEASES_PATH}}/
|
gh release upload "${{github.event.release.tag_name}}" ${files_list}
|
||||||
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}}"
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
133
.gitignore
vendored
@@ -1,120 +1,17 @@
|
|||||||
# This file is used to ignore files which are generated
|
/build
|
||||||
# ----------------------------------------------------------------------------
|
/bin
|
||||||
|
/CMakeLists.txt.user
|
||||||
# Build
|
/.kdev4
|
||||||
build/
|
/strawberry.kdev4
|
||||||
bin/
|
/.vscode
|
||||||
|
/.code-workspace
|
||||||
# CMake
|
/.sublime-workspace
|
||||||
CMakeLists.txt.user
|
/.idea
|
||||||
CMakeCache.txt
|
|
||||||
CMakeFiles
|
|
||||||
CMakeScripts
|
|
||||||
Makefile*
|
|
||||||
Testing
|
|
||||||
cmake_install.cmake
|
|
||||||
install_manifest.txt
|
|
||||||
compile_commands.json
|
|
||||||
CTestTestfile.cmake
|
|
||||||
_deps
|
|
||||||
|
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Compiled Object files
|
|
||||||
*.slo
|
|
||||||
*.lo
|
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
|
||||||
*.so
|
|
||||||
*.so.*
|
|
||||||
*.dylib
|
|
||||||
*.dll
|
|
||||||
|
|
||||||
# Fortran module files
|
|
||||||
*.mod
|
|
||||||
*.smod
|
|
||||||
|
|
||||||
# Compiled Static libraries
|
|
||||||
*.lai
|
|
||||||
*.la
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
|
|
||||||
# Dump files
|
|
||||||
*.core
|
|
||||||
*.stackdump
|
|
||||||
|
|
||||||
# Qt
|
|
||||||
*build-*
|
|
||||||
moc_*.cpp
|
|
||||||
moc_*.h
|
|
||||||
qrc_*.cpp
|
|
||||||
ui_*.h
|
|
||||||
*.moc
|
|
||||||
*.qm
|
|
||||||
|
|
||||||
# Temporary files
|
|
||||||
*~
|
|
||||||
*.autosave
|
|
||||||
*.orig
|
|
||||||
*.rej
|
|
||||||
.*.kate-swp
|
|
||||||
.swp.*
|
|
||||||
.*.swp
|
|
||||||
*.flc
|
|
||||||
|
|
||||||
# Directory files
|
|
||||||
.directory
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# MinGW generated files
|
|
||||||
*.Debug
|
|
||||||
*.Release
|
|
||||||
|
|
||||||
# Package files
|
|
||||||
*.spec
|
|
||||||
*.nsi
|
|
||||||
*.plist
|
|
||||||
|
|
||||||
# Stuff in dist
|
|
||||||
maketarball.sh
|
|
||||||
changelog
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
translations.pot
|
|
||||||
zanata.xml
|
|
||||||
.zanata-cache/
|
|
||||||
|
|
||||||
# QtCreator
|
|
||||||
CMakeLists.txt.user*
|
|
||||||
*.pro.user
|
|
||||||
*.pro.user.*
|
|
||||||
*creator.user*
|
|
||||||
target_wrapper.*
|
|
||||||
compile_commands.json
|
|
||||||
|
|
||||||
*.kdev4
|
|
||||||
*.vscode
|
|
||||||
*.code-workspace
|
|
||||||
*.sublime-workspace
|
|
||||||
|
|
||||||
# MSVC
|
|
||||||
CMakeSettings.json
|
|
||||||
/.vs
|
/.vs
|
||||||
/out
|
/out
|
||||||
|
/CMakeSettings.json
|
||||||
# CLion
|
/dist/scripts/maketarball.sh
|
||||||
/.idea
|
/dist/unix/strawberry.spec
|
||||||
|
/dist/windows/strawberry.nsi
|
||||||
|
/debian/control
|
||||||
|
/debian/changelog
|
||||||
|
|||||||
@@ -99,23 +99,17 @@ if(CCACHE_EXECUTABLE)
|
|||||||
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE})
|
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(USE_ICU "Use ICU" ON)
|
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
find_package(Backtrace)
|
find_package(Backtrace)
|
||||||
if(Backtrace_FOUND)
|
if(Backtrace_FOUND)
|
||||||
set(HAVE_BACKTRACE ON)
|
set(HAVE_BACKTRACE ON)
|
||||||
endif()
|
endif()
|
||||||
find_package(Boost REQUIRED)
|
find_package(Boost CONFIG)
|
||||||
if(USE_ICU)
|
if(NOT Boost_FOUND)
|
||||||
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
find_package(Boost REQUIRED)
|
||||||
if(ICU_FOUND)
|
|
||||||
set(HAVE_ICU ON)
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
find_package(Iconv)
|
|
||||||
endif()
|
endif()
|
||||||
|
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
||||||
find_package(Protobuf CONFIG)
|
find_package(Protobuf CONFIG)
|
||||||
if(NOT Protobuf_FOUND)
|
if(NOT Protobuf_FOUND)
|
||||||
find_package(Protobuf REQUIRED)
|
find_package(Protobuf REQUIRED)
|
||||||
@@ -266,36 +260,36 @@ if(X11_FOUND)
|
|||||||
|
|
||||||
endif(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)
|
option(USE_TAGPARSER "Build with TagParser" OFF)
|
||||||
|
|
||||||
if(NOT USE_TAGLIB AND NOT USE_TAGPARSER)
|
|
||||||
set(USE_TAGLIB ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# TAGLIB
|
# TAGLIB
|
||||||
if(USE_TAGLIB)
|
if(USE_TAGLIB)
|
||||||
pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1)
|
find_package(TagLib 2.0)
|
||||||
if(TAGLIB_FOUND)
|
if(TARGET TagLib::TagLib)
|
||||||
find_path(HAVE_TAGLIB_DSFFILE_H taglib/dsffile.h)
|
set(TAGLIB_FOUND ON)
|
||||||
find_path(HAVE_TAGLIB_DSDIFFFILE_H taglib/dsdifffile.h)
|
set(TAGLIB_LIBRARIES TagLib::TagLib)
|
||||||
if(HAVE_TAGLIB_DSFFILE_H)
|
|
||||||
set(HAVE_TAGLIB_DSFFILE ON)
|
set(HAVE_TAGLIB_DSFFILE ON)
|
||||||
endif(HAVE_TAGLIB_DSFFILE_H)
|
|
||||||
if(HAVE_TAGLIB_DSDIFFFILE_H)
|
|
||||||
set(HAVE_TAGLIB_DSDIFFFILE ON)
|
set(HAVE_TAGLIB_DSDIFFFILE ON)
|
||||||
endif(HAVE_TAGLIB_DSDIFFFILE_H)
|
else()
|
||||||
|
pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1)
|
||||||
endif()
|
endif()
|
||||||
|
set(HAVE_TAGLIB ON)
|
||||||
|
else()
|
||||||
|
set(HAVE_TAGLIB OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# TAGPARSER
|
# TAGPARSER
|
||||||
if(USE_TAGPARSER)
|
if(USE_TAGPARSER)
|
||||||
pkg_check_modules(TAGPARSER REQUIRED tagparser)
|
pkg_check_modules(TAGPARSER REQUIRED tagparser)
|
||||||
|
set(HAVE_TAGPARSER ON)
|
||||||
|
else()
|
||||||
|
set(HAVE_TAGPARSER OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
pkg_check_modules(LIBEBUR128 IMPORTED_TARGET libebur128)
|
pkg_check_modules(LIBEBUR128 IMPORTED_TARGET libebur128)
|
||||||
|
|
||||||
if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND)
|
if(NOT HAVE_TAGLIB AND NOT HAVE_TAGPARSER)
|
||||||
message(FATAL_ERROR "You need either TagLib or TagParser!")
|
message(FATAL_ERROR "You need either TagLib or TagParser!")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -445,6 +439,7 @@ option(INSTALL_TRANSLATIONS "Install translations" OFF)
|
|||||||
|
|
||||||
optional_component(SUBSONIC ON "Streaming: Subsonic")
|
optional_component(SUBSONIC ON "Streaming: Subsonic")
|
||||||
optional_component(TIDAL ON "Streaming: Tidal")
|
optional_component(TIDAL ON "Streaming: Tidal")
|
||||||
|
optional_component(SPOTIFY ON "Streaming: Spotify" DEPENDS "gstreamer" GSTREAMER_FOUND)
|
||||||
optional_component(QOBUZ ON "Streaming: Qobuz")
|
optional_component(QOBUZ ON "Streaming: Qobuz")
|
||||||
|
|
||||||
optional_component(MOODBAR ON "Moodbar"
|
optional_component(MOODBAR ON "Moodbar"
|
||||||
@@ -469,9 +464,11 @@ if(NOT CMAKE_CROSSCOMPILING)
|
|||||||
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
||||||
set(CMAKE_REQUIRED_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Sql)
|
set(CMAKE_REQUIRED_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Sql)
|
||||||
check_cxx_source_runs("
|
check_cxx_source_runs("
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
int main() {
|
int main(int argc, char *argv[]) {
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");
|
QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");
|
||||||
db.setDatabaseName(\":memory:\");
|
db.setDatabaseName(\":memory:\");
|
||||||
if (!db.open()) { return 1; }
|
if (!db.open()) { return 1; }
|
||||||
@@ -482,25 +479,6 @@ if(NOT CMAKE_CROSSCOMPILING)
|
|||||||
"
|
"
|
||||||
QT_SQLITE_TEST
|
QT_SQLITE_TEST
|
||||||
)
|
)
|
||||||
if(QT_SQLITE_TEST)
|
|
||||||
# Check that we have sqlite3 with FTS5
|
|
||||||
check_cxx_source_runs("
|
|
||||||
#include <QSqlDatabase>
|
|
||||||
#include <QSqlQuery>
|
|
||||||
int main() {
|
|
||||||
QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");
|
|
||||||
db.setDatabaseName(\":memory:\");
|
|
||||||
if (!db.open()) { return 1; }
|
|
||||||
QSqlQuery q(db);
|
|
||||||
q.prepare(\"CREATE VIRTUAL TABLE test_fts USING fts5(test, tokenize = 'unicode61 remove_diacritics 0');\");
|
|
||||||
if (!q.exec()) return 1;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
SQLITE_FTS5_TEST
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
unset(CMAKE_REQUIRED_FLAGS)
|
|
||||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set up definitions
|
# Set up definitions
|
||||||
@@ -514,6 +492,8 @@ add_definitions(
|
|||||||
-DQT_NO_CAST_TO_ASCII
|
-DQT_NO_CAST_TO_ASCII
|
||||||
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
|
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
|
||||||
-DQT_NO_FOREACH
|
-DQT_NO_FOREACH
|
||||||
|
-DQT_ASCII_CAST_WARNINGS
|
||||||
|
-DQT_NO_CAST_FROM_ASCII
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
@@ -559,15 +539,11 @@ if(QT_VERSION_MAJOR EQUAL 5)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_CROSSCOMPILING)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
if(QT_SQLITE_TEST)
|
if(NOT QT_SQLITE_TEST)
|
||||||
if(NOT SQLITE_FTS5_TEST)
|
|
||||||
message(WARNING "sqlite must be enabled with FTS5. See: https://www.sqlite.org/fts5.html")
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
message(WARNING "The Qt sqlite driver test failed.")
|
message(WARNING "The Qt sqlite driver test failed.")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND AND NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12)
|
if(HAVE_TAGLIB AND TAGLIB_FOUND AND NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12)
|
||||||
message(WARNING "There is a critical bug in TagLib (1.11.1) that can result in corrupt Ogg files, see: https://github.com/taglib/taglib/issues/864, please consider updating TagLib to the newest version.")
|
message(WARNING "There is a critical bug in TagLib (1.11.1) that can result in corrupt Ogg files, see: https://github.com/taglib/taglib/issues/864, please consider updating TagLib to the newest version.")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -48,24 +48,27 @@ small as possible.
|
|||||||
|
|
||||||
### Commit messages
|
### Commit messages
|
||||||
|
|
||||||
The first line should start with "Class:", which referer to the class
|
The first line should start with the name of the class that is changed
|
||||||
that is changed. Don't use a trailing period after the first line.
|
followed by a colon then a short explanation of the commit.
|
||||||
If this change affects more than one class, omit the class and write a
|
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.
|
more general message.
|
||||||
|
|
||||||
You only need to include a main description (body) for larger changes
|
You only need to include a main description (body) for larger changes
|
||||||
where the one line is not enough to describe everything.
|
where the one line is not enough to describe everything.
|
||||||
The main description starts after two newlines, it is normal prose and
|
The main description starts after two newlines, it is normal prose and
|
||||||
should use normal punctuation and capital letters where appropriate.
|
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:
|
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,
|
As of Qt 6.6.1, style changes are resetting the column sizes. To prevent this, we set a default section size.
|
||||||
and what bugs were fixed.
|
|
||||||
|
|
||||||
Fixes #1234
|
Fixes #1328
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
84
Changelog
@@ -2,6 +2,90 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
Unreleased:
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed Tidal Open API cover provider to only login when needed instead of on startup.
|
||||||
|
* Fixed KDE added keyboard accelerator characters (ampersands) appearing in sidebar (#1400, #1389, #1476).
|
||||||
|
* Fixed KDE added keyboard accelerator characters (ampersands) appearing when editing playlist name (#1499).
|
||||||
|
* Fixed collection "Search for this" adding prefix without value (#1510).
|
||||||
|
* (macOS) Fixed missing Spotify.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Improved volume adjustment and track seeking using touchpad (#1498).
|
||||||
|
|
||||||
|
Version 1.1.1 (2024.07.22):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed compilation songs being split into different albums when using album grouping.
|
||||||
|
* Fixed adding playlist columns not working when stretch mode is disabled (#1085).
|
||||||
|
* Fixed resetting playlist columns.
|
||||||
|
* Fixed adding songs to playlist adding all songs instead of filtered songs.
|
||||||
|
* Fixed collection filter matching entire text instead of individual words.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Use same code for collection and playlist filter search.
|
||||||
|
|
||||||
|
Version 1.1.0 (2024.07.14):
|
||||||
|
|
||||||
|
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).
|
||||||
|
* Fixed misredered playlist search field with Wayland using scaling (#1255).
|
||||||
|
* Fixed Azlyrics lyrics provider because of website changes.
|
||||||
|
* Fixed Musixmatch lyrics provider because of website changes.
|
||||||
|
* Fixed application exiting when closing file dialog (#1401).
|
||||||
|
* Fixed playlist shuffle randomness (#707).
|
||||||
|
* Fixed playlist shuffle order always the same when restarting playback (#1381).
|
||||||
|
* Fixed dynamic random mix not always ignoring shuffle and repeat mode (#1366).
|
||||||
|
* Fixed manual shuffle while playing not setting current song to new index (##1353).
|
||||||
|
* Fixed volume sync with PA when output is set to auto (#1123).
|
||||||
|
* Fixed mpris:trackid type with KDE 6 (#1397).
|
||||||
|
* Fixed open in file manager feature not handling missing XDG_DATA_DIRS variable.
|
||||||
|
* Fixed collection pixmap disk cache and moodbar cache with newer Qt versions.
|
||||||
|
* Fixed reading common metadata CUE's with multiple files (#1463).
|
||||||
|
* Fixed playlist header stretch mode to only resize the right column of the column being resized.
|
||||||
|
* Fixed adding columns to playlist header not working when not using stretch mode (#1085).
|
||||||
|
* Fixed severe memory leak (!) in context album cover fading (#1464).
|
||||||
|
* Separate albums by different artist in album groupings (#1276).
|
||||||
|
* Removed -new-window parameter from dolphin command for open in file manager (#1412).
|
||||||
|
* Only use playbin3 with GStreamer 1.24 and higher, not with GStreamer 1.22 or lower.
|
||||||
|
* (macOS/Windows) Fixed dash and hls streaming, plugins were missing.
|
||||||
|
* (Windows) Fixed incorrect colors in smart playlist wizard with Fusion in dark mode (#1399).
|
||||||
|
* (Windows) Fixed update window blocking sponsor window on startup.
|
||||||
|
|
||||||
|
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.
|
||||||
|
* Added button for fetching lyrics to tag editor (#1391).
|
||||||
|
* Added option not to skip "A", "An" and "The” when sorting artist names in collection (#1393).
|
||||||
|
* Improved album and title disc, remastered, etc matching and stripping (#1387).
|
||||||
|
* Save volume to settings when adjusting (#1272).
|
||||||
|
* Resolve song from collection using track with Cue in XSPF (#1181).
|
||||||
|
* Added background image to sidebar.
|
||||||
|
* Read metadata from RIFF WAV files (#1424).
|
||||||
|
* Use original path instead of canonical path when adding directories to the collection.
|
||||||
|
* Only apply added/removed collection directories when settings are saved.
|
||||||
|
* Detect and handle different text encodings when reading CUE files (#1429).
|
||||||
|
* The collection has been rewritten and improved (model/filter/search) (#392).
|
||||||
|
* Improve error messages from tag reader.
|
||||||
|
* (Unix) Add experimental GStreamer pipewire support.
|
||||||
|
* (Windows) Add experimental exclusive mode for WASAPI.
|
||||||
|
* (Windows MSVC) Added experimental ASIO support.
|
||||||
|
* (Windows MSVC) Add back WASAPI2.
|
||||||
|
|
||||||
|
New features:
|
||||||
|
* Letras lyrics provider.
|
||||||
|
* Open Tidal API (openapi.tidal.com) cover provider.
|
||||||
|
* Turbine analyzer.
|
||||||
|
* WaveRubber analyzer.
|
||||||
|
* Spotify streaming support.
|
||||||
|
|
||||||
|
Removed features:
|
||||||
|
* Removed now broken lyricsmode.com lyrics provider because of website changes.
|
||||||
|
|
||||||
Version 1.0.23 (2024.01.11):
|
Version 1.0.23 (2024.01.11):
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|||||||
14
README.md
@@ -1,4 +1,4 @@
|
|||||||
:strawberry: Strawberry Music Player [](https://github.com/strawberrymusicplayer/strawberry/actions)
|
:strawberry: Strawberry Music Player [](https://github.com/strawberrymusicplayer/strawberry/actions)
|
||||||
=======================
|
=======================
|
||||||
[](https://github.com/sponsors/jonaski)
|
[](https://github.com/sponsors/jonaski)
|
||||||
[](https://patreon.com/jonaskvinge)
|
[](https://patreon.com/jonaskvinge)
|
||||||
@@ -19,7 +19,7 @@ Resources:
|
|||||||
* openSUSE buildservice: https://build.opensuse.org/package/show/home:jonaski:audio/strawberry
|
* openSUSE buildservice: https://build.opensuse.org/package/show/home:jonaski:audio/strawberry
|
||||||
* Ubuntu PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry
|
* Ubuntu PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry
|
||||||
* Ubuntu Unstable PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable
|
* Ubuntu Unstable PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable
|
||||||
* Translations: https://translate.zanata.org/iteration/view/strawberry/master
|
* Translations: https://crowdin.com/project/strawberrymusicplayer/
|
||||||
|
|
||||||
### :bangbang: Opening an issue
|
### :bangbang: Opening an issue
|
||||||
|
|
||||||
@@ -54,18 +54,18 @@ Funding developers is a way to contribute to open source projects you appreciate
|
|||||||
* Edit tags on audio files
|
* Edit tags on audio files
|
||||||
* Fetch tags from MusicBrainz
|
* 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/)
|
* 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
|
* Support for multiple backends
|
||||||
* Audio analyzer
|
* Audio analyzer
|
||||||
* Audio equalizer
|
* Audio equalizer
|
||||||
* Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
* Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
* Scrobbler with support for [Last.fm](https://www.last.fm/), [Libre.fm](https://libre.fm/) and [ListenBrainz](https://listenbrainz.org/)
|
* Scrobbler with support for [Last.fm](https://www.last.fm/), [Libre.fm](https://libre.fm/) and [ListenBrainz](https://listenbrainz.org/)
|
||||||
* Subsonic, Tidal and Qobuz streaming support
|
* Subsonic, Tidal, Spotify and Qobuz streaming support
|
||||||
|
|
||||||
|
|
||||||
It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
|
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 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.**
|
**Access to macOS and Windows releases are currently restricted to sponsors, a 5 USD monthly sponsorship is required. You can sponsor strawberry through <a href="https://www.patreon.com/jonaskvinge">Patreon</a> for direct access to new releases. If you are sponsoring through GitHub, Ko-fi or PayPal, please e-mail support AT strawberrymusicplayer.org for access to downloads.**
|
||||||
|
|
||||||
### :heavy_exclamation_mark: Requirements
|
### :heavy_exclamation_mark: Requirements
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ To build Strawberry from source you need the following installed on your system
|
|||||||
* [Boost](https://www.boost.org/)
|
* [Boost](https://www.boost.org/)
|
||||||
* [GLib](https://developer.gnome.org/glib/)
|
* [GLib](https://developer.gnome.org/glib/)
|
||||||
* [Qt 6 or Qt 5.12 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
* [Qt 6 or Qt 5.12 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
* [SQLite 3.9 or newer](https://www.sqlite.org)
|
||||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||||
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||||
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||||
@@ -120,4 +120,4 @@ To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/st
|
|||||||
|
|
||||||
### :penguin: Packaging status
|
### :penguin: Packaging status
|
||||||
|
|
||||||
[](https://repology.org/metapackage/strawberry/versions)
|
[](https://repology.org/metapackage/strawberry/versions)
|
||||||
|
|||||||
@@ -1,21 +1,33 @@
|
|||||||
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
|
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext REQUIRED)
|
||||||
if(NOT GETTEXT_XGETTEXT_EXECUTABLE)
|
find_program(CAT_EXECUTABLE cat REQUIRED)
|
||||||
message(FATAL_ERROR "Could not find xgettext executable")
|
|
||||||
endif(NOT GETTEXT_XGETTEXT_EXECUTABLE)
|
|
||||||
|
|
||||||
set (XGETTEXT_OPTIONS
|
list(APPEND XGETTEXT_OPTIONS
|
||||||
--qt
|
--qt
|
||||||
--keyword=tr:1,2c
|
--keyword=tr:1,2c
|
||||||
--keyword=tr --flag=tr:1:pass-c-format --flag=tr:1:pass-qt-format
|
--keyword=tr
|
||||||
--keyword=trUtf8 --flag=tr:1:pass-c-format --flag=tr:1:pass-qt-format
|
--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,3c
|
||||||
--keyword=translate:2 --flag=translate:2:pass-c-format --flag=translate:2:pass-qt-format
|
--keyword=translate:2
|
||||||
--keyword=QT_TR_NOOP --flag=QT_TR_NOOP:1:pass-c-format --flag=QT_TR_NOOP:1:pass-qt-format
|
--flag=translate:2:pass-c-format
|
||||||
--keyword=QT_TRANSLATE_NOOP:2 --flag=QT_TRANSLATE_NOOP:2:pass-c-format --flag=QT_TRANSLATE_NOOP:2:pass-qt-format
|
--flag=translate:2:pass-qt-format
|
||||||
--keyword=_ --flag=_:1:pass-c-format --flag=_:1:pass-qt-format
|
--keyword=QT_TR_NOOP
|
||||||
--keyword=N_ --flag=N_:1:pass-c-format --flag=N_:1:pass-qt-format
|
--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
|
--from-code=utf-8
|
||||||
)
|
)
|
||||||
|
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/translations)
|
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(
|
add_custom_command(
|
||||||
OUTPUT ${pot}
|
OUTPUT ${pot}
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
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 ${GETTEXT_XGETTEXT_EXECUTABLE} ${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}
|
COMMAND cat ${header} ${CMAKE_CURRENT_BINARY_DIR}/pot.temp > ${pot}
|
||||||
DEPENDS ${add_pot_sources} ${header}
|
DEPENDS ${add_pot_sources} ${header}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,27 +1,15 @@
|
|||||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||||
set(STRAWBERRY_VERSION_MINOR 0)
|
set(STRAWBERRY_VERSION_MINOR 1)
|
||||||
set(STRAWBERRY_VERSION_PATCH 23)
|
set(STRAWBERRY_VERSION_PATCH 1)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
set(INCLUDE_GIT_REVISION OFF)
|
set(INCLUDE_GIT_REVISION ON)
|
||||||
|
|
||||||
set(majorminorpatch "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}.${STRAWBERRY_VERSION_PATCH}")
|
set(majorminorpatch "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}.${STRAWBERRY_VERSION_PATCH}")
|
||||||
|
|
||||||
set(STRAWBERRY_VERSION_DISPLAY "${majorminorpatch}")
|
if(FORCE_GIT_REVISION)
|
||||||
set(STRAWBERRY_VERSION_PACKAGE "${majorminorpatch}")
|
set(GIT_REVISION ${FORCE_GIT_REVISION})
|
||||||
set(STRAWBERRY_VERSION_RPM_V "${majorminorpatch}")
|
elseif(INCLUDE_GIT_REVISION AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||||
set(STRAWBERRY_VERSION_RPM_R "1")
|
|
||||||
set(STRAWBERRY_VERSION_PAC_V "${majorminorpatch}")
|
|
||||||
set(STRAWBERRY_VERSION_PAC_R "1")
|
|
||||||
|
|
||||||
if(STRAWBERRY_VERSION_PRERELEASE)
|
|
||||||
set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_DISPLAY} ${STRAWBERRY_VERSION_PRERELEASE}")
|
|
||||||
set(STRAWBERRY_VERSION_RPM_R "0.${STRAWBERRY_VERSION_PRERELEASE}")
|
|
||||||
set(STRAWBERRY_VERSION_PACKAGE "${STRAWBERRY_VERSION_PACKAGE}${STRAWBERRY_VERSION_PRERELEASE}")
|
|
||||||
endif(STRAWBERRY_VERSION_PRERELEASE)
|
|
||||||
|
|
||||||
|
|
||||||
if(INCLUDE_GIT_REVISION AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
|
||||||
|
|
||||||
find_program(GIT_EXECUTABLE git)
|
find_program(GIT_EXECUTABLE git)
|
||||||
if(NOT GIT_EXECUTABLE OR GIT_EXECUTABLE-NOTFOUND)
|
if(NOT GIT_EXECUTABLE OR GIT_EXECUTABLE-NOTFOUND)
|
||||||
@@ -53,10 +41,6 @@ if(INCLUDE_GIT_REVISION AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
|||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(FORCE_GIT_REVISION)
|
|
||||||
set(GIT_REVISION ${FORCE_GIT_REVISION})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(GIT_REVISION)
|
if(GIT_REVISION)
|
||||||
|
|
||||||
string(REGEX REPLACE "^(.+)-([0-9]+)-(g[a-f0-9]+)$" "\\1;\\2;\\3" GIT_PARTS ${GIT_REVISION})
|
string(REGEX REPLACE "^(.+)-([0-9]+)-(g[a-f0-9]+)$" "\\1;\\2;\\3" GIT_PARTS ${GIT_REVISION})
|
||||||
@@ -78,15 +62,23 @@ if(GIT_REVISION)
|
|||||||
|
|
||||||
set(STRAWBERRY_VERSION_DISPLAY "${GIT_REVISION}")
|
set(STRAWBERRY_VERSION_DISPLAY "${GIT_REVISION}")
|
||||||
set(STRAWBERRY_VERSION_PACKAGE "${GIT_TAGNAME}.${GIT_COMMITCOUNT}.${GIT_SHA1}")
|
set(STRAWBERRY_VERSION_PACKAGE "${GIT_TAGNAME}.${GIT_COMMITCOUNT}.${GIT_SHA1}")
|
||||||
set(STRAWBERRY_VERSION_RPM_V "${GIT_TAGNAME}")
|
string(REPLACE "-" "~" STRAWBERRY_VERSION_RPM_V "${GIT_TAGNAME}")
|
||||||
set(STRAWBERRY_VERSION_RPM_R "2.${GIT_COMMITCOUNT}.${GIT_SHA1}")
|
set(STRAWBERRY_VERSION_RPM_R "2.${GIT_COMMITCOUNT}.${GIT_SHA1}")
|
||||||
set(STRAWBERRY_VERSION_PAC_V "${GIT_TAGNAME}.r${GIT_COMMITCOUNT}.${GIT_SHA1}")
|
|
||||||
set(STRAWBERRY_VERSION_PAC_R "1")
|
|
||||||
|
|
||||||
|
else()
|
||||||
|
if(STRAWBERRY_VERSION_PRERELEASE)
|
||||||
|
set(STRAWBERRY_VERSION_DISPLAY "${majorminorpatch}-${STRAWBERRY_VERSION_PRERELEASE}")
|
||||||
|
set(STRAWBERRY_VERSION_RPM_V "${majorminorpatch}~${STRAWBERRY_VERSION_PRERELEASE}")
|
||||||
|
set(STRAWBERRY_VERSION_PACKAGE "${majorminorpatch}${STRAWBERRY_VERSION_PRERELEASE}")
|
||||||
|
else()
|
||||||
|
set(STRAWBERRY_VERSION_DISPLAY "${majorminorpatch}")
|
||||||
|
set(STRAWBERRY_VERSION_PACKAGE "${majorminorpatch}")
|
||||||
|
set(STRAWBERRY_VERSION_RPM_V "${majorminorpatch}")
|
||||||
|
endif()
|
||||||
|
set(STRAWBERRY_VERSION_RPM_R "1")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
message(STATUS "Strawberry Version:")
|
message(STATUS "Strawberry Version:")
|
||||||
message(STATUS "Display: ${STRAWBERRY_VERSION_DISPLAY}")
|
message(STATUS "Display: ${STRAWBERRY_VERSION_DISPLAY}")
|
||||||
message(STATUS "Package: ${STRAWBERRY_VERSION_PACKAGE}")
|
message(STATUS "Package: ${STRAWBERRY_VERSION_PACKAGE}")
|
||||||
message(STATUS "RPM: ${STRAWBERRY_VERSION_RPM_V}-${STRAWBERRY_VERSION_RPM_R}")
|
message(STATUS "RPM: ${STRAWBERRY_VERSION_RPM_V}-${STRAWBERRY_VERSION_RPM_R}")
|
||||||
message(STATUS "PAC: ${STRAWBERRY_VERSION_PAC_V}-${STRAWBERRY_VERSION_PAC_R}")
|
|
||||||
|
|||||||
3
crowdin.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
files:
|
||||||
|
- source: /src/translations/translations.pot
|
||||||
|
translation: /src/translations/%locale_with_underscore%.po
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
<file>schema/schema-16.sql</file>
|
<file>schema/schema-16.sql</file>
|
||||||
<file>schema/schema-17.sql</file>
|
<file>schema/schema-17.sql</file>
|
||||||
<file>schema/schema-18.sql</file>
|
<file>schema/schema-18.sql</file>
|
||||||
|
<file>schema/schema-19.sql</file>
|
||||||
|
<file>schema/schema-20.sql</file>
|
||||||
<file>schema/device-schema.sql</file>
|
<file>schema/device-schema.sql</file>
|
||||||
<file>style/strawberry.css</file>
|
<file>style/strawberry.css</file>
|
||||||
<file>style/smartplaylistsearchterm.css</file>
|
<file>style/smartplaylistsearchterm.css</file>
|
||||||
@@ -42,5 +44,6 @@
|
|||||||
<file>pictures/star-off.png</file>
|
<file>pictures/star-off.png</file>
|
||||||
<file>mood/sample.mood</file>
|
<file>mood/sample.mood</file>
|
||||||
<file>text/ghosts.txt</file>
|
<file>text/ghosts.txt</file>
|
||||||
|
<file>pictures/sidebar-background.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
<file>icons/128x128/love.png</file>
|
<file>icons/128x128/love.png</file>
|
||||||
<file>icons/128x128/subsonic.png</file>
|
<file>icons/128x128/subsonic.png</file>
|
||||||
<file>icons/128x128/tidal.png</file>
|
<file>icons/128x128/tidal.png</file>
|
||||||
|
<file>icons/128x128/spotify.png</file>
|
||||||
<file>icons/128x128/qobuz.png</file>
|
<file>icons/128x128/qobuz.png</file>
|
||||||
<file>icons/128x128/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/128x128/multimedia-player-ipod-standard-black.png</file>
|
||||||
<file>icons/128x128/radio.png</file>
|
<file>icons/128x128/radio.png</file>
|
||||||
@@ -189,6 +190,7 @@
|
|||||||
<file>icons/64x64/love.png</file>
|
<file>icons/64x64/love.png</file>
|
||||||
<file>icons/64x64/subsonic.png</file>
|
<file>icons/64x64/subsonic.png</file>
|
||||||
<file>icons/64x64/tidal.png</file>
|
<file>icons/64x64/tidal.png</file>
|
||||||
|
<file>icons/64x64/spotify.png</file>
|
||||||
<file>icons/64x64/qobuz.png</file>
|
<file>icons/64x64/qobuz.png</file>
|
||||||
<file>icons/64x64/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/64x64/multimedia-player-ipod-standard-black.png</file>
|
||||||
<file>icons/64x64/radio.png</file>
|
<file>icons/64x64/radio.png</file>
|
||||||
@@ -291,6 +293,7 @@
|
|||||||
<file>icons/48x48/love.png</file>
|
<file>icons/48x48/love.png</file>
|
||||||
<file>icons/48x48/subsonic.png</file>
|
<file>icons/48x48/subsonic.png</file>
|
||||||
<file>icons/48x48/tidal.png</file>
|
<file>icons/48x48/tidal.png</file>
|
||||||
|
<file>icons/48x48/spotify.png</file>
|
||||||
<file>icons/48x48/qobuz.png</file>
|
<file>icons/48x48/qobuz.png</file>
|
||||||
<file>icons/48x48/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/48x48/multimedia-player-ipod-standard-black.png</file>
|
||||||
<file>icons/48x48/radio.png</file>
|
<file>icons/48x48/radio.png</file>
|
||||||
@@ -393,6 +396,7 @@
|
|||||||
<file>icons/32x32/love.png</file>
|
<file>icons/32x32/love.png</file>
|
||||||
<file>icons/32x32/subsonic.png</file>
|
<file>icons/32x32/subsonic.png</file>
|
||||||
<file>icons/32x32/tidal.png</file>
|
<file>icons/32x32/tidal.png</file>
|
||||||
|
<file>icons/32x32/spotify.png</file>
|
||||||
<file>icons/32x32/qobuz.png</file>
|
<file>icons/32x32/qobuz.png</file>
|
||||||
<file>icons/32x32/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/32x32/multimedia-player-ipod-standard-black.png</file>
|
||||||
<file>icons/32x32/radio.png</file>
|
<file>icons/32x32/radio.png</file>
|
||||||
@@ -495,6 +499,7 @@
|
|||||||
<file>icons/22x22/love.png</file>
|
<file>icons/22x22/love.png</file>
|
||||||
<file>icons/22x22/subsonic.png</file>
|
<file>icons/22x22/subsonic.png</file>
|
||||||
<file>icons/22x22/tidal.png</file>
|
<file>icons/22x22/tidal.png</file>
|
||||||
|
<file>icons/22x22/spotify.png</file>
|
||||||
<file>icons/22x22/qobuz.png</file>
|
<file>icons/22x22/qobuz.png</file>
|
||||||
<file>icons/22x22/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/22x22/multimedia-player-ipod-standard-black.png</file>
|
||||||
<file>icons/22x22/radio.png</file>
|
<file>icons/22x22/radio.png</file>
|
||||||
|
|||||||
BIN
data/icons/128x128/spotify.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
data/icons/22x22/spotify.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
data/icons/32x32/spotify.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
data/icons/48x48/spotify.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
data/icons/64x64/spotify.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
data/icons/full/spotify.png
Executable file
|
After Width: | Height: | Size: 16 KiB |
BIN
data/pictures/sidebar-background.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
@@ -94,9 +94,4 @@ CREATE INDEX idx_device_%deviceid_songs_album ON device_%deviceid_songs (album);
|
|||||||
|
|
||||||
CREATE INDEX idx_device_%deviceid_songs_comp_artist ON device_%deviceid_songs (compilation_effective, artist);
|
CREATE INDEX idx_device_%deviceid_songs_comp_artist ON device_%deviceid_songs (compilation_effective, artist);
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
|
|
||||||
ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
);
|
|
||||||
|
|
||||||
UPDATE devices SET schema_version=5 WHERE ROWID=%deviceid;
|
UPDATE devices SET schema_version=5 WHERE ROWID=%deviceid;
|
||||||
|
|||||||
19
data/schema/schema-19.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
DROP TABLE IF EXISTS %allsongstables_fts;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS songs_fts;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS subsonic_songs_fts;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS tidal_artists_songs_fts;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS tidal_albums_songs_fts;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS tidal_songs_fts;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS qobuz_artists_songs_fts;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS qobuz_albums_songs_fts;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS qobuz_songs_fts;
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=19;
|
||||||
244
data/schema/schema-20.sql
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS spotify_artists_songs (
|
||||||
|
|
||||||
|
title TEXT,
|
||||||
|
album TEXT,
|
||||||
|
artist TEXT,
|
||||||
|
albumartist TEXT,
|
||||||
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
|
genre TEXT,
|
||||||
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
|
composer TEXT,
|
||||||
|
performer TEXT,
|
||||||
|
grouping TEXT,
|
||||||
|
comment TEXT,
|
||||||
|
lyrics TEXT,
|
||||||
|
|
||||||
|
artist_id TEXT,
|
||||||
|
album_id TEXT,
|
||||||
|
song_id TEXT,
|
||||||
|
|
||||||
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT,
|
||||||
|
|
||||||
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
|
art_automatic TEXT,
|
||||||
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
effective_albumartist TEXT,
|
||||||
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
cue_path TEXT,
|
||||||
|
|
||||||
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
|
ebur128_loudness_range_lu REAL
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS spotify_albums_songs (
|
||||||
|
|
||||||
|
title TEXT,
|
||||||
|
album TEXT,
|
||||||
|
artist TEXT,
|
||||||
|
albumartist TEXT,
|
||||||
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
|
genre TEXT,
|
||||||
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
|
composer TEXT,
|
||||||
|
performer TEXT,
|
||||||
|
grouping TEXT,
|
||||||
|
comment TEXT,
|
||||||
|
lyrics TEXT,
|
||||||
|
|
||||||
|
artist_id TEXT,
|
||||||
|
album_id TEXT,
|
||||||
|
song_id TEXT,
|
||||||
|
|
||||||
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT,
|
||||||
|
|
||||||
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
|
art_automatic TEXT,
|
||||||
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
effective_albumartist TEXT,
|
||||||
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
cue_path TEXT,
|
||||||
|
|
||||||
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
|
ebur128_loudness_range_lu REAL
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS spotify_songs (
|
||||||
|
|
||||||
|
title TEXT,
|
||||||
|
album TEXT,
|
||||||
|
artist TEXT,
|
||||||
|
albumartist TEXT,
|
||||||
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
|
genre TEXT,
|
||||||
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
|
composer TEXT,
|
||||||
|
performer TEXT,
|
||||||
|
grouping TEXT,
|
||||||
|
comment TEXT,
|
||||||
|
lyrics TEXT,
|
||||||
|
|
||||||
|
artist_id TEXT,
|
||||||
|
album_id TEXT,
|
||||||
|
song_id TEXT,
|
||||||
|
|
||||||
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT,
|
||||||
|
|
||||||
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
|
art_automatic TEXT,
|
||||||
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
effective_albumartist TEXT,
|
||||||
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
cue_path TEXT,
|
||||||
|
|
||||||
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
|
ebur128_loudness_range_lu REAL
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=20;
|
||||||
@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
|
|||||||
|
|
||||||
DELETE FROM schema_version;
|
DELETE FROM schema_version;
|
||||||
|
|
||||||
INSERT INTO schema_version (version) VALUES (18);
|
INSERT INTO schema_version (version) VALUES (20);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS directories (
|
CREATE TABLE IF NOT EXISTS directories (
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
@@ -422,6 +422,249 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
|||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS spotify_artists_songs (
|
||||||
|
|
||||||
|
title TEXT,
|
||||||
|
album TEXT,
|
||||||
|
artist TEXT,
|
||||||
|
albumartist TEXT,
|
||||||
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
|
genre TEXT,
|
||||||
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
|
composer TEXT,
|
||||||
|
performer TEXT,
|
||||||
|
grouping TEXT,
|
||||||
|
comment TEXT,
|
||||||
|
lyrics TEXT,
|
||||||
|
|
||||||
|
artist_id TEXT,
|
||||||
|
album_id TEXT,
|
||||||
|
song_id TEXT,
|
||||||
|
|
||||||
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT,
|
||||||
|
|
||||||
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
|
art_automatic TEXT,
|
||||||
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
effective_albumartist TEXT,
|
||||||
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
cue_path TEXT,
|
||||||
|
|
||||||
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
|
ebur128_loudness_range_lu REAL
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS spotify_albums_songs (
|
||||||
|
|
||||||
|
title TEXT,
|
||||||
|
album TEXT,
|
||||||
|
artist TEXT,
|
||||||
|
albumartist TEXT,
|
||||||
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
|
genre TEXT,
|
||||||
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
|
composer TEXT,
|
||||||
|
performer TEXT,
|
||||||
|
grouping TEXT,
|
||||||
|
comment TEXT,
|
||||||
|
lyrics TEXT,
|
||||||
|
|
||||||
|
artist_id TEXT,
|
||||||
|
album_id TEXT,
|
||||||
|
song_id TEXT,
|
||||||
|
|
||||||
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT,
|
||||||
|
|
||||||
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
|
art_automatic TEXT,
|
||||||
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
effective_albumartist TEXT,
|
||||||
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
cue_path TEXT,
|
||||||
|
|
||||||
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
|
ebur128_loudness_range_lu REAL
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS spotify_songs (
|
||||||
|
|
||||||
|
title TEXT,
|
||||||
|
album TEXT,
|
||||||
|
artist TEXT,
|
||||||
|
albumartist TEXT,
|
||||||
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
|
genre TEXT,
|
||||||
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
|
composer TEXT,
|
||||||
|
performer TEXT,
|
||||||
|
grouping TEXT,
|
||||||
|
comment TEXT,
|
||||||
|
lyrics TEXT,
|
||||||
|
|
||||||
|
artist_id TEXT,
|
||||||
|
album_id TEXT,
|
||||||
|
song_id TEXT,
|
||||||
|
|
||||||
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||||
|
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT,
|
||||||
|
|
||||||
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
|
art_automatic TEXT,
|
||||||
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
effective_albumartist TEXT,
|
||||||
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
cue_path TEXT,
|
||||||
|
|
||||||
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
|
ebur128_loudness_range_lu REAL
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
@@ -796,138 +1039,3 @@ CREATE INDEX IF NOT EXISTS idx_album ON songs (album);
|
|||||||
CREATE INDEX IF NOT EXISTS idx_title ON songs (title);
|
CREATE INDEX IF NOT EXISTS idx_title ON songs (title);
|
||||||
|
|
||||||
CREATE VIEW IF NOT EXISTS duplicated_songs as select artist dup_artist, album dup_album, title dup_title from songs as inner_songs where artist != '' and album != '' and title != '' and unavailable = 0 group by artist, album , title having count(*) > 1;
|
CREATE VIEW IF NOT EXISTS duplicated_songs as select artist dup_artist, album dup_album, title dup_title from songs as inner_songs where artist != '' and album != '' and title != '' and unavailable = 0 group by artist, album , title having count(*) > 1;
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS songs_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_artists_songs_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_albums_songs_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_songs_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS %allsongstables_fts USING fts5(
|
|
||||||
|
|
||||||
ftstitle,
|
|
||||||
ftsalbum,
|
|
||||||
ftsartist,
|
|
||||||
ftsalbumartist,
|
|
||||||
ftscomposer,
|
|
||||||
ftsperformer,
|
|
||||||
ftsgrouping,
|
|
||||||
ftsgenre,
|
|
||||||
ftscomment,
|
|
||||||
tokenize = "unicode61 remove_diacritics 1"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -35,32 +35,32 @@
|
|||||||
background-color: %palette-base;
|
background-color: %palette-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
QToolButton {
|
QToolButton[accessibleName="MenuPopupToolButton"] {
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
QToolButton:hover {
|
QToolButton:hover[accessibleName="MenuPopupToolButton"] {
|
||||||
border: 2px solid %palette-highlight;
|
border: 2px solid %palette-highlight;
|
||||||
background-color: %palette-highlight-lighter;
|
background-color: %palette-highlight-lighter;
|
||||||
}
|
}
|
||||||
|
|
||||||
QToolButton:pressed {
|
QToolButton:pressed[accessibleName="MenuPopupToolButton"] {
|
||||||
border: 2px solid %palette-highlight-darker;
|
border: 2px solid %palette-highlight-darker;
|
||||||
background-color: %palette-highlight-lighter;
|
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;
|
padding-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For backwards compatibility with Qt 5 as it does not support property name */
|
/* 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;
|
padding-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
QToolButton::menu-button {
|
QToolButton::menu-button[accessibleName="MenuPopupToolButton"] {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|||||||
2
debian/control.in
vendored
@@ -53,7 +53,7 @@ Description: music player and music collection organizer
|
|||||||
- Edit tags on audio files
|
- Edit tags on audio files
|
||||||
- Automatically retrieve tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- 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 analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
|
|||||||
5
dist/CMakeLists.txt
vendored
@@ -4,6 +4,11 @@ if(RPM_DISTRO AND RPM_DATE)
|
|||||||
endif(RPM_DISTRO AND RPM_DATE)
|
endif(RPM_DISTRO AND RPM_DATE)
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
|
if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET})
|
||||||
|
set(LSMinimumSystemVersion $ENV{MACOSX_DEPLOYMENT_TARGET})
|
||||||
|
else()
|
||||||
|
set(LSMinimumSystemVersion 12.0)
|
||||||
|
endif()
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
|
||||||
endif(APPLE)
|
endif(APPLE)
|
||||||
|
|
||||||
|
|||||||
2
dist/macos/Info.plist.in
vendored
@@ -33,7 +33,7 @@
|
|||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.music</string>
|
<string>public.app-category.music</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>11.0</string>
|
<string>@LSMinimumSystemVersion@</string>
|
||||||
<key>SUFeedURL</key>
|
<key>SUFeedURL</key>
|
||||||
<string>https://www.strawberrymusicplayer.org/sparkle-macos</string>
|
<string>https://www.strawberrymusicplayer.org/sparkle-macos</string>
|
||||||
<key>SUPublicEDKey</key>
|
<key>SUPublicEDKey</key>
|
||||||
|
|||||||
10
dist/macos/macgstcopy.sh
vendored
@@ -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
|
install_name_tool -add_rpath "@loader_path/../Frameworks" "${bundledir}/Contents/PlugIns/$(basename ${GST_PLUGIN_SCANNER})" || exit 1
|
||||||
|
|
||||||
gst_plugins="
|
gst_plugins="
|
||||||
|
libgstadaptivedemux2
|
||||||
libgstaes
|
libgstaes
|
||||||
libgstaiff
|
libgstaiff
|
||||||
libgstapetag
|
libgstapetag
|
||||||
@@ -70,16 +71,14 @@ libgstasf
|
|||||||
libgstasfmux
|
libgstasfmux
|
||||||
libgstaudioconvert
|
libgstaudioconvert
|
||||||
libgstaudiofx
|
libgstaudiofx
|
||||||
libgstaudiomixer
|
|
||||||
libgstaudioparsers
|
libgstaudioparsers
|
||||||
libgstaudiorate
|
|
||||||
libgstaudioresample
|
libgstaudioresample
|
||||||
libgstaudiotestsrc
|
|
||||||
libgstautodetect
|
libgstautodetect
|
||||||
libgstbs2b
|
libgstbs2b
|
||||||
libgstcdio
|
libgstcdio
|
||||||
libgstcoreelements
|
libgstcoreelements
|
||||||
libgstdash
|
libgstdash
|
||||||
|
libgstdsd
|
||||||
libgstequalizer
|
libgstequalizer
|
||||||
libgstfaac
|
libgstfaac
|
||||||
libgstfaad
|
libgstfaad
|
||||||
@@ -92,6 +91,10 @@ libgstid3demux
|
|||||||
libgstid3tag
|
libgstid3tag
|
||||||
libgstisomp4
|
libgstisomp4
|
||||||
libgstlame
|
libgstlame
|
||||||
|
libgstmpegpsdemux
|
||||||
|
libgstmpegpsmux
|
||||||
|
libgstmpegtsdemux
|
||||||
|
libgstmpegtsmux
|
||||||
libgstlibav
|
libgstlibav
|
||||||
libgstmpg123
|
libgstmpg123
|
||||||
libgstmusepack
|
libgstmusepack
|
||||||
@@ -108,6 +111,7 @@ libgstrtsp
|
|||||||
libgstsoup
|
libgstsoup
|
||||||
libgstspectrum
|
libgstspectrum
|
||||||
libgstspeex
|
libgstspeex
|
||||||
|
libgstspotify
|
||||||
libgsttaglib
|
libgsttaglib
|
||||||
libgsttcp
|
libgsttcp
|
||||||
libgsttwolame
|
libgsttwolame
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<li>Edit tags on audio files</li>
|
<li>Edit tags on audio files</li>
|
||||||
<li>Automatically retrieve tags from MusicBrainz</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>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>Support for multiple backends</li>
|
||||||
<li>Audio analyzer and equalizer</li>
|
<li>Audio analyzer and equalizer</li>
|
||||||
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||||
@@ -50,6 +50,8 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="1.1.1" date="2024-07-22"/>
|
||||||
|
<release version="1.1.0" date="2024-07-14"/>
|
||||||
<release version="1.0.23" date="2024-01-11"/>
|
<release version="1.0.23" date="2024-01-11"/>
|
||||||
<release version="1.0.22" date="2023-12-09"/>
|
<release version="1.0.22" date="2023-12-09"/>
|
||||||
<release version="1.0.21" date="2023-10-21"/>
|
<release version="1.0.21" date="2023-10-21"/>
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ Version=1.0
|
|||||||
Type=Application
|
Type=Application
|
||||||
Name=Strawberry
|
Name=Strawberry
|
||||||
GenericName=Strawberry Music Player
|
GenericName=Strawberry Music Player
|
||||||
|
GenericName[fr]=Lecteur de musique Strawberry
|
||||||
GenericName[ru]=Музыкальный проигрыватель Strawberry
|
GenericName[ru]=Музыкальный проигрыватель Strawberry
|
||||||
Comment=Plays music
|
Comment=Plays music
|
||||||
|
Comment[fr]=Joue de la musique
|
||||||
Comment[ru]=Прослушивание музыки
|
Comment[ru]=Прослушивание музыки
|
||||||
Exec=strawberry %U
|
Exec=strawberry %U
|
||||||
TryExec=strawberry
|
TryExec=strawberry
|
||||||
|
|||||||
2
dist/unix/strawberry.spec.in
vendored
@@ -99,7 +99,7 @@ Features:
|
|||||||
- Edit tags on audio files
|
- Edit tags on audio files
|
||||||
- Automatically retrieve tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- 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
|
- Support for multiple backends
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
|
|||||||
103
dist/windows/strawberry.nsi.in
vendored
@@ -109,7 +109,11 @@
|
|||||||
|
|
||||||
Unicode True
|
Unicode True
|
||||||
|
|
||||||
|
!ifdef debug
|
||||||
|
SetCompressor lzma
|
||||||
|
!else
|
||||||
SetCompressor /SOLID lzma
|
SetCompressor /SOLID lzma
|
||||||
|
!endif
|
||||||
|
|
||||||
!include "MUI2.nsh"
|
!include "MUI2.nsh"
|
||||||
!include "FileAssociation.nsh"
|
!include "FileAssociation.nsh"
|
||||||
@@ -257,7 +261,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "libFLAC-12.dll"
|
File "libFLAC-12.dll"
|
||||||
File "libbrotlicommon.dll"
|
File "libbrotlicommon.dll"
|
||||||
File "libbrotlidec.dll"
|
File "libbrotlidec.dll"
|
||||||
File "libbrotlienc.dll"
|
|
||||||
File "libbs2b-0.dll"
|
File "libbs2b-0.dll"
|
||||||
File "libbz2.dll"
|
File "libbz2.dll"
|
||||||
File "libchromaprint.dll"
|
File "libchromaprint.dll"
|
||||||
@@ -282,8 +285,10 @@ Section "Strawberry" Strawberry
|
|||||||
File "libgstaudio-1.0-0.dll"
|
File "libgstaudio-1.0-0.dll"
|
||||||
File "libgstbadaudio-1.0-0.dll"
|
File "libgstbadaudio-1.0-0.dll"
|
||||||
File "libgstbase-1.0-0.dll"
|
File "libgstbase-1.0-0.dll"
|
||||||
|
File "libgstcodecparsers-1.0-0.dll"
|
||||||
File "libgstfft-1.0-0.dll"
|
File "libgstfft-1.0-0.dll"
|
||||||
File "libgstisoff-1.0-0.dll"
|
File "libgstisoff-1.0-0.dll"
|
||||||
|
File "libgstmpegts-1.0-0.dll"
|
||||||
File "libgstnet-1.0-0.dll"
|
File "libgstnet-1.0-0.dll"
|
||||||
File "libgstpbutils-1.0-0.dll"
|
File "libgstpbutils-1.0-0.dll"
|
||||||
File "libgstreamer-1.0-0.dll"
|
File "libgstreamer-1.0-0.dll"
|
||||||
@@ -328,7 +333,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "libvorbisfile-3.dll"
|
File "libvorbisfile-3.dll"
|
||||||
File "libwavpack-1.dll"
|
File "libwavpack-1.dll"
|
||||||
File "libwinpthread-1.dll"
|
File "libwinpthread-1.dll"
|
||||||
File "libxml2-2.dll"
|
|
||||||
File "libzstd.dll"
|
File "libzstd.dll"
|
||||||
File "zlib1.dll"
|
File "zlib1.dll"
|
||||||
|
|
||||||
@@ -422,8 +426,10 @@ Section "Strawberry" Strawberry
|
|||||||
File "gstaudio-1.0-0.dll"
|
File "gstaudio-1.0-0.dll"
|
||||||
File "gstbadaudio-1.0-0.dll"
|
File "gstbadaudio-1.0-0.dll"
|
||||||
File "gstbase-1.0-0.dll"
|
File "gstbase-1.0-0.dll"
|
||||||
|
File "gstcodecparsers-1.0-0.dll"
|
||||||
File "gstfft-1.0-0.dll"
|
File "gstfft-1.0-0.dll"
|
||||||
File "gstisoff-1.0-0.dll"
|
File "gstisoff-1.0-0.dll"
|
||||||
|
File "gstmpegts-1.0-0.dll"
|
||||||
File "gstnet-1.0-0.dll"
|
File "gstnet-1.0-0.dll"
|
||||||
File "gstpbutils-1.0-0.dll"
|
File "gstpbutils-1.0-0.dll"
|
||||||
File "gstreamer-1.0-0.dll"
|
File "gstreamer-1.0-0.dll"
|
||||||
@@ -465,7 +471,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "libiconv.dll"
|
File "libiconv.dll"
|
||||||
File "libpng16.dll"
|
File "libpng16.dll"
|
||||||
File "libspeex.dll"
|
File "libspeex.dll"
|
||||||
File "libxml2.dll"
|
|
||||||
File "pcre2-8.dll"
|
File "pcre2-8.dll"
|
||||||
File "pcre2-16.dll"
|
File "pcre2-16.dll"
|
||||||
File "twolame.dll"
|
File "twolame.dll"
|
||||||
@@ -476,7 +481,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "libiconvd.dll"
|
File "libiconvd.dll"
|
||||||
File "libpng16d.dll"
|
File "libpng16d.dll"
|
||||||
File "libspeexd.dll"
|
File "libspeexd.dll"
|
||||||
File "libxml2d.dll"
|
|
||||||
File "pcre2-8d.dll"
|
File "pcre2-8d.dll"
|
||||||
File "pcre2-16d.dll"
|
File "pcre2-16d.dll"
|
||||||
File "twolamed.dll"
|
File "twolamed.dll"
|
||||||
@@ -493,7 +497,7 @@ Section "Strawberry" Strawberry
|
|||||||
|
|
||||||
; Common files
|
; Common files
|
||||||
|
|
||||||
File "icudt74.dll"
|
File "icudt75.dll"
|
||||||
File "libfftw3-3.dll"
|
File "libfftw3-3.dll"
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
File "libprotobufd.dll"
|
File "libprotobufd.dll"
|
||||||
@@ -501,8 +505,9 @@ Section "Strawberry" Strawberry
|
|||||||
File "libprotobuf.dll"
|
File "libprotobuf.dll"
|
||||||
!endif
|
!endif
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
File "icuin74d.dll"
|
File "icuin75d.dll"
|
||||||
File "icuuc74d.dll"
|
File "icuuc75d.dll"
|
||||||
|
File "libxml2d.dll"
|
||||||
File "Qt6Concurrentd.dll"
|
File "Qt6Concurrentd.dll"
|
||||||
File "Qt6Cored.dll"
|
File "Qt6Cored.dll"
|
||||||
File "Qt6Guid.dll"
|
File "Qt6Guid.dll"
|
||||||
@@ -510,8 +515,9 @@ Section "Strawberry" Strawberry
|
|||||||
File "Qt6Sqld.dll"
|
File "Qt6Sqld.dll"
|
||||||
File "Qt6Widgetsd.dll"
|
File "Qt6Widgetsd.dll"
|
||||||
!else
|
!else
|
||||||
File "icuin74.dll"
|
File "icuin75.dll"
|
||||||
File "icuuc74.dll"
|
File "icuuc75.dll"
|
||||||
|
File "libxml2.dll"
|
||||||
File "Qt6Concurrent.dll"
|
File "Qt6Concurrent.dll"
|
||||||
File "Qt6Core.dll"
|
File "Qt6Core.dll"
|
||||||
File "Qt6Gui.dll"
|
File "Qt6Gui.dll"
|
||||||
@@ -584,9 +590,9 @@ SectionEnd
|
|||||||
Section "Qt styles" styles
|
Section "Qt styles" styles
|
||||||
SetOutPath "$INSTDIR\styles"
|
SetOutPath "$INSTDIR\styles"
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
File "/oname=qwindowsvistastyled.dll" "styles\qwindowsvistastyled.dll"
|
File "/oname=qmodernwindowsstyled.dll" "styles\qmodernwindowsstyled.dll"
|
||||||
!else
|
!else
|
||||||
File "/oname=qwindowsvistastyle.dll" "styles\qwindowsvistastyle.dll"
|
File "/oname=qmodernwindowsstyle.dll" "styles\qmodernwindowsstyle.dll"
|
||||||
!endif
|
!endif
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
@@ -627,6 +633,7 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
SetOutPath "$INSTDIR\gstreamer-plugins"
|
SetOutPath "$INSTDIR\gstreamer-plugins"
|
||||||
|
|
||||||
!ifdef mingw
|
!ifdef mingw
|
||||||
|
File "/oname=libgstadaptivedemux2.dll" "gstreamer-plugins\libgstadaptivedemux2.dll"
|
||||||
File "/oname=libgstaes.dll" "gstreamer-plugins\libgstaes.dll"
|
File "/oname=libgstaes.dll" "gstreamer-plugins\libgstaes.dll"
|
||||||
File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll"
|
File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll"
|
||||||
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
|
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
|
||||||
@@ -635,16 +642,14 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll"
|
File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll"
|
||||||
File "/oname=libgstaudioconvert.dll" "gstreamer-plugins\libgstaudioconvert.dll"
|
File "/oname=libgstaudioconvert.dll" "gstreamer-plugins\libgstaudioconvert.dll"
|
||||||
File "/oname=libgstaudiofx.dll" "gstreamer-plugins\libgstaudiofx.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=libgstaudioparsers.dll" "gstreamer-plugins\libgstaudioparsers.dll"
|
||||||
File "/oname=libgstaudiorate.dll" "gstreamer-plugins\libgstaudiorate.dll"
|
|
||||||
File "/oname=libgstaudioresample.dll" "gstreamer-plugins\libgstaudioresample.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=libgstautodetect.dll" "gstreamer-plugins\libgstautodetect.dll"
|
||||||
File "/oname=libgstbs2b.dll" "gstreamer-plugins\libgstbs2b.dll"
|
File "/oname=libgstbs2b.dll" "gstreamer-plugins\libgstbs2b.dll"
|
||||||
File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll"
|
File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll"
|
||||||
File "/oname=libgstdash.dll" "gstreamer-plugins\libgstdash.dll"
|
File "/oname=libgstdash.dll" "gstreamer-plugins\libgstdash.dll"
|
||||||
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.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=libgstequalizer.dll" "gstreamer-plugins\libgstequalizer.dll"
|
||||||
File "/oname=libgstfaac.dll" "gstreamer-plugins\libgstfaac.dll"
|
File "/oname=libgstfaac.dll" "gstreamer-plugins\libgstfaac.dll"
|
||||||
File "/oname=libgstfaad.dll" "gstreamer-plugins\libgstfaad.dll"
|
File "/oname=libgstfaad.dll" "gstreamer-plugins\libgstfaad.dll"
|
||||||
@@ -659,6 +664,10 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=libgstisomp4.dll" "gstreamer-plugins\libgstisomp4.dll"
|
File "/oname=libgstisomp4.dll" "gstreamer-plugins\libgstisomp4.dll"
|
||||||
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
|
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
|
||||||
File "/oname=libgstlibav.dll" "gstreamer-plugins\libgstlibav.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=libgstmpg123.dll" "gstreamer-plugins\libgstmpg123.dll"
|
||||||
File "/oname=libgstmusepack.dll" "gstreamer-plugins\libgstmusepack.dll"
|
File "/oname=libgstmusepack.dll" "gstreamer-plugins\libgstmusepack.dll"
|
||||||
File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll"
|
File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll"
|
||||||
@@ -681,6 +690,7 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll"
|
File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll"
|
||||||
File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll"
|
File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll"
|
||||||
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.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=libgstwavenc.dll" "gstreamer-plugins\libgstwavenc.dll"
|
||||||
File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
|
File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
|
||||||
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
|
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
|
||||||
@@ -688,24 +698,24 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
!endif ; MinGW
|
!endif ; MinGW
|
||||||
|
|
||||||
!ifdef msvc
|
!ifdef msvc
|
||||||
|
File "/oname=gstadaptivedemux2.dll" "gstreamer-plugins\gstadaptivedemux2.dll"
|
||||||
File "/oname=gstaes.dll" "gstreamer-plugins\gstaes.dll"
|
File "/oname=gstaes.dll" "gstreamer-plugins\gstaes.dll"
|
||||||
File "/oname=gstaiff.dll" "gstreamer-plugins\gstaiff.dll"
|
File "/oname=gstaiff.dll" "gstreamer-plugins\gstaiff.dll"
|
||||||
File "/oname=gstapetag.dll" "gstreamer-plugins\gstapetag.dll"
|
File "/oname=gstapetag.dll" "gstreamer-plugins\gstapetag.dll"
|
||||||
File "/oname=gstapp.dll" "gstreamer-plugins\gstapp.dll"
|
File "/oname=gstapp.dll" "gstreamer-plugins\gstapp.dll"
|
||||||
File "/oname=gstasf.dll" "gstreamer-plugins\gstasf.dll"
|
File "/oname=gstasf.dll" "gstreamer-plugins\gstasf.dll"
|
||||||
File "/oname=gstasfmux.dll" "gstreamer-plugins\gstasfmux.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=gstaudioconvert.dll" "gstreamer-plugins\gstaudioconvert.dll"
|
||||||
File "/oname=gstaudiofx.dll" "gstreamer-plugins\gstaudiofx.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=gstaudioparsers.dll" "gstreamer-plugins\gstaudioparsers.dll"
|
||||||
File "/oname=gstaudiorate.dll" "gstreamer-plugins\gstaudiorate.dll"
|
|
||||||
File "/oname=gstaudioresample.dll" "gstreamer-plugins\gstaudioresample.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=gstautodetect.dll" "gstreamer-plugins\gstautodetect.dll"
|
||||||
File "/oname=gstbs2b.dll" "gstreamer-plugins\gstbs2b.dll"
|
File "/oname=gstbs2b.dll" "gstreamer-plugins\gstbs2b.dll"
|
||||||
File "/oname=gstcoreelements.dll" "gstreamer-plugins\gstcoreelements.dll"
|
File "/oname=gstcoreelements.dll" "gstreamer-plugins\gstcoreelements.dll"
|
||||||
File "/oname=gstdash.dll" "gstreamer-plugins\gstdash.dll"
|
File "/oname=gstdash.dll" "gstreamer-plugins\gstdash.dll"
|
||||||
File "/oname=gstdirectsound.dll" "gstreamer-plugins\gstdirectsound.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=gstequalizer.dll" "gstreamer-plugins\gstequalizer.dll"
|
||||||
File "/oname=gstfaac.dll" "gstreamer-plugins\gstfaac.dll"
|
File "/oname=gstfaac.dll" "gstreamer-plugins\gstfaac.dll"
|
||||||
File "/oname=gstfaad.dll" "gstreamer-plugins\gstfaad.dll"
|
File "/oname=gstfaad.dll" "gstreamer-plugins\gstfaad.dll"
|
||||||
@@ -720,6 +730,10 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=gstisomp4.dll" "gstreamer-plugins\gstisomp4.dll"
|
File "/oname=gstisomp4.dll" "gstreamer-plugins\gstisomp4.dll"
|
||||||
File "/oname=gstlame.dll" "gstreamer-plugins\gstlame.dll"
|
File "/oname=gstlame.dll" "gstreamer-plugins\gstlame.dll"
|
||||||
File "/oname=gstlibav.dll" "gstreamer-plugins\gstlibav.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=gstmpg123.dll" "gstreamer-plugins\gstmpg123.dll"
|
||||||
File "/oname=gstmusepack.dll" "gstreamer-plugins\gstmusepack.dll"
|
File "/oname=gstmusepack.dll" "gstreamer-plugins\gstmusepack.dll"
|
||||||
File "/oname=gstogg.dll" "gstreamer-plugins\gstogg.dll"
|
File "/oname=gstogg.dll" "gstreamer-plugins\gstogg.dll"
|
||||||
@@ -742,12 +756,15 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
|
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
|
||||||
File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.dll"
|
File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.dll"
|
||||||
File "/oname=gstwasapi.dll" "gstreamer-plugins\gstwasapi.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=gstwavenc.dll" "gstreamer-plugins\gstwavenc.dll"
|
||||||
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
|
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
|
||||||
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
|
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
|
||||||
File "/oname=gstxingmux.dll" "gstreamer-plugins\gstxingmux.dll"
|
File "/oname=gstxingmux.dll" "gstreamer-plugins\gstxingmux.dll"
|
||||||
|
!ifdef arch_x64
|
||||||
|
File "/oname=gstspotify.dll" "gstreamer-plugins\gstspotify.dll"
|
||||||
|
!endif
|
||||||
!endif ; MSVC
|
!endif ; MSVC
|
||||||
|
|
||||||
SectionEnd
|
SectionEnd
|
||||||
@@ -814,7 +831,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libFLAC-12.dll"
|
Delete "$INSTDIR\libFLAC-12.dll"
|
||||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||||
Delete "$INSTDIR\libbrotlidec.dll"
|
Delete "$INSTDIR\libbrotlidec.dll"
|
||||||
Delete "$INSTDIR\libbrotlienc.dll"
|
|
||||||
Delete "$INSTDIR\libbs2b-0.dll"
|
Delete "$INSTDIR\libbs2b-0.dll"
|
||||||
Delete "$INSTDIR\libbz2.dll"
|
Delete "$INSTDIR\libbz2.dll"
|
||||||
Delete "$INSTDIR\libchromaprint.dll"
|
Delete "$INSTDIR\libchromaprint.dll"
|
||||||
@@ -839,8 +855,10 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libgstaudio-1.0-0.dll"
|
Delete "$INSTDIR\libgstaudio-1.0-0.dll"
|
||||||
Delete "$INSTDIR\libgstbadaudio-1.0-0.dll"
|
Delete "$INSTDIR\libgstbadaudio-1.0-0.dll"
|
||||||
Delete "$INSTDIR\libgstbase-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\libgstfft-1.0-0.dll"
|
||||||
Delete "$INSTDIR\libgstisoff-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\libgstnet-1.0-0.dll"
|
||||||
Delete "$INSTDIR\libgstpbutils-1.0-0.dll"
|
Delete "$INSTDIR\libgstpbutils-1.0-0.dll"
|
||||||
Delete "$INSTDIR\libgstreamer-1.0-0.dll"
|
Delete "$INSTDIR\libgstreamer-1.0-0.dll"
|
||||||
@@ -885,7 +903,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libvorbisfile-3.dll"
|
Delete "$INSTDIR\libvorbisfile-3.dll"
|
||||||
Delete "$INSTDIR\libwavpack-1.dll"
|
Delete "$INSTDIR\libwavpack-1.dll"
|
||||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||||
Delete "$INSTDIR\libxml2-2.dll"
|
|
||||||
Delete "$INSTDIR\libzstd.dll"
|
Delete "$INSTDIR\libzstd.dll"
|
||||||
Delete "$INSTDIR\zlib1.dll"
|
Delete "$INSTDIR\zlib1.dll"
|
||||||
|
|
||||||
@@ -979,8 +996,10 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstaudio-1.0-0.dll"
|
Delete "$INSTDIR\gstaudio-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gstbadaudio-1.0-0.dll"
|
Delete "$INSTDIR\gstbadaudio-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gstbase-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\gstfft-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gstisoff-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\gstnet-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gstpbutils-1.0-0.dll"
|
Delete "$INSTDIR\gstpbutils-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gstreamer-1.0-0.dll"
|
Delete "$INSTDIR\gstreamer-1.0-0.dll"
|
||||||
@@ -1022,7 +1041,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libiconv.dll"
|
Delete "$INSTDIR\libiconv.dll"
|
||||||
Delete "$INSTDIR\libpng16.dll"
|
Delete "$INSTDIR\libpng16.dll"
|
||||||
Delete "$INSTDIR\libspeex.dll"
|
Delete "$INSTDIR\libspeex.dll"
|
||||||
Delete "$INSTDIR\libxml2.dll"
|
|
||||||
Delete "$INSTDIR\pcre2-8.dll"
|
Delete "$INSTDIR\pcre2-8.dll"
|
||||||
Delete "$INSTDIR\pcre2-16.dll"
|
Delete "$INSTDIR\pcre2-16.dll"
|
||||||
Delete "$INSTDIR\twolame.dll"
|
Delete "$INSTDIR\twolame.dll"
|
||||||
@@ -1033,7 +1051,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libiconvd.dll"
|
Delete "$INSTDIR\libiconvd.dll"
|
||||||
Delete "$INSTDIR\libpng16d.dll"
|
Delete "$INSTDIR\libpng16d.dll"
|
||||||
Delete "$INSTDIR\libspeexd.dll"
|
Delete "$INSTDIR\libspeexd.dll"
|
||||||
Delete "$INSTDIR\libxml2d.dll"
|
|
||||||
Delete "$INSTDIR\pcre2-8d.dll"
|
Delete "$INSTDIR\pcre2-8d.dll"
|
||||||
Delete "$INSTDIR\pcre2-16d.dll"
|
Delete "$INSTDIR\pcre2-16d.dll"
|
||||||
Delete "$INSTDIR\twolamed.dll"
|
Delete "$INSTDIR\twolamed.dll"
|
||||||
@@ -1049,7 +1066,7 @@ Section "Uninstall"
|
|||||||
|
|
||||||
; Common files
|
; Common files
|
||||||
|
|
||||||
Delete "$INSTDIR\icudt74.dll"
|
Delete "$INSTDIR\icudt75.dll"
|
||||||
Delete "$INSTDIR\libfftw3-3.dll"
|
Delete "$INSTDIR\libfftw3-3.dll"
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
Delete "$INSTDIR\libprotobufd.dll"
|
Delete "$INSTDIR\libprotobufd.dll"
|
||||||
@@ -1057,8 +1074,9 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libprotobuf.dll"
|
Delete "$INSTDIR\libprotobuf.dll"
|
||||||
!endif
|
!endif
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
Delete "$INSTDIR\icuin74d.dll"
|
Delete "$INSTDIR\icuin75d.dll"
|
||||||
Delete "$INSTDIR\icuuc74d.dll"
|
Delete "$INSTDIR\icuuc75d.dll"
|
||||||
|
Delete "$INSTDIR\libxml2d.dll"
|
||||||
Delete "$INSTDIR\Qt6Concurrentd.dll"
|
Delete "$INSTDIR\Qt6Concurrentd.dll"
|
||||||
Delete "$INSTDIR\Qt6Cored.dll"
|
Delete "$INSTDIR\Qt6Cored.dll"
|
||||||
Delete "$INSTDIR\Qt6Guid.dll"
|
Delete "$INSTDIR\Qt6Guid.dll"
|
||||||
@@ -1066,8 +1084,9 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\Qt6Sqld.dll"
|
Delete "$INSTDIR\Qt6Sqld.dll"
|
||||||
Delete "$INSTDIR\Qt6Widgetsd.dll"
|
Delete "$INSTDIR\Qt6Widgetsd.dll"
|
||||||
!else
|
!else
|
||||||
Delete "$INSTDIR\icuin74.dll"
|
Delete "$INSTDIR\icuin75.dll"
|
||||||
Delete "$INSTDIR\icuuc74.dll"
|
Delete "$INSTDIR\icuuc75.dll"
|
||||||
|
Delete "$INSTDIR\libxml2.dll"
|
||||||
Delete "$INSTDIR\Qt6Concurrent.dll"
|
Delete "$INSTDIR\Qt6Concurrent.dll"
|
||||||
Delete "$INSTDIR\Qt6Core.dll"
|
Delete "$INSTDIR\Qt6Core.dll"
|
||||||
Delete "$INSTDIR\Qt6Gui.dll"
|
Delete "$INSTDIR\Qt6Gui.dll"
|
||||||
@@ -1095,7 +1114,7 @@ Section "Uninstall"
|
|||||||
|
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
Delete "$INSTDIR\platforms\qwindowsd.dll"
|
Delete "$INSTDIR\platforms\qwindowsd.dll"
|
||||||
Delete "$INSTDIR\styles\qwindowsvistastyled.dll"
|
Delete "$INSTDIR\styles\qmodernwindowsstyled.dll"
|
||||||
Delete "$INSTDIR\tls\qschannelbackendd.dll"
|
Delete "$INSTDIR\tls\qschannelbackendd.dll"
|
||||||
Delete "$INSTDIR\tls\qopensslbackendd.dll"
|
Delete "$INSTDIR\tls\qopensslbackendd.dll"
|
||||||
Delete "$INSTDIR\sqldrivers\qsqlited.dll"
|
Delete "$INSTDIR\sqldrivers\qsqlited.dll"
|
||||||
@@ -1104,7 +1123,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\imageformats\qjpegd.dll"
|
Delete "$INSTDIR\imageformats\qjpegd.dll"
|
||||||
!else
|
!else
|
||||||
Delete "$INSTDIR\platforms\qwindows.dll"
|
Delete "$INSTDIR\platforms\qwindows.dll"
|
||||||
Delete "$INSTDIR\styles\qwindowsvistastyle.dll"
|
Delete "$INSTDIR\styles\qmodernwindowsstyle.dll"
|
||||||
Delete "$INSTDIR\tls\qschannelbackend.dll"
|
Delete "$INSTDIR\tls\qschannelbackend.dll"
|
||||||
Delete "$INSTDIR\tls\qopensslbackend.dll"
|
Delete "$INSTDIR\tls\qopensslbackend.dll"
|
||||||
Delete "$INSTDIR\sqldrivers\qsqlite.dll"
|
Delete "$INSTDIR\sqldrivers\qsqlite.dll"
|
||||||
@@ -1116,6 +1135,7 @@ Section "Uninstall"
|
|||||||
; MinGW GStreamer plugins
|
; MinGW GStreamer plugins
|
||||||
|
|
||||||
!ifdef mingw
|
!ifdef mingw
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\libgstadaptivedemux2.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaes.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstaes.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaiff.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstaiff.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstapetag.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstapetag.dll"
|
||||||
@@ -1124,16 +1144,14 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\libgstasfmux.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstasfmux.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudioconvert.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstaudioconvert.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudiofx.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstaudiofx.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudiomixer.dll"
|
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudioparsers.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstaudioparsers.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudiorate.dll"
|
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudioresample.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstaudioresample.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudiotestsrc.dll"
|
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstautodetect.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstautodetect.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstbs2b.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstbs2b.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstdash.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstdash.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\libgstdsd.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstequalizer.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstequalizer.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstfaac.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstfaac.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstfaad.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstfaad.dll"
|
||||||
@@ -1148,6 +1166,10 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\libgstisomp4.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstisomp4.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstlibav.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\libgstmpg123.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstmusepack.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstmusepack.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstogg.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstogg.dll"
|
||||||
@@ -1170,6 +1192,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\libgstwaveform.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstwavenc.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstwavenc.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll"
|
Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll"
|
||||||
@@ -1179,24 +1202,24 @@ Section "Uninstall"
|
|||||||
; MSVC GStreamer plugins
|
; MSVC GStreamer plugins
|
||||||
|
|
||||||
!ifdef msvc
|
!ifdef msvc
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\gstadaptivedemux2.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaes.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstaes.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaiff.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstaiff.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstapetag.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstapetag.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstapp.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstapp.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstasf.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstasf.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstasfmux.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstasfmux.dll"
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\gstasio.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaudioconvert.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstaudioconvert.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaudiofx.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstaudiofx.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaudiomixer.dll"
|
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaudioparsers.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstaudioparsers.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaudiorate.dll"
|
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaudioresample.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstaudioresample.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstaudiotestsrc.dll"
|
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstautodetect.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstautodetect.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstbs2b.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstbs2b.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstcoreelements.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstcoreelements.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstdash.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstdash.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstdirectsound.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstdirectsound.dll"
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\gstdsd.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstequalizer.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstequalizer.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstfaac.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstfaac.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstfaad.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstfaad.dll"
|
||||||
@@ -1211,6 +1234,10 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\gstisomp4.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstisomp4.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstlame.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstlame.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstlibav.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\gstmpg123.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstmusepack.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstmusepack.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstogg.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstogg.dll"
|
||||||
@@ -1234,10 +1261,14 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwasapi2.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwasapi2.dll"
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\gstwaveform.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstxingmux.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstxingmux.dll"
|
||||||
|
!ifdef arch_x64
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\gstspotify.dll"
|
||||||
|
!endif
|
||||||
!endif ; msvc
|
!endif ; msvc
|
||||||
|
|
||||||
Delete "$INSTDIR\Uninstall.exe"
|
Delete "$INSTDIR\Uninstall.exe"
|
||||||
|
|||||||
@@ -21,8 +21,6 @@
|
|||||||
#include "gstfastspectrum.h"
|
#include "gstfastspectrum.h"
|
||||||
#include "gstmoodbarplugin.h"
|
#include "gstmoodbarplugin.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
|
static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
|
||||||
|
|
||||||
if (!gst_element_register(plugin, "fastspectrum", GST_RANK_NONE, GST_TYPE_FASTSPECTRUM)) {
|
if (!gst_element_register(plugin, "fastspectrum", GST_RANK_NONE, GST_TYPE_FASTSPECTRUM)) {
|
||||||
@@ -32,8 +30,6 @@ static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int gstfastspectrum_register_static() {
|
int gstfastspectrum_register_static() {
|
||||||
|
|
||||||
return gst_plugin_register_static(
|
return gst_plugin_register_static(
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ static QIODevice *sNullDevice = nullptr;
|
|||||||
|
|
||||||
const char *kDefaultLogLevels = "*:3";
|
const char *kDefaultLogLevels = "*:3";
|
||||||
|
|
||||||
static const char *kMessageHandlerMagic = "__logging_message__";
|
static constexpr char kMessageHandlerMagic[] = "__logging_message__";
|
||||||
static const size_t kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
|
static const size_t kMessageHandlerMagicLen = strlen(kMessageHandlerMagic);
|
||||||
static QtMessageHandler sOriginalMessageHandler = nullptr;
|
static QtMessageHandler sOriginalMessageHandler = nullptr;
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
@@ -135,9 +135,9 @@ class LoggedDebug : public DebugBase<LoggedDebug> {
|
|||||||
|
|
||||||
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
|
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
|
||||||
|
|
||||||
if (message.startsWith(kMessageHandlerMagic)) {
|
if (message.startsWith(QLatin1String(kMessageHandlerMagic))) {
|
||||||
QByteArray message_data = message.toUtf8();
|
QByteArray message_data = message.toUtf8();
|
||||||
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLength);
|
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLen);
|
||||||
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -157,8 +157,8 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const QString &line : message.split('\n')) {
|
for (const QString &line : message.split(QLatin1Char('\n'))) {
|
||||||
BufferedDebug d = CreateLogger<BufferedDebug>(level, "unknown", -1, nullptr);
|
BufferedDebug d = CreateLogger<BufferedDebug>(level, QStringLiteral("unknown"), -1, nullptr);
|
||||||
d << line.toLocal8Bit().constData();
|
d << line.toLocal8Bit().constData();
|
||||||
if (d.buf_) {
|
if (d.buf_) {
|
||||||
d.buf_->close();
|
d.buf_->close();
|
||||||
@@ -193,8 +193,8 @@ void SetLevels(const QString &levels) {
|
|||||||
|
|
||||||
if (!sClassLevels) return;
|
if (!sClassLevels) return;
|
||||||
|
|
||||||
for (const QString &item : levels.split(',')) {
|
for (const QString &item : levels.split(QLatin1Char(','))) {
|
||||||
const QStringList class_level = item.split(':');
|
const QStringList class_level = item.split(QLatin1Char(':'));
|
||||||
|
|
||||||
QString class_name;
|
QString class_name;
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
@@ -212,7 +212,7 @@ void SetLevels(const QString &levels) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (class_name.isEmpty() || class_name == "*") {
|
if (class_name.isEmpty() || class_name == QLatin1Char('*')) {
|
||||||
sDefaultLevel = static_cast<Level>(level);
|
sDefaultLevel = static_cast<Level>(level);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -225,10 +225,10 @@ void SetLevels(const QString &levels) {
|
|||||||
static QString ParsePrettyFunction(const char *pretty_function) {
|
static QString ParsePrettyFunction(const char *pretty_function) {
|
||||||
|
|
||||||
// Get the class name out of the function name.
|
// Get the class name out of the function name.
|
||||||
QString class_name = pretty_function;
|
QString class_name = QLatin1String(pretty_function);
|
||||||
const qint64 paren = class_name.indexOf('(');
|
const qint64 paren = class_name.indexOf(QLatin1Char('('));
|
||||||
if (paren != -1) {
|
if (paren != -1) {
|
||||||
const qint64 colons = class_name.lastIndexOf("::", paren);
|
const qint64 colons = class_name.lastIndexOf(QLatin1String("::"), paren);
|
||||||
if (colons != -1) {
|
if (colons != -1) {
|
||||||
class_name = class_name.left(colons);
|
class_name = class_name.left(colons);
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,7 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const qint64 space = class_name.lastIndexOf(' ');
|
const qint64 space = class_name.lastIndexOf(QLatin1Char(' '));
|
||||||
if (space != -1) {
|
if (space != -1) {
|
||||||
class_name = class_name.mid(space + 1);
|
class_name = class_name.mid(space + 1);
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
|
|||||||
case Level_Fatal: level_name = " FATAL "; break;
|
case Level_Fatal: level_name = " FATAL "; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString filter_category = (category != nullptr) ? category : class_name;
|
QString filter_category = (category != nullptr) ? QLatin1String(category) : class_name;
|
||||||
// Check the settings to see if we're meant to show or hide this message.
|
// Check the settings to see if we're meant to show or hide this message.
|
||||||
Level threshold_level = sDefaultLevel;
|
Level threshold_level = sDefaultLevel;
|
||||||
if (sClassLevels && sClassLevels->contains(filter_category)) {
|
if (sClassLevels && sClassLevels->contains(filter_category)) {
|
||||||
@@ -272,10 +272,10 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
|
|||||||
|
|
||||||
QString function_line = class_name;
|
QString function_line = class_name;
|
||||||
if (line != -1) {
|
if (line != -1) {
|
||||||
function_line += ":" + QString::number(line);
|
function_line += QLatin1Char(':') + QString::number(line);
|
||||||
}
|
}
|
||||||
if (category) {
|
if (category) {
|
||||||
function_line += "(" + QString(category) + ")";
|
function_line += QLatin1Char('(') + QLatin1String(category) + QLatin1Char(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
QtMsgType type = QtDebugMsg;
|
QtMsgType type = QtDebugMsg;
|
||||||
@@ -284,7 +284,7 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
T ret(type);
|
T ret(type);
|
||||||
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
|
ret.nospace() << QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss.zzz")).toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
|
||||||
|
|
||||||
return ret.space();
|
return ret.space();
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ QString CXXDemangle(const QString &mangled_function) {
|
|||||||
QString LinuxDemangle(const QString &symbol);
|
QString LinuxDemangle(const QString &symbol);
|
||||||
QString LinuxDemangle(const QString &symbol) {
|
QString LinuxDemangle(const QString &symbol) {
|
||||||
|
|
||||||
QRegularExpression regex("\\(([^+]+)");
|
QRegularExpression regex(QStringLiteral("\\(([^+]+)"));
|
||||||
QRegularExpressionMatch match = regex.match(symbol);
|
QRegularExpressionMatch match = regex.match(symbol);
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
return symbol;
|
return symbol;
|
||||||
@@ -326,9 +326,9 @@ QString DarwinDemangle(const QString &symbol);
|
|||||||
QString DarwinDemangle(const QString &symbol) {
|
QString DarwinDemangle(const QString &symbol) {
|
||||||
|
|
||||||
# if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
# if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
QStringList split = symbol.split(' ', Qt::SkipEmptyParts);
|
QStringList split = symbol.split(QLatin1Char(' '), Qt::SkipEmptyParts);
|
||||||
# else
|
# else
|
||||||
QStringList split = symbol.split(' ', QString::SkipEmptyParts);
|
QStringList split = symbol.split(QLatin1Char(' '), QString::SkipEmptyParts);
|
||||||
# endif
|
# endif
|
||||||
QString mangled_function = split[3];
|
QString mangled_function = split[3];
|
||||||
return CXXDemangle(mangled_function);
|
return CXXDemangle(mangled_function);
|
||||||
@@ -392,7 +392,7 @@ namespace {
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
QString print_duration(T duration, const std::string &unit) {
|
QString print_duration(T duration, const std::string &unit) {
|
||||||
return QString("%1%2").arg(duration.count()).arg(unit.c_str());
|
return QStringLiteral("%1%2").arg(duration.count()).arg(QString::fromStdString(unit));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -120,13 +120,13 @@ template<typename MT>
|
|||||||
void AbstractMessageHandler<MT>::SendMessage(const MessageType &message) {
|
void AbstractMessageHandler<MT>::SendMessage(const MessageType &message) {
|
||||||
Q_ASSERT(QThread::currentThread() == thread());
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
std::string data = message.SerializeAsString();
|
const std::string data = message.SerializeAsString();
|
||||||
WriteMessage(QByteArray(data.data(), data.size()));
|
WriteMessage(QByteArray(data.data(), data.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename MT>
|
template<typename MT>
|
||||||
void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType &message) {
|
void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType &message) {
|
||||||
std::string data = message.SerializeAsString();
|
const std::string data = message.SerializeAsString();
|
||||||
QMetaObject::invokeMethod(this, "WriteMessage", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray(data.data(), data.size())));
|
QMetaObject::invokeMethod(this, "WriteMessage", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray(data.data(), data.size())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -171,7 +172,7 @@ WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
|||||||
local_server_name_ = qApp->applicationName().toLower();
|
local_server_name_ = qApp->applicationName().toLower();
|
||||||
|
|
||||||
if (local_server_name_.isEmpty()) {
|
if (local_server_name_.isEmpty()) {
|
||||||
local_server_name_ = "workerpool";
|
local_server_name_ = QStringLiteral("workerpool");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -243,15 +244,15 @@ void WorkerPool<HandlerType>::DoStart() {
|
|||||||
QStringList search_path;
|
QStringList search_path;
|
||||||
search_path << QCoreApplication::applicationDirPath();
|
search_path << QCoreApplication::applicationDirPath();
|
||||||
#if defined(Q_OS_UNIX)
|
#if defined(Q_OS_UNIX)
|
||||||
search_path << "/usr/libexec";
|
search_path << QStringLiteral("/usr/libexec");
|
||||||
search_path << "/usr/local/libexec";
|
search_path << QStringLiteral("/usr/local/libexec");
|
||||||
#endif
|
#endif
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
search_path << QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../PlugIns");
|
search_path << QDir::cleanPath(QCoreApplication::applicationDirPath() + QStringLiteral("/../PlugIns"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (const QString &path_prefix : search_path) {
|
for (const QString &path_prefix : std::as_const(search_path)) {
|
||||||
const QString executable_path = path_prefix + "/" + executable_name_;
|
const QString executable_path = path_prefix + QLatin1Char('/') + executable_name_;
|
||||||
if (QFile::exists(executable_path)) {
|
if (QFile::exists(executable_path)) {
|
||||||
executable_path_ = executable_path;
|
executable_path_ = executable_path;
|
||||||
qLog(Debug) << "Using worker" << executable_name_ << "from" << path_prefix;
|
qLog(Debug) << "Using worker" << executable_name_ << "from" << path_prefix;
|
||||||
@@ -294,7 +295,7 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
|||||||
// Create a server, find an unused name and start listening
|
// Create a server, find an unused name and start listening
|
||||||
forever {
|
forever {
|
||||||
const quint32 unique_number = QRandomGenerator::global()->bounded(static_cast<quint32>(quint64(this) & 0xFFFFFFFF));
|
const quint32 unique_number = QRandomGenerator::global()->bounded(static_cast<quint32>(quint64(this) & 0xFFFFFFFF));
|
||||||
const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number);
|
const QString name = QStringLiteral("%1_%2").arg(local_server_name_).arg(unique_number);
|
||||||
|
|
||||||
if (worker->local_server_->listen(name)) {
|
if (worker->local_server_->listen(name)) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ endif()
|
|||||||
|
|
||||||
set(SOURCES tagreaderbase.cpp tagreadermessages.proto)
|
set(SOURCES tagreaderbase.cpp tagreadermessages.proto)
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(HAVE_TAGLIB)
|
||||||
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
|
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
if(HAVE_TAGPARSER)
|
||||||
list(APPEND SOURCES tagreadertagparser.cpp)
|
list(APPEND SOURCES tagreadertagparser.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -24,11 +24,11 @@ link_directories(
|
|||||||
${PROTOBUF_LIBRARY_DIRS}
|
${PROTOBUF_LIBRARY_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(HAVE_TAGLIB)
|
||||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
if(HAVE_TAGPARSER)
|
||||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -56,12 +56,12 @@ target_link_libraries(libstrawberry-tagreader PRIVATE
|
|||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
)
|
)
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(HAVE_TAGLIB)
|
||||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
if(HAVE_TAGPARSER)
|
||||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
Strawberry is free software: you can redistribute it and/or modify
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
@@ -33,13 +34,38 @@
|
|||||||
TagReaderBase::TagReaderBase() = default;
|
TagReaderBase::TagReaderBase() = default;
|
||||||
TagReaderBase::~TagReaderBase() = default;
|
TagReaderBase::~TagReaderBase() = default;
|
||||||
|
|
||||||
|
QString TagReaderBase::ErrorString(const Result &result) {
|
||||||
|
|
||||||
|
switch (result.error_code) {
|
||||||
|
case Result::ErrorCode::Success:
|
||||||
|
return QObject::tr("Success");
|
||||||
|
case Result::ErrorCode::Unsupported:
|
||||||
|
return QObject::tr("File is unsupported");
|
||||||
|
case Result::ErrorCode::FilenameMissing:
|
||||||
|
return QObject::tr("Filename is missing");
|
||||||
|
case Result::ErrorCode::FileDoesNotExist:
|
||||||
|
return QObject::tr("File does not exist");
|
||||||
|
case Result::ErrorCode::FileOpenError:
|
||||||
|
return QObject::tr("File could not be opened");
|
||||||
|
case Result::ErrorCode::FileParseError:
|
||||||
|
return QObject::tr("Could not parse file");
|
||||||
|
case Result::ErrorCode::FileSaveError:
|
||||||
|
return QObject::tr("Could save file");
|
||||||
|
case Result::ErrorCode::CustomError:
|
||||||
|
return result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QObject::tr("Unknown error");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||||
|
|
||||||
if (POPM_rating < 0x01) return 0.0F;
|
if (POPM_rating < 0x01) return 0.0F;
|
||||||
else if (POPM_rating < 0x40) return 0.20F;
|
if (POPM_rating < 0x40) return 0.20F;
|
||||||
else if (POPM_rating < 0x80) return 0.40F;
|
if (POPM_rating < 0x80) return 0.40F;
|
||||||
else if (POPM_rating < 0xC0) return 0.60F;
|
if (POPM_rating < 0xC0) return 0.60F;
|
||||||
else if (POPM_rating < 0xFC) return 0.80F;
|
if (POPM_rating < 0xFC) return 0.80F;
|
||||||
|
|
||||||
return 1.0F;
|
return 1.0F;
|
||||||
|
|
||||||
@@ -48,25 +74,24 @@ float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
|||||||
int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
||||||
|
|
||||||
if (rating < 0.20) return 0x00;
|
if (rating < 0.20) return 0x00;
|
||||||
else if (rating < 0.40) return 0x01;
|
if (rating < 0.40) return 0x01;
|
||||||
else if (rating < 0.60) return 0x40;
|
if (rating < 0.60) return 0x40;
|
||||||
else if (rating < 0.80) return 0x80;
|
if (rating < 0.80) return 0x80;
|
||||||
else if (rating < 1.0) return 0xC0;
|
if (rating < 1.0) return 0xC0;
|
||||||
|
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request) {
|
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::WriteFileRequest &request) {
|
||||||
|
|
||||||
if (!request.has_save_cover() || !request.save_cover()) {
|
if (!request.has_save_cover() || !request.save_cover()) {
|
||||||
return Cover();
|
return Cover();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString song_filename = QString::fromUtf8(request.filename().data(), static_cast<qint64>(request.filename().size()));
|
|
||||||
QString cover_filename;
|
QString cover_filename;
|
||||||
if (request.has_cover_filename()) {
|
if (request.has_cover_filename()) {
|
||||||
cover_filename = QString::fromUtf8(request.cover_filename().data(), static_cast<qint64>(request.cover_filename().size()));
|
cover_filename = QString::fromStdString(request.cover_filename());
|
||||||
}
|
}
|
||||||
QByteArray cover_data;
|
QByteArray cover_data;
|
||||||
if (request.has_cover_data()) {
|
if (request.has_cover_data()) {
|
||||||
@@ -74,19 +99,18 @@ TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::S
|
|||||||
}
|
}
|
||||||
QString cover_mime_type;
|
QString cover_mime_type;
|
||||||
if (request.has_cover_mime_type()) {
|
if (request.has_cover_mime_type()) {
|
||||||
cover_mime_type = QByteArray(request.cover_mime_type().data(), static_cast<qint64>(request.cover_mime_type().size()));
|
cover_mime_type = QString::fromStdString(request.cover_mime_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
|
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request) {
|
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::SaveEmbeddedArtRequest &request) {
|
||||||
|
|
||||||
const QString song_filename = QString::fromUtf8(request.filename().data(), static_cast<qint64>(request.filename().size()));
|
|
||||||
QString cover_filename;
|
QString cover_filename;
|
||||||
if (request.has_cover_filename()) {
|
if (request.has_cover_filename()) {
|
||||||
cover_filename = QString::fromUtf8(request.cover_filename().data(), static_cast<qint64>(request.cover_filename().size()));
|
cover_filename = QString::fromStdString(request.cover_filename());
|
||||||
}
|
}
|
||||||
QByteArray cover_data;
|
QByteArray cover_data;
|
||||||
if (request.has_cover_data()) {
|
if (request.has_cover_data()) {
|
||||||
@@ -94,7 +118,7 @@ TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::S
|
|||||||
}
|
}
|
||||||
QString cover_mime_type;
|
QString cover_mime_type;
|
||||||
if (request.has_cover_mime_type()) {
|
if (request.has_cover_mime_type()) {
|
||||||
cover_mime_type = QByteArray(request.cover_mime_type().data(), static_cast<qint64>(request.cover_mime_type().size()));
|
cover_mime_type = QString::fromStdString(request.cover_mime_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
|
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
|
||||||
@@ -118,24 +142,28 @@ TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_fil
|
|||||||
if (cover_mime_type.isEmpty()) {
|
if (cover_mime_type.isEmpty()) {
|
||||||
cover_mime_type = QMimeDatabase().mimeTypeForData(cover_data).name();
|
cover_mime_type = QMimeDatabase().mimeTypeForData(cover_data).name();
|
||||||
}
|
}
|
||||||
if (cover_mime_type == "image/jpeg") {
|
if (cover_mime_type == QLatin1String("image/jpeg")) {
|
||||||
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||||
return Cover(cover_data, cover_mime_type);
|
return Cover(cover_data, cover_mime_type);
|
||||||
}
|
}
|
||||||
if (cover_mime_type == "image/png") {
|
if (cover_mime_type == QLatin1String("image/png")) {
|
||||||
qLog(Debug) << "Using cover from PNG data for" << song_filename;
|
qLog(Debug) << "Using cover from PNG data for" << song_filename;
|
||||||
return Cover(cover_data, cover_mime_type);
|
return Cover(cover_data, cover_mime_type);
|
||||||
}
|
}
|
||||||
// Convert image to JPEG.
|
// Convert image to JPEG.
|
||||||
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
|
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
|
||||||
QImage cover_image(cover_data);
|
QImage cover_image;
|
||||||
|
if (!cover_image.loadFromData(cover_data)) {
|
||||||
|
qLog(Error) << "Failed to load image from cover data for" << song_filename;
|
||||||
|
return Cover();
|
||||||
|
}
|
||||||
cover_data.clear();
|
cover_data.clear();
|
||||||
QBuffer buffer(&cover_data);
|
QBuffer buffer(&cover_data);
|
||||||
if (buffer.open(QIODevice::WriteOnly)) {
|
if (buffer.open(QIODevice::WriteOnly)) {
|
||||||
cover_image.save(&buffer, "JPEG");
|
cover_image.save(&buffer, "JPEG");
|
||||||
buffer.close();
|
buffer.close();
|
||||||
}
|
}
|
||||||
return Cover(cover_data, "image/jpeg");
|
return Cover(cover_data, QStringLiteral("image/jpeg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Cover();
|
return Cover();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
Strawberry is free software: you can redistribute it and/or modify
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -34,7 +34,25 @@
|
|||||||
class TagReaderBase {
|
class TagReaderBase {
|
||||||
public:
|
public:
|
||||||
explicit TagReaderBase();
|
explicit TagReaderBase();
|
||||||
~TagReaderBase();
|
virtual ~TagReaderBase();
|
||||||
|
|
||||||
|
class Result {
|
||||||
|
public:
|
||||||
|
enum class ErrorCode {
|
||||||
|
Success,
|
||||||
|
Unsupported,
|
||||||
|
FilenameMissing,
|
||||||
|
FileDoesNotExist,
|
||||||
|
FileOpenError,
|
||||||
|
FileParseError,
|
||||||
|
FileSaveError,
|
||||||
|
CustomError,
|
||||||
|
};
|
||||||
|
Result(const ErrorCode _error_code, const QString &_error = QString()) : error_code(_error_code), error(_error) {}
|
||||||
|
ErrorCode error_code;
|
||||||
|
QString error;
|
||||||
|
bool success() const { return error_code == ErrorCode::Success; }
|
||||||
|
};
|
||||||
|
|
||||||
class Cover {
|
class Cover {
|
||||||
public:
|
public:
|
||||||
@@ -44,22 +62,25 @@ class TagReaderBase {
|
|||||||
QString error;
|
QString error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static QString ErrorString(const Result &result);
|
||||||
|
|
||||||
virtual bool IsMediaFile(const QString &filename) const = 0;
|
virtual bool IsMediaFile(const QString &filename) const = 0;
|
||||||
|
|
||||||
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
virtual Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
||||||
virtual bool SaveFile(const spb::tagreader::SaveFileRequest &request) const = 0;
|
virtual Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const = 0;
|
||||||
|
|
||||||
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
virtual Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const = 0;
|
||||||
virtual bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const = 0;
|
virtual Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const = 0;
|
||||||
|
|
||||||
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
virtual Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const = 0;
|
||||||
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
virtual Result SaveSongRatingToFile(const QString &filename, const float rating) const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
static float ConvertPOPMRating(const int POPM_rating);
|
static float ConvertPOPMRating(const int POPM_rating);
|
||||||
static int ConvertToPOPMRating(const float rating);
|
static int ConvertToPOPMRating(const float rating);
|
||||||
|
|
||||||
static Cover LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request);
|
static Cover LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::WriteFileRequest &request);
|
||||||
static Cover LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request);
|
static Cover LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::SaveEmbeddedArtRequest &request);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Cover LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type);
|
static Cover LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type);
|
||||||
|
|||||||
@@ -19,8 +19,7 @@
|
|||||||
|
|
||||||
#include "tagreadergme.h"
|
#include "tagreadergme.h"
|
||||||
|
|
||||||
#include <tag.h>
|
#include <taglib/apefile.h>
|
||||||
#include <apefile.h>
|
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@@ -35,22 +34,23 @@
|
|||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
#include "tagreadertaglib.h"
|
#include "tagreadertaglib.h"
|
||||||
|
|
||||||
bool GME::IsSupportedFormat(const QFileInfo &file_info) {
|
#undef TStringToQString
|
||||||
return file_info.exists() && (file_info.completeSuffix().endsWith("spc", Qt::CaseInsensitive) || file_info.completeSuffix().endsWith("vgm"), Qt::CaseInsensitive);
|
#undef QStringToTString
|
||||||
|
|
||||||
|
bool GME::IsSupportedFormat(const QFileInfo &fileinfo) {
|
||||||
|
return fileinfo.exists() && (fileinfo.completeSuffix().endsWith(QLatin1String("spc"), Qt::CaseInsensitive) || fileinfo.completeSuffix().endsWith(QLatin1String("vgm")), Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GME::ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
|
TagReaderBase::Result GME::ReadFile(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) {
|
||||||
|
|
||||||
if (file_info.completeSuffix().endsWith("spc"), Qt::CaseInsensitive) {
|
if (fileinfo.completeSuffix().endsWith(QLatin1String("spc")), Qt::CaseInsensitive) {
|
||||||
SPC::Read(file_info, song_info);
|
return SPC::Read(fileinfo, song);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (file_info.completeSuffix().endsWith("vgm", Qt::CaseInsensitive)) {
|
if (fileinfo.completeSuffix().endsWith(QLatin1String("vgm"), Qt::CaseInsensitive)) {
|
||||||
VGM::Read(file_info, song_info);
|
return VGM::Read(fileinfo, song);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return TagReaderBase::Result::ErrorCode::Unsupported;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,15 +67,19 @@ quint32 GME::UnpackBytes32(const char *const bytes, size_t length) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
|
TagReaderBase::Result GME::SPC::Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) {
|
||||||
|
|
||||||
QFile file(file_info.filePath());
|
QFile file(fileinfo.filePath());
|
||||||
if (!file.open(QIODevice::ReadOnly)) return;
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
return TagReaderBase::Result(TagReaderBase::Result::ErrorCode::FileOpenError, file.errorString());
|
||||||
|
}
|
||||||
|
|
||||||
qLog(Debug) << "Reading tags from SPC file" << file_info.fileName();
|
qLog(Debug) << "Reading tags from SPC file" << fileinfo.fileName();
|
||||||
|
|
||||||
// Check for header -- more reliable than file name alone.
|
// Check for header -- more reliable than file name alone.
|
||||||
if (!file.read(33).startsWith(QString("SNES-SPC700").toLatin1())) return;
|
if (!file.read(33).startsWith(QStringLiteral("SNES-SPC700").toLatin1())) {
|
||||||
|
return TagReaderBase::Result::ErrorCode::Unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
// First order of business -- get any tag values that exist within the core file information.
|
// First order of business -- get any tag values that exist within the core file information.
|
||||||
// These only allow for a certain number of bytes per field,
|
// These only allow for a certain number of bytes per field,
|
||||||
@@ -88,13 +92,13 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
const bool has_id6 = id6_status.length() >= 1 && id6_status[0] == static_cast<char>(xID6_STATUS::ON);
|
const bool has_id6 = id6_status.length() >= 1 && id6_status[0] == static_cast<char>(xID6_STATUS::ON);
|
||||||
|
|
||||||
file.seek(SONG_TITLE_OFFSET);
|
file.seek(SONG_TITLE_OFFSET);
|
||||||
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
|
song->set_title(QString::fromLatin1(file.read(32)).toStdString());
|
||||||
|
|
||||||
file.seek(GAME_TITLE_OFFSET);
|
file.seek(GAME_TITLE_OFFSET);
|
||||||
song_info->set_album(QString::fromLatin1(file.read(32)).toStdString());
|
song->set_album(QString::fromLatin1(file.read(32)).toStdString());
|
||||||
|
|
||||||
file.seek(ARTIST_OFFSET);
|
file.seek(ARTIST_OFFSET);
|
||||||
song_info->set_artist(QString::fromLatin1(file.read(32)).toStdString());
|
song->set_artist(QString::fromLatin1(file.read(32)).toStdString());
|
||||||
|
|
||||||
file.seek(INTRO_LENGTH_OFFSET);
|
file.seek(INTRO_LENGTH_OFFSET);
|
||||||
QByteArray length_bytes = file.read(INTRO_LENGTH_SIZE);
|
QByteArray length_bytes = file.read(INTRO_LENGTH_SIZE);
|
||||||
@@ -107,7 +111,7 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (length_in_sec < 0x1FFF) {
|
if (length_in_sec < 0x1FFF) {
|
||||||
song_info->set_length_nanosec(length_in_sec * kNsecPerSec);
|
song->set_length_nanosec(length_in_sec * kNsecPerSec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,13 +129,13 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
// Check for XID6 data -- this is infrequently used, but being able to fill in data from this is ideal before trying to rely on APETAG values.
|
// Check for XID6 data -- this is infrequently used, but being able to fill in data from this is ideal before trying to rely on APETAG values.
|
||||||
// XID6 format follows EA's binary file format standard named "IFF"
|
// XID6 format follows EA's binary file format standard named "IFF"
|
||||||
file.seek(XID6_OFFSET);
|
file.seek(XID6_OFFSET);
|
||||||
if (has_id6 && file.read(4) == QString("xid6").toLatin1()) {
|
if (has_id6 && file.read(4) == QStringLiteral("xid6").toLatin1()) {
|
||||||
QByteArray xid6_head_data = file.read(4);
|
QByteArray xid6_head_data = file.read(4);
|
||||||
if (xid6_head_data.size() >= 4) {
|
if (xid6_head_data.size() >= 4) {
|
||||||
qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3];
|
qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3];
|
||||||
// This should be the size remaining for entire ID6 block, but it seems that most files treat this as the size of the remaining header space...
|
// This should be the size remaining for entire ID6 block, but it seems that most files treat this as the size of the remaining header space...
|
||||||
|
|
||||||
qLog(Debug) << file_info.fileName() << "has ID6 tag.";
|
qLog(Debug) << fileinfo.fileName() << "has ID6 tag.";
|
||||||
|
|
||||||
while ((file.pos()) + 4 < XID6_OFFSET + xid6_size) {
|
while ((file.pos()) + 4 < XID6_OFFSET + xid6_size) {
|
||||||
QByteArray arr = file.read(4);
|
QByteArray arr = file.read(4);
|
||||||
@@ -152,21 +156,25 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
// an APETAG entry at the bottom of the file instead of writing into the xid6 tagging space.
|
// an APETAG entry at the bottom of the file instead of writing into the xid6 tagging space.
|
||||||
// This is where a lot of the extra data for a file is stored, such as genre or replaygain data.
|
// This is where a lot of the extra data for a file is stored, such as genre or replaygain data.
|
||||||
// This data is currently supported by TagLib, so we will simply use that for the remaining values.
|
// This data is currently supported by TagLib, so we will simply use that for the remaining values.
|
||||||
TagLib::APE::File ape(file_info.filePath().toStdString().data());
|
TagLib::APE::File ape(fileinfo.filePath().toStdString().data());
|
||||||
if (ape.hasAPETag()) {
|
if (ape.hasAPETag()) {
|
||||||
TagLib::Tag *tag = ape.tag();
|
TagLib::Tag *tag = ape.tag();
|
||||||
if (!tag) return;
|
if (!tag) {
|
||||||
|
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||||
TagReaderTagLib::TStringToStdString(tag->artist(), song_info->mutable_artist());
|
|
||||||
TagReaderTagLib::TStringToStdString(tag->album(), song_info->mutable_album());
|
|
||||||
TagReaderTagLib::TStringToStdString(tag->title(), song_info->mutable_title());
|
|
||||||
TagReaderTagLib::TStringToStdString(tag->genre(), song_info->mutable_genre());
|
|
||||||
song_info->set_track(static_cast<std::int32_t>(tag->track()));
|
|
||||||
song_info->set_year(static_cast<std::int32_t>(tag->year()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
song_info->set_valid(true);
|
TagReaderTagLib::AssignTagLibStringToStdString(tag->artist(), song->mutable_artist());
|
||||||
song_info->set_filetype(spb::tagreader::SongMetadata_FileType_SPC);
|
TagReaderTagLib::AssignTagLibStringToStdString(tag->album(), song->mutable_album());
|
||||||
|
TagReaderTagLib::AssignTagLibStringToStdString(tag->title(), song->mutable_title());
|
||||||
|
TagReaderTagLib::AssignTagLibStringToStdString(tag->genre(), song->mutable_genre());
|
||||||
|
song->set_track(static_cast<std::int32_t>(tag->track()));
|
||||||
|
song->set_year(static_cast<std::int32_t>(tag->year()));
|
||||||
|
}
|
||||||
|
|
||||||
|
song->set_valid(true);
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType_SPC);
|
||||||
|
|
||||||
|
return TagReaderBase::Result::ErrorCode::Success;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,18 +196,24 @@ quint64 GME::SPC::ConvertSPCStringToNum(const QByteArray &arr) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
|
TagReaderBase::Result GME::VGM::Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) {
|
||||||
|
|
||||||
QFile file(file_info.filePath());
|
QFile file(fileinfo.filePath());
|
||||||
if (!file.open(QIODevice::ReadOnly)) return;
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
return TagReaderBase::Result(TagReaderBase::Result::ErrorCode::FileOpenError, file.errorString());
|
||||||
|
}
|
||||||
|
|
||||||
qLog(Debug) << "Reading tags from VGM file" << file_info.fileName();
|
qLog(Debug) << "Reading tags from VGM file" << fileinfo.filePath();
|
||||||
|
|
||||||
if (!file.read(4).startsWith(QString("Vgm ").toLatin1())) return;
|
if (!file.read(4).startsWith(QStringLiteral("Vgm ").toLatin1())) {
|
||||||
|
return TagReaderBase::Result::ErrorCode::Unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
file.seek(GD3_TAG_PTR);
|
file.seek(GD3_TAG_PTR);
|
||||||
QByteArray gd3_head = file.read(4);
|
QByteArray gd3_head = file.read(4);
|
||||||
if (gd3_head.size() < 4) return;
|
if (gd3_head.size() < 4) {
|
||||||
|
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||||
|
}
|
||||||
|
|
||||||
quint64 pt = GME::UnpackBytes32(gd3_head.constData(), gd3_head.size());
|
quint64 pt = GME::UnpackBytes32(gd3_head.constData(), gd3_head.size());
|
||||||
|
|
||||||
@@ -209,7 +223,9 @@ void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
QByteArray loop_count_bytes = file.read(4);
|
QByteArray loop_count_bytes = file.read(4);
|
||||||
quint64 length = 0;
|
quint64 length = 0;
|
||||||
|
|
||||||
if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) return;
|
if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) {
|
||||||
|
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||||
|
}
|
||||||
|
|
||||||
file.seek(static_cast<qint64>(GD3_TAG_PTR + pt));
|
file.seek(static_cast<qint64>(GD3_TAG_PTR + pt));
|
||||||
QByteArray gd3_version = file.read(4);
|
QByteArray gd3_version = file.read(4);
|
||||||
@@ -226,19 +242,23 @@ void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
#else
|
#else
|
||||||
fileTagStream.setCodec("UTF-16");
|
fileTagStream.setCodec("UTF-16");
|
||||||
#endif
|
#endif
|
||||||
QStringList strings = fileTagStream.readLine(0).split(QChar('\0'));
|
QStringList strings = fileTagStream.readLine(0).split(QLatin1Char('\0'));
|
||||||
if (strings.count() < 10) return;
|
if (strings.count() < 10) {
|
||||||
|
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||||
|
}
|
||||||
|
|
||||||
// VGM standard dictates string tag data exist in specific order.
|
// VGM standard dictates string tag data exist in specific order.
|
||||||
// Order alternates between English and Japanese version of data.
|
// Order alternates between English and Japanese version of data.
|
||||||
// Read GD3 tag standard for more details.
|
// Read GD3 tag standard for more details.
|
||||||
song_info->set_title(strings[0].toStdString());
|
song->set_title(strings[0].toStdString());
|
||||||
song_info->set_album(strings[2].toStdString());
|
song->set_album(strings[2].toStdString());
|
||||||
song_info->set_artist(strings[6].toStdString());
|
song->set_artist(strings[6].toStdString());
|
||||||
song_info->set_year(strings[8].left(4).toInt());
|
song->set_year(strings[8].left(4).toInt());
|
||||||
song_info->set_length_nanosec(length * kNsecPerMsec);
|
song->set_length_nanosec(length * kNsecPerMsec);
|
||||||
song_info->set_valid(true);
|
song->set_valid(true);
|
||||||
song_info->set_filetype(spb::tagreader::SongMetadata_FileType_VGM);
|
song->set_filetype(spb::tagreader::SongMetadata_FileType_VGM);
|
||||||
|
|
||||||
|
return TagReaderBase::Result::ErrorCode::Success;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,34 +287,63 @@ bool GME::VGM::GetPlaybackLength(const QByteArray &sample_count_bytes, const QBy
|
|||||||
}
|
}
|
||||||
|
|
||||||
TagReaderGME::TagReaderGME() = default;
|
TagReaderGME::TagReaderGME() = default;
|
||||||
TagReaderGME::~TagReaderGME() = default;
|
|
||||||
|
|
||||||
bool TagReaderGME::IsMediaFile(const QString &filename) const {
|
bool TagReaderGME::IsMediaFile(const QString &filename) const {
|
||||||
|
|
||||||
QFileInfo fileinfo(filename);
|
QFileInfo fileinfo(filename);
|
||||||
return GME::IsSupportedFormat(fileinfo);
|
return GME::IsSupportedFormat(fileinfo);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
TagReaderBase::Result TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
||||||
|
|
||||||
QFileInfo fileinfo(filename);
|
QFileInfo fileinfo(filename);
|
||||||
return GME::ReadFile(fileinfo, song);
|
return GME::ReadFile(fileinfo, song);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::SaveFile(const spb::tagreader::SaveFileRequest&) const {
|
TagReaderBase::Result TagReaderGME::WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const {
|
||||||
return false;
|
|
||||||
|
Q_UNUSED(filename);
|
||||||
|
Q_UNUSED(request);
|
||||||
|
|
||||||
|
return Result::ErrorCode::Unsupported;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const {
|
TagReaderBase::Result TagReaderGME::LoadEmbeddedArt(const QString &filename, QByteArray &data) const {
|
||||||
return QByteArray();
|
|
||||||
|
Q_UNUSED(filename);
|
||||||
|
Q_UNUSED(data);
|
||||||
|
|
||||||
|
return Result::ErrorCode::Unsupported;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest&) const {
|
TagReaderBase::Result TagReaderGME::SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const {
|
||||||
return false;
|
|
||||||
|
Q_UNUSED(filename);
|
||||||
|
Q_UNUSED(request);
|
||||||
|
|
||||||
|
return Result::ErrorCode::Unsupported;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const {
|
TagReaderBase::Result TagReaderGME::SaveSongPlaycountToFile(const QString &filename, const uint playcount) const {
|
||||||
return false;
|
|
||||||
|
Q_UNUSED(filename);
|
||||||
|
Q_UNUSED(playcount);
|
||||||
|
|
||||||
|
return Result::ErrorCode::Unsupported;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::SaveSongRatingToFile(const QString&, const spb::tagreader::SongMetadata&) const {
|
TagReaderBase::Result TagReaderGME::SaveSongRatingToFile(const QString &filename, const float rating) const {
|
||||||
return false;
|
|
||||||
|
Q_UNUSED(filename);
|
||||||
|
Q_UNUSED(rating);
|
||||||
|
|
||||||
|
return Result::ErrorCode::Unsupported;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
#ifndef TAGREADERGME_H
|
#ifndef TAGREADERGME_H
|
||||||
#define TAGREADERGME_H
|
#define TAGREADERGME_H
|
||||||
|
|
||||||
#include <taglib/tstring.h>
|
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@@ -29,10 +27,9 @@
|
|||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
#include "tagreadermessages.pb.h"
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
|
|
||||||
namespace GME {
|
namespace GME {
|
||||||
bool IsSupportedFormat(const QFileInfo &file_info);
|
bool IsSupportedFormat(const QFileInfo &fileinfo);
|
||||||
bool ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
|
TagReaderBase::Result ReadFile(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song);
|
||||||
|
|
||||||
uint32_t UnpackBytes32(const char *const bytes, size_t length);
|
uint32_t UnpackBytes32(const char *const bytes, size_t length);
|
||||||
|
|
||||||
@@ -72,7 +69,7 @@ enum class xID6_TYPE {
|
|||||||
Integer = 0x4
|
Integer = 0x4
|
||||||
};
|
};
|
||||||
|
|
||||||
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
|
TagReaderBase::Result Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song);
|
||||||
qint16 GetNextMemAddressAlign32bit(qint16 input);
|
qint16 GetNextMemAddressAlign32bit(qint16 input);
|
||||||
quint64 ConvertSPCStringToNum(const QByteArray &arr);
|
quint64 ConvertSPCStringToNum(const QByteArray &arr);
|
||||||
} // namespace SPC
|
} // namespace SPC
|
||||||
@@ -88,7 +85,7 @@ constexpr int LOOP_SAMPLE_COUNT = 0x20;
|
|||||||
constexpr int SAMPLE_TIMEBASE = 44100;
|
constexpr int SAMPLE_TIMEBASE = 44100;
|
||||||
constexpr int GST_GME_LOOP_TIME_MS = 8000;
|
constexpr int GST_GME_LOOP_TIME_MS = 8000;
|
||||||
|
|
||||||
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
|
TagReaderBase::Result Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song);
|
||||||
// Takes in two QByteArrays, expected to be 4 bytes long. Desired length is returned via output parameter out_length. Returns false on error.
|
// Takes in two QByteArrays, expected to be 4 bytes long. Desired length is returned via output parameter out_length. Returns false on error.
|
||||||
bool GetPlaybackLength(const QByteArray &sample_count_bytes, const QByteArray &loop_count_bytes, quint64 &out_length);
|
bool GetPlaybackLength(const QByteArray &sample_count_bytes, const QByteArray &loop_count_bytes, quint64 &out_length);
|
||||||
|
|
||||||
@@ -102,18 +99,17 @@ class TagReaderGME : public TagReaderBase {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TagReaderGME();
|
explicit TagReaderGME();
|
||||||
~TagReaderGME();
|
|
||||||
|
|
||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override;
|
||||||
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
Result SaveSongRatingToFile(const QString &filename, const float rating) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TAGREADERGME_H
|
#endif // TAGREADERGME_H
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ message IsMediaFileRequest {
|
|||||||
|
|
||||||
message IsMediaFileResponse {
|
message IsMediaFileResponse {
|
||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
optional string error = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ReadFileRequest {
|
message ReadFileRequest {
|
||||||
@@ -105,11 +104,12 @@ message ReadFileRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message ReadFileResponse {
|
message ReadFileResponse {
|
||||||
optional SongMetadata metadata = 1;
|
optional bool success = 1;
|
||||||
optional string error = 2;
|
optional SongMetadata metadata = 2;
|
||||||
|
optional string error = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveFileRequest {
|
message WriteFileRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
optional bool save_tags = 2;
|
optional bool save_tags = 2;
|
||||||
optional bool save_playcount = 3;
|
optional bool save_playcount = 3;
|
||||||
@@ -121,7 +121,7 @@ message SaveFileRequest {
|
|||||||
optional string cover_mime_type = 9;
|
optional string cover_mime_type = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveFileResponse {
|
message WriteFileResponse {
|
||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
optional string error = 2;
|
optional string error = 2;
|
||||||
}
|
}
|
||||||
@@ -131,8 +131,9 @@ message LoadEmbeddedArtRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message LoadEmbeddedArtResponse {
|
message LoadEmbeddedArtResponse {
|
||||||
optional bytes data = 1;
|
optional bool success = 1;
|
||||||
optional string error = 2;
|
optional bytes data = 2;
|
||||||
|
optional string error = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveEmbeddedArtRequest {
|
message SaveEmbeddedArtRequest {
|
||||||
@@ -149,7 +150,7 @@ message SaveEmbeddedArtResponse {
|
|||||||
|
|
||||||
message SaveSongPlaycountToFileRequest {
|
message SaveSongPlaycountToFileRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
optional SongMetadata metadata = 2;
|
optional uint32 playcount = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveSongPlaycountToFileResponse {
|
message SaveSongPlaycountToFileResponse {
|
||||||
@@ -159,7 +160,7 @@ message SaveSongPlaycountToFileResponse {
|
|||||||
|
|
||||||
message SaveSongRatingToFileRequest {
|
message SaveSongRatingToFileRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
optional SongMetadata metadata = 2;
|
optional float rating = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveSongRatingToFileResponse {
|
message SaveSongRatingToFileResponse {
|
||||||
@@ -173,8 +174,8 @@ message Message {
|
|||||||
optional ReadFileRequest read_file_request = 2;
|
optional ReadFileRequest read_file_request = 2;
|
||||||
optional ReadFileResponse read_file_response = 3;
|
optional ReadFileResponse read_file_response = 3;
|
||||||
|
|
||||||
optional SaveFileRequest save_file_request = 4;
|
optional WriteFileRequest write_file_request = 4;
|
||||||
optional SaveFileResponse save_file_response = 5;
|
optional WriteFileResponse write_file_response = 5;
|
||||||
|
|
||||||
optional IsMediaFileRequest is_media_file_request = 6;
|
optional IsMediaFileRequest is_media_file_request = 6;
|
||||||
optional IsMediaFileResponse is_media_file_response = 7;
|
optional IsMediaFileResponse is_media_file_response = 7;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
Copyright 2013, David Sansome <me@davidsansome.com>
|
Copyright 2013, David Sansome <me@davidsansome.com>
|
||||||
Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
Strawberry is free software: you can redistribute it and/or modify
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -37,10 +37,15 @@
|
|||||||
#include <taglib/asffile.h>
|
#include <taglib/asffile.h>
|
||||||
#include <taglib/id3v2tag.h>
|
#include <taglib/id3v2tag.h>
|
||||||
#include <taglib/popularimeterframe.h>
|
#include <taglib/popularimeterframe.h>
|
||||||
|
#include <taglib/mp4tag.h>
|
||||||
|
#include <taglib/asftag.h>
|
||||||
|
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
#include "tagreadermessages.pb.h"
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
|
#undef TStringToQString
|
||||||
|
#undef QStringToTString
|
||||||
|
|
||||||
class FileRefFactory;
|
class FileRefFactory;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -50,55 +55,85 @@ class FileRefFactory;
|
|||||||
class TagReaderTagLib : public TagReaderBase {
|
class TagReaderTagLib : public TagReaderBase {
|
||||||
public:
|
public:
|
||||||
explicit TagReaderTagLib();
|
explicit TagReaderTagLib();
|
||||||
~TagReaderTagLib();
|
~TagReaderTagLib() override;
|
||||||
|
|
||||||
|
static inline TagLib::String StdStringToTagLibString(const std::string &s) {
|
||||||
|
return TagLib::String(s.c_str(), TagLib::String::UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::string TagLibStringToStdString(const TagLib::String &s) {
|
||||||
|
return std::string(s.toCString(true), s.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline TagLib::String QStringToTagLibString(const QString &s) {
|
||||||
|
return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline QString TagLibStringToQString(const TagLib::String &s) {
|
||||||
|
return QString::fromUtf8((s).toCString(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void AssignTagLibStringToStdString(const TagLib::String &tstr, std::string *output) {
|
||||||
|
|
||||||
|
const QString qstr = TagLibStringToQString(tstr).trimmed();
|
||||||
|
const QByteArray data = qstr.toUtf8();
|
||||||
|
output->assign(data.constData(), data.size());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override;
|
||||||
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
Result SaveSongRatingToFile(const QString &filename, const float rating) const override;
|
||||||
|
|
||||||
static void TStringToStdString(const TagLib::String &tag, std::string *output);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||||
|
|
||||||
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
void ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
void ParseVorbisComments(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
|
void ParseAPETags(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const spb::tagreader::SongMetadata &song) const;
|
void ParseMP4Tags(TagLib::MP4::Tag *tag, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
void SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void ParseASFTags(TagLib::ASF::Tag *tag, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
|
void ParseASFAttribute(const TagLib::ASF::AttributeListMap &attributes_map, const char *attribute, std::string *str) const;
|
||||||
|
|
||||||
|
void SetID3v2Tag(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
void SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
void SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
void SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
void SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
|
|
||||||
|
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetASFTag(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const std::string &value) const;
|
||||||
|
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const int value) const;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
|
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
|
||||||
|
|
||||||
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
||||||
|
|
||||||
void SetPlaycount(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const;
|
void SetPlaycount(TagLib::Ogg::XiphComment *vorbis_comment, const uint playcount) const;
|
||||||
void SetPlaycount(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SetPlaycount(TagLib::APE::Tag *tag, const uint playcount) const;
|
||||||
void SetPlaycount(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SetPlaycount(TagLib::ID3v2::Tag *tag, const uint playcount) const;
|
||||||
void SetPlaycount(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SetPlaycount(TagLib::MP4::Tag *tag, const uint playcount) const;
|
||||||
void SetPlaycount(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SetPlaycount(TagLib::ASF::Tag *tag, const uint playcount) const;
|
||||||
|
|
||||||
void SetRating(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const;
|
void SetRating(TagLib::Ogg::XiphComment *vorbis_comment, const float rating) const;
|
||||||
void SetRating(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SetRating(TagLib::APE::Tag *tag, const float rating) const;
|
||||||
void SetRating(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SetRating(TagLib::ID3v2::Tag *tag, const float rating) const;
|
||||||
void SetRating(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SetRating(TagLib::MP4::Tag *tag, const float rating) const;
|
||||||
void SetRating(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SetRating(TagLib::ASF::Tag *tag, const float rating) const;
|
||||||
|
|
||||||
void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const;
|
void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mime_type) const;
|
||||||
void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const;
|
void SetEmbeddedArt(TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mime_type) const;
|
||||||
void SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
void SetEmbeddedArt(TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
||||||
void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
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
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -46,14 +46,12 @@
|
|||||||
|
|
||||||
TagReaderTagParser::TagReaderTagParser() = default;
|
TagReaderTagParser::TagReaderTagParser() = default;
|
||||||
|
|
||||||
TagReaderTagParser::~TagReaderTagParser() = default;
|
|
||||||
|
|
||||||
bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
||||||
|
|
||||||
qLog(Debug) << "Checking for valid file" << filename;
|
qLog(Debug) << "Checking for valid file" << filename;
|
||||||
|
|
||||||
QFileInfo fileinfo(filename);
|
QFileInfo fileinfo(filename);
|
||||||
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
|
if (!fileinfo.exists() || fileinfo.suffix().compare(QLatin1String("bak"), Qt::CaseInsensitive) == 0) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TagParser::MediaFileInfo taginfo;
|
TagParser::MediaFileInfo taginfo;
|
||||||
@@ -94,13 +92,13 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
TagReaderBase::Result TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
||||||
|
|
||||||
qLog(Debug) << "Reading tags from" << filename;
|
qLog(Debug) << "Reading tags from" << filename;
|
||||||
|
|
||||||
const QFileInfo fileinfo(filename);
|
const QFileInfo fileinfo(filename);
|
||||||
|
|
||||||
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
|
if (!fileinfo.exists() || fileinfo.suffix().compare(QLatin1String("bak"), Qt::CaseInsensitive) == 0) return Result::ErrorCode::FileParseError;
|
||||||
|
|
||||||
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
||||||
const QByteArray basefilename = fileinfo.fileName().toUtf8();
|
const QByteArray basefilename = fileinfo.fileName().toUtf8();
|
||||||
@@ -134,19 +132,19 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
taginfo.parseContainerFormat(diag, progress);
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.parseTracks(diag, progress);
|
taginfo.parseTracks(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.parseTags(diag, progress);
|
taginfo.parseTags(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const TagParser::DiagMessage &msg : diag) {
|
for (const TagParser::DiagMessage &msg : diag) {
|
||||||
@@ -205,7 +203,7 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
|
|
||||||
if (song->filetype() == spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_UNKNOWN) {
|
if (song->filetype() == spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_UNKNOWN) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::Unsupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
@@ -246,21 +244,20 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
|
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
|
|
||||||
return true;
|
return Result::ErrorCode::Success;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(...) {
|
catch(...) {
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request) const {
|
TagReaderBase::Result TagReaderTagParser::WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const {
|
||||||
|
|
||||||
if (request.filename().empty()) return false;
|
if (request.filename().empty()) return Result::ErrorCode::FilenameMissing;
|
||||||
|
|
||||||
const QString filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
const spb::tagreader::SongMetadata &song = request.metadata();
|
||||||
const spb::tagreader::SongMetadata song = request.metadata();
|
|
||||||
const bool save_tags = request.has_save_tags() && request.save_tags();
|
const bool save_tags = request.has_save_tags() && request.save_tags();
|
||||||
const bool save_playcount = request.has_save_playcount() && request.save_playcount();
|
const bool save_playcount = request.has_save_playcount() && request.save_playcount();
|
||||||
const bool save_rating = request.has_save_rating() && request.save_rating();
|
const bool save_rating = request.has_save_rating() && request.save_rating();
|
||||||
@@ -268,21 +265,21 @@ bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request
|
|||||||
|
|
||||||
QStringList save_tags_options;
|
QStringList save_tags_options;
|
||||||
if (save_tags) {
|
if (save_tags) {
|
||||||
save_tags_options << "tags";
|
save_tags_options << QStringLiteral("tags");
|
||||||
}
|
}
|
||||||
if (save_playcount) {
|
if (save_playcount) {
|
||||||
save_tags_options << "playcount";
|
save_tags_options << QStringLiteral("playcount");
|
||||||
}
|
}
|
||||||
if (save_rating) {
|
if (save_rating) {
|
||||||
save_tags_options << "rating";
|
save_tags_options << QStringLiteral("rating");
|
||||||
}
|
}
|
||||||
if (save_cover) {
|
if (save_cover) {
|
||||||
save_tags_options << "embedded cover";
|
save_tags_options << QStringLiteral("embedded cover");
|
||||||
}
|
}
|
||||||
|
|
||||||
qLog(Debug) << "Saving" << save_tags_options.join(", ") << "to" << filename;
|
qLog(Debug) << "Saving" << save_tags_options.join(QLatin1String(", ")) << "to" << filename;
|
||||||
|
|
||||||
const QByteArray cover_data = LoadCoverDataFromRequest(request);
|
const Cover cover = LoadCoverFromRequest(filename, request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TagParser::MediaFileInfo taginfo;
|
TagParser::MediaFileInfo taginfo;
|
||||||
@@ -298,19 +295,19 @@ bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request
|
|||||||
taginfo.parseContainerFormat(diag, progress);
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.parseTracks(diag, progress);
|
taginfo.parseTracks(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.parseTags(diag, progress);
|
taginfo.parseTags(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taginfo.tags().size() <= 0) {
|
if (taginfo.tags().size() <= 0) {
|
||||||
@@ -335,13 +332,13 @@ bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request
|
|||||||
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
||||||
}
|
}
|
||||||
if (save_playcount) {
|
if (save_playcount) {
|
||||||
SaveSongPlaycountToFile(tag, song);
|
SaveSongPlaycountToFile(tag, song.playcount());
|
||||||
}
|
}
|
||||||
if (save_rating) {
|
if (save_rating) {
|
||||||
SaveSongRatingToFile(tag, song);
|
SaveSongRatingToFile(tag, song.rating());
|
||||||
}
|
}
|
||||||
if (save_cover) {
|
if (save_cover) {
|
||||||
SaveEmbeddedArt(tag, cover_data);
|
SaveEmbeddedArt(tag, cover.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,17 +349,17 @@ bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request
|
|||||||
qLog(Debug) << QString::fromStdString(msg.message());
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return Result::ErrorCode::Success;
|
||||||
}
|
}
|
||||||
catch(...) {}
|
catch(...) {}
|
||||||
|
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
TagReaderBase::Result TagReaderTagParser::LoadEmbeddedArt(const QString &filename, QByteArray &data) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) return QByteArray();
|
if (filename.isEmpty()) return Result::ErrorCode::FilenameMissing;
|
||||||
|
|
||||||
qLog(Debug) << "Loading art from" << filename;
|
qLog(Debug) << "Loading art from" << filename;
|
||||||
|
|
||||||
@@ -383,20 +380,20 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
|||||||
taginfo.parseContainerFormat(diag, progress);
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return QByteArray();
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.parseTags(diag, progress);
|
taginfo.parseTags(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return QByteArray();
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
||||||
QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
data = QByteArray(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return data;
|
return Result::ErrorCode::Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,7 +406,7 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
|||||||
}
|
}
|
||||||
catch(...) {}
|
catch(...) {}
|
||||||
|
|
||||||
return QByteArray();
|
return Result::ErrorCode::FileParseError;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,15 +416,13 @@ void TagReaderTagParser::SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const {
|
TagReaderBase::Result TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const {
|
||||||
|
|
||||||
if (request.filename().empty()) return false;
|
if (request.filename().empty()) return Result::ErrorCode::FilenameMissing;
|
||||||
|
|
||||||
const QString filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
|
||||||
|
|
||||||
qLog(Debug) << "Saving art to" << filename;
|
qLog(Debug) << "Saving art to" << filename;
|
||||||
|
|
||||||
const QByteArray cover_data = LoadCoverDataFromRequest(request);
|
const Cover cover = LoadCoverFromRequest(filename, request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -446,13 +441,13 @@ bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRe
|
|||||||
taginfo.parseContainerFormat(diag, progress);
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.parseTags(diag, progress);
|
taginfo.parseTags(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taginfo.tags().size() <= 0) {
|
if (taginfo.tags().size() <= 0) {
|
||||||
@@ -460,7 +455,7 @@ bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
SaveEmbeddedArt(tag, cover_data);
|
SaveEmbeddedArt(tag, cover.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.applyChanges(diag, progress);
|
taginfo.applyChanges(diag, progress);
|
||||||
@@ -470,28 +465,40 @@ bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRe
|
|||||||
qLog(Debug) << QString::fromStdString(msg.message());
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return Result::ErrorCode::Success;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(...) {}
|
catch(...) {}
|
||||||
|
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagReaderTagParser::SaveSongPlaycountToFile(TagParser::Tag*, const spb::tagreader::SongMetadata&) const {}
|
void TagReaderTagParser::SaveSongPlaycountToFile(TagParser::Tag *tag, const uint playcount) const {
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; }
|
Q_UNUSED(tag);
|
||||||
|
Q_UNUSED(playcount);
|
||||||
void TagReaderTagParser::SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const {
|
|
||||||
|
|
||||||
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
TagReaderBase::Result TagReaderTagParser::SaveSongPlaycountToFile(const QString &filename, const uint playcount) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) return false;
|
Q_UNUSED(filename);
|
||||||
|
Q_UNUSED(playcount);
|
||||||
|
|
||||||
|
return Result::ErrorCode::Unsupported;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagReaderTagParser::SaveSongRatingToFile(TagParser::Tag *tag, const float rating) const {
|
||||||
|
|
||||||
|
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(rating)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagReaderBase::Result TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const float rating) const {
|
||||||
|
|
||||||
|
if (filename.isEmpty()) return Result::ErrorCode::FilenameMissing;
|
||||||
|
|
||||||
qLog(Debug) << "Saving song rating to" << filename;
|
qLog(Debug) << "Saving song rating to" << filename;
|
||||||
|
|
||||||
@@ -509,19 +516,19 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb
|
|||||||
taginfo.parseContainerFormat(diag, progress);
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.parseTracks(diag, progress);
|
taginfo.parseTracks(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.parseTags(diag, progress);
|
taginfo.parseTags(diag, progress);
|
||||||
if (progress.isAborted()) {
|
if (progress.isAborted()) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taginfo.tags().size() <= 0) {
|
if (taginfo.tags().size() <= 0) {
|
||||||
@@ -529,7 +536,7 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
SaveSongRatingToFile(tag, song);
|
SaveSongRatingToFile(tag, rating);
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.applyChanges(diag, progress);
|
taginfo.applyChanges(diag, progress);
|
||||||
@@ -539,10 +546,10 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb
|
|||||||
qLog(Debug) << QString::fromStdString(msg.message());
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return Result::ErrorCode::Success;
|
||||||
}
|
}
|
||||||
catch(...) {}
|
catch(...) {}
|
||||||
|
|
||||||
return false;
|
return Result::ErrorCode::FileParseError;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
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
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -37,22 +37,21 @@
|
|||||||
class TagReaderTagParser : public TagReaderBase {
|
class TagReaderTagParser : public TagReaderBase {
|
||||||
public:
|
public:
|
||||||
explicit TagReaderTagParser();
|
explicit TagReaderTagParser();
|
||||||
~TagReaderTagParser();
|
|
||||||
|
|
||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override;
|
||||||
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
Result SaveSongRatingToFile(const QString &filename, const float rating) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SaveSongPlaycountToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SaveSongPlaycountToFile(TagParser::Tag *tag, const uint playcount) const;
|
||||||
void SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SaveSongRatingToFile(TagParser::Tag *tag, const float rating) const;
|
||||||
void SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const;
|
void SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ qt_wrap_cpp(MOC ${HEADERS})
|
|||||||
|
|
||||||
link_directories(${GLIB_LIBRARY_DIRS})
|
link_directories(${GLIB_LIBRARY_DIRS})
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(HAVE_TAGLIB)
|
||||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
if(HAVE_TAGPARSER)
|
||||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -39,12 +39,12 @@ target_link_libraries(strawberry-tagreader PRIVATE
|
|||||||
libstrawberry-tagreader
|
libstrawberry-tagreader
|
||||||
)
|
)
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(HAVE_TAGLIB)
|
||||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
target_link_libraries(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
if(HAVE_TAGPARSER)
|
||||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
target_link_libraries(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
Strawberry is free software: you can redistribute it and/or modify
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
@@ -27,20 +28,37 @@
|
|||||||
|
|
||||||
#include "tagreaderworker.h"
|
#include "tagreaderworker.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_TAGLIB
|
||||||
|
# include "tagreadertaglib.h"
|
||||||
|
# include "tagreadergme.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_TAGPARSER
|
||||||
|
# include "tagreadertagparser.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using std::make_shared;
|
||||||
|
using std::shared_ptr;
|
||||||
|
|
||||||
TagReaderWorker::TagReaderWorker(QIODevice *socket, QObject *parent)
|
TagReaderWorker::TagReaderWorker(QIODevice *socket, QObject *parent)
|
||||||
: AbstractMessageHandler<spb::tagreader::Message>(socket, parent) {}
|
: AbstractMessageHandler<spb::tagreader::Message>(socket, parent) {
|
||||||
|
|
||||||
|
#ifdef HAVE_TAGLIB
|
||||||
|
tagreaders_ << make_shared<TagReaderTagLib>();
|
||||||
|
tagreaders_ << make_shared<TagReaderGME>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_TAGPARSER
|
||||||
|
tagreaders_ << make_shared<TagReaderTagParser>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
|
void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
|
||||||
|
|
||||||
spb::tagreader::Message reply;
|
spb::tagreader::Message reply;
|
||||||
|
|
||||||
bool success = HandleMessage(message, reply, &tag_reader_);
|
HandleMessage(message, reply);
|
||||||
if (!success) {
|
|
||||||
#if defined(USE_TAGLIB)
|
|
||||||
HandleMessage(message, reply, &tag_reader_gme_);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
SendReply(message, &reply);
|
SendReply(message, &reply);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -53,48 +71,123 @@ void TagReaderWorker::DeviceClosed() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
|
void TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply) {
|
||||||
|
|
||||||
|
for (shared_ptr<TagReaderBase> reader : tagreaders_) {
|
||||||
|
|
||||||
if (message.has_is_media_file_request()) {
|
if (message.has_is_media_file_request()) {
|
||||||
const QString filename = QString::fromUtf8(message.is_media_file_request().filename().data(), static_cast<qint64>(message.is_media_file_request().filename().size()));
|
const QString filename = QString::fromStdString(message.is_media_file_request().filename());
|
||||||
bool success = reader->IsMediaFile(filename);
|
const bool success = reader->IsMediaFile(filename);
|
||||||
reply.mutable_is_media_file_response()->set_success(success);
|
reply.mutable_is_media_file_response()->set_success(success);
|
||||||
return success;
|
if (success) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (message.has_read_file_request()) {
|
|
||||||
const QString filename = QString::fromUtf8(message.read_file_request().filename().data(), static_cast<qint64>(static_cast<qint64>(message.read_file_request().filename().size())));
|
|
||||||
bool success = reader->ReadFile(filename, reply.mutable_read_file_response()->mutable_metadata());
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
else if (message.has_save_file_request()) {
|
if (message.has_read_file_request()) {
|
||||||
bool success = reader->SaveFile(message.save_file_request());
|
const QString filename = QString::fromStdString(message.read_file_request().filename());
|
||||||
reply.mutable_save_file_response()->set_success(success);
|
spb::tagreader::ReadFileResponse *response = reply.mutable_read_file_response();
|
||||||
return success;
|
const TagReaderBase::Result result = reader->ReadFile(filename, response->mutable_metadata());
|
||||||
|
response->set_success(result.success());
|
||||||
|
if (result.success()) {
|
||||||
|
if (response->has_error()) {
|
||||||
|
response->clear_error();
|
||||||
}
|
}
|
||||||
else if (message.has_load_embedded_art_request()) {
|
return;
|
||||||
const QString filename = QString::fromUtf8(message.load_embedded_art_request().filename().data(), static_cast<qint64>(static_cast<qint64>(message.load_embedded_art_request().filename().size())));
|
|
||||||
QByteArray data = reader->LoadEmbeddedArt(filename);
|
|
||||||
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (message.has_save_embedded_art_request()) {
|
else {
|
||||||
bool success = reader->SaveEmbeddedArt(message.save_embedded_art_request());
|
if (!response->has_error()) {
|
||||||
reply.mutable_save_embedded_art_response()->set_success(success);
|
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||||
return success;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message.has_write_file_request()) {
|
||||||
|
const QString filename = QString::fromStdString(message.write_file_request().filename());
|
||||||
|
const TagReaderBase::Result result = reader->WriteFile(filename, message.write_file_request());
|
||||||
|
spb::tagreader::WriteFileResponse *response = reply.mutable_write_file_response();
|
||||||
|
response->set_success(result.success());
|
||||||
|
if (result.success()) {
|
||||||
|
if (response->has_error()) {
|
||||||
|
response->clear_error();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!response->has_error()) {
|
||||||
|
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message.has_load_embedded_art_request()) {
|
||||||
|
const QString filename = QString::fromStdString(message.load_embedded_art_request().filename());
|
||||||
|
QByteArray data;
|
||||||
|
const TagReaderBase::Result result = reader->LoadEmbeddedArt(filename, data);
|
||||||
|
spb::tagreader::LoadEmbeddedArtResponse *response = reply.mutable_load_embedded_art_response();
|
||||||
|
response->set_success(result.success());
|
||||||
|
if (result.success()) {
|
||||||
|
response->set_data(data.toStdString());
|
||||||
|
if (response->has_error()) {
|
||||||
|
response->clear_error();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!response->has_error()) {
|
||||||
|
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message.has_save_embedded_art_request()) {
|
||||||
|
const QString filename = QString::fromStdString(message.save_embedded_art_request().filename());
|
||||||
|
const TagReaderBase::Result result = reader->SaveEmbeddedArt(filename, message.save_embedded_art_request());
|
||||||
|
spb::tagreader::SaveEmbeddedArtResponse *response = reply.mutable_save_embedded_art_response();
|
||||||
|
response->set_success(result.success());
|
||||||
|
if (result.success()) {
|
||||||
|
if (response->has_error()) {
|
||||||
|
response->clear_error();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!response->has_error()) {
|
||||||
|
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message.has_save_song_playcount_to_file_request()) {
|
||||||
|
const QString filename = QString::fromStdString(message.save_song_playcount_to_file_request().filename());
|
||||||
|
const TagReaderBase::Result result = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().playcount());
|
||||||
|
spb::tagreader::SaveSongPlaycountToFileResponse *response = reply.mutable_save_song_playcount_to_file_response();
|
||||||
|
response->set_success(result.success());
|
||||||
|
if (result.success()) {
|
||||||
|
if (response->has_error()) {
|
||||||
|
response->clear_error();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!response->has_error()) {
|
||||||
|
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message.has_save_song_rating_to_file_request()) {
|
||||||
|
const QString filename = QString::fromStdString(message.save_song_rating_to_file_request().filename());
|
||||||
|
const TagReaderBase::Result result = reader->SaveSongRatingToFile(filename, message.save_song_rating_to_file_request().rating());
|
||||||
|
spb::tagreader::SaveSongRatingToFileResponse *response = reply.mutable_save_song_rating_to_file_response();
|
||||||
|
response->set_success(result.success());
|
||||||
|
if (result.success()) {
|
||||||
|
if (response->has_error()) {
|
||||||
|
response->clear_error();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!response->has_error()) {
|
||||||
|
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||||
}
|
}
|
||||||
else if (message.has_save_song_playcount_to_file_request()) {
|
|
||||||
const QString filename = QString::fromUtf8(message.save_song_playcount_to_file_request().filename().data(), static_cast<qint64>(static_cast<qint64>(message.save_song_playcount_to_file_request().filename().size())));
|
|
||||||
bool success = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().metadata());
|
|
||||||
reply.mutable_save_song_playcount_to_file_response()->set_success(success);
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
else if (message.has_save_song_rating_to_file_request()) {
|
|
||||||
const QString filename = QString::fromUtf8(message.save_song_rating_to_file_request().filename().data(), static_cast<qint64>(message.save_song_rating_to_file_request().filename().size()));
|
|
||||||
bool success = reader->SaveSongRatingToFile(filename, message.save_song_rating_to_file_request().metadata());
|
|
||||||
reply.mutable_save_song_rating_to_file_response()->set_success(success);
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
Strawberry is free software: you can redistribute it and/or modify
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,19 +21,19 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
#include "core/messagehandler.h"
|
#include "core/messagehandler.h"
|
||||||
#if defined(USE_TAGLIB)
|
|
||||||
# include "tagreadertaglib.h"
|
|
||||||
# include "tagreadergme.h"
|
|
||||||
#elif defined(USE_TAGPARSER)
|
|
||||||
# include "tagreadertagparser.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "tagreadermessages.pb.h"
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
|
class TagReaderBase;
|
||||||
|
|
||||||
|
using std::shared_ptr;
|
||||||
|
|
||||||
class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
|
class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -46,15 +46,9 @@ class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
|
|||||||
void DeviceClosed() override;
|
void DeviceClosed() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Handle message using specific TagReaderBase implementation. Returns true on successful message handle.
|
void HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply);
|
||||||
bool HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase* reader);
|
|
||||||
|
|
||||||
#if defined(USE_TAGLIB)
|
QList<shared_ptr<TagReaderBase>> tagreaders_;
|
||||||
TagReaderTagLib tag_reader_;
|
|
||||||
TagReaderGME tag_reader_gme_;
|
|
||||||
#elif defined(USE_TAGPARSER)
|
|
||||||
TagReaderTagParser tag_reader_;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TAGREADERWORKER_H
|
#endif // TAGREADERWORKER_H
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ set(SOURCES
|
|||||||
core/networktimeouts.cpp
|
core/networktimeouts.cpp
|
||||||
core/networkproxyfactory.cpp
|
core/networkproxyfactory.cpp
|
||||||
core/qtfslistener.cpp
|
core/qtfslistener.cpp
|
||||||
|
core/settings.cpp
|
||||||
core/settingsprovider.cpp
|
core/settingsprovider.cpp
|
||||||
core/signalchecker.cpp
|
core/signalchecker.cpp
|
||||||
core/song.cpp
|
core/song.cpp
|
||||||
@@ -39,7 +40,7 @@ set(SOURCES
|
|||||||
core/scopedtransaction.cpp
|
core/scopedtransaction.cpp
|
||||||
core/translations.cpp
|
core/translations.cpp
|
||||||
core/systemtrayicon.cpp
|
core/systemtrayicon.cpp
|
||||||
|
core/localredirectserver.cpp
|
||||||
utilities/strutils.cpp
|
utilities/strutils.cpp
|
||||||
utilities/envutils.cpp
|
utilities/envutils.cpp
|
||||||
utilities/colorutils.cpp
|
utilities/colorutils.cpp
|
||||||
@@ -57,7 +58,10 @@ set(SOURCES
|
|||||||
utilities/filemanagerutils.cpp
|
utilities/filemanagerutils.cpp
|
||||||
utilities/coverutils.cpp
|
utilities/coverutils.cpp
|
||||||
utilities/screenutils.cpp
|
utilities/screenutils.cpp
|
||||||
utilities/searchparserutils.cpp
|
utilities/textencodingutils.cpp
|
||||||
|
|
||||||
|
filterparser/filterparser.cpp
|
||||||
|
filterparser/filtertree.cpp
|
||||||
|
|
||||||
engine/enginebase.cpp
|
engine/enginebase.cpp
|
||||||
engine/enginedevice.cpp
|
engine/enginedevice.cpp
|
||||||
@@ -70,8 +74,10 @@ set(SOURCES
|
|||||||
analyzer/analyzercontainer.cpp
|
analyzer/analyzercontainer.cpp
|
||||||
analyzer/blockanalyzer.cpp
|
analyzer/blockanalyzer.cpp
|
||||||
analyzer/boomanalyzer.cpp
|
analyzer/boomanalyzer.cpp
|
||||||
|
analyzer/turbineanalyzer.cpp
|
||||||
|
analyzer/sonogramanalyzer.cpp
|
||||||
|
analyzer/waverubberanalyzer.cpp
|
||||||
analyzer/rainbowanalyzer.cpp
|
analyzer/rainbowanalyzer.cpp
|
||||||
analyzer/sonogram.cpp
|
|
||||||
|
|
||||||
equalizer/equalizer.cpp
|
equalizer/equalizer.cpp
|
||||||
equalizer/equalizerslider.cpp
|
equalizer/equalizerslider.cpp
|
||||||
@@ -89,19 +95,19 @@ set(SOURCES
|
|||||||
collection/collectiondirectorymodel.cpp
|
collection/collectiondirectorymodel.cpp
|
||||||
collection/collectionfilteroptions.cpp
|
collection/collectionfilteroptions.cpp
|
||||||
collection/collectionfilterwidget.cpp
|
collection/collectionfilterwidget.cpp
|
||||||
|
collection/collectionfilter.cpp
|
||||||
collection/collectionplaylistitem.cpp
|
collection/collectionplaylistitem.cpp
|
||||||
collection/collectionquery.cpp
|
collection/collectionquery.cpp
|
||||||
collection/collectionqueryoptions.cpp
|
|
||||||
collection/savedgroupingmanager.cpp
|
collection/savedgroupingmanager.cpp
|
||||||
collection/groupbydialog.cpp
|
collection/groupbydialog.cpp
|
||||||
collection/collectiontask.cpp
|
collection/collectiontask.cpp
|
||||||
|
collection/collectionmodelupdate.cpp
|
||||||
|
|
||||||
playlist/playlist.cpp
|
playlist/playlist.cpp
|
||||||
playlist/playlistbackend.cpp
|
playlist/playlistbackend.cpp
|
||||||
playlist/playlistcontainer.cpp
|
playlist/playlistcontainer.cpp
|
||||||
playlist/playlistdelegates.cpp
|
playlist/playlistdelegates.cpp
|
||||||
playlist/playlistfilter.cpp
|
playlist/playlistfilter.cpp
|
||||||
playlist/playlistfilterparser.cpp
|
|
||||||
playlist/playlistheader.cpp
|
playlist/playlistheader.cpp
|
||||||
playlist/playlistitem.cpp
|
playlist/playlistitem.cpp
|
||||||
playlist/playlistlistcontainer.cpp
|
playlist/playlistlistcontainer.cpp
|
||||||
@@ -170,7 +176,7 @@ set(SOURCES
|
|||||||
covermanager/deezercoverprovider.cpp
|
covermanager/deezercoverprovider.cpp
|
||||||
covermanager/qobuzcoverprovider.cpp
|
covermanager/qobuzcoverprovider.cpp
|
||||||
covermanager/musixmatchcoverprovider.cpp
|
covermanager/musixmatchcoverprovider.cpp
|
||||||
covermanager/spotifycoverprovider.cpp
|
covermanager/opentidalcoverprovider.cpp
|
||||||
|
|
||||||
lyrics/lyricsproviders.cpp
|
lyrics/lyricsproviders.cpp
|
||||||
lyrics/lyricsprovider.cpp
|
lyrics/lyricsprovider.cpp
|
||||||
@@ -188,7 +194,7 @@ set(SOURCES
|
|||||||
lyrics/songlyricscomlyricsprovider.cpp
|
lyrics/songlyricscomlyricsprovider.cpp
|
||||||
lyrics/azlyricscomlyricsprovider.cpp
|
lyrics/azlyricscomlyricsprovider.cpp
|
||||||
lyrics/elyricsnetlyricsprovider.cpp
|
lyrics/elyricsnetlyricsprovider.cpp
|
||||||
lyrics/lyricsmodecomlyricsprovider.cpp
|
lyrics/letraslyricsprovider.cpp
|
||||||
|
|
||||||
providers/musixmatchprovider.cpp
|
providers/musixmatchprovider.cpp
|
||||||
|
|
||||||
@@ -196,6 +202,7 @@ set(SOURCES
|
|||||||
settings/settingspage.cpp
|
settings/settingspage.cpp
|
||||||
settings/behavioursettingspage.cpp
|
settings/behavioursettingspage.cpp
|
||||||
settings/collectionsettingspage.cpp
|
settings/collectionsettingspage.cpp
|
||||||
|
settings/collectionsettingsdirectorymodel.cpp
|
||||||
settings/backendsettingspage.cpp
|
settings/backendsettingspage.cpp
|
||||||
settings/playlistsettingspage.cpp
|
settings/playlistsettingspage.cpp
|
||||||
settings/scrobblersettingspage.cpp
|
settings/scrobblersettingspage.cpp
|
||||||
@@ -223,6 +230,8 @@ set(SOURCES
|
|||||||
widgets/busyindicator.cpp
|
widgets/busyindicator.cpp
|
||||||
widgets/clickablelabel.cpp
|
widgets/clickablelabel.cpp
|
||||||
widgets/fancytabwidget.cpp
|
widgets/fancytabwidget.cpp
|
||||||
|
widgets/fancytabbar.cpp
|
||||||
|
widgets/fancytabdata.cpp
|
||||||
widgets/favoritewidget.cpp
|
widgets/favoritewidget.cpp
|
||||||
widgets/fileview.cpp
|
widgets/fileview.cpp
|
||||||
widgets/fileviewlist.cpp
|
widgets/fileviewlist.cpp
|
||||||
@@ -249,19 +258,18 @@ set(SOURCES
|
|||||||
osd/osdbase.cpp
|
osd/osdbase.cpp
|
||||||
osd/osdpretty.cpp
|
osd/osdpretty.cpp
|
||||||
|
|
||||||
internet/internetservices.cpp
|
streaming/streamingservices.cpp
|
||||||
internet/internetservice.cpp
|
streaming/streamingservice.cpp
|
||||||
internet/internetplaylistitem.cpp
|
streaming/streamplaylistitem.cpp
|
||||||
internet/internetsearchview.cpp
|
streaming/streamingsearchview.cpp
|
||||||
internet/internetsearchmodel.cpp
|
streaming/streamingsearchmodel.cpp
|
||||||
internet/internetsearchsortmodel.cpp
|
streaming/streamingsearchsortmodel.cpp
|
||||||
internet/internetsearchitemdelegate.cpp
|
streaming/streamingsearchitemdelegate.cpp
|
||||||
internet/localredirectserver.cpp
|
streaming/streamingsongsview.cpp
|
||||||
internet/internetsongsview.cpp
|
streaming/streamingtabsview.cpp
|
||||||
internet/internettabsview.cpp
|
streaming/streamingcollectionview.cpp
|
||||||
internet/internetcollectionview.cpp
|
streaming/streamingcollectionviewcontainer.cpp
|
||||||
internet/internetcollectionviewcontainer.cpp
|
streaming/streamingsearchview.cpp
|
||||||
internet/internetsearchview.cpp
|
|
||||||
|
|
||||||
radios/radioservices.cpp
|
radios/radioservices.cpp
|
||||||
radios/radiobackend.cpp
|
radios/radiobackend.cpp
|
||||||
@@ -306,6 +314,7 @@ set(HEADERS
|
|||||||
core/threadsafenetworkdiskcache.h
|
core/threadsafenetworkdiskcache.h
|
||||||
core/networktimeouts.h
|
core/networktimeouts.h
|
||||||
core/qtfslistener.h
|
core/qtfslistener.h
|
||||||
|
core/settings.h
|
||||||
core/songloader.h
|
core/songloader.h
|
||||||
core/tagreaderclient.h
|
core/tagreaderclient.h
|
||||||
core/taskmanager.h
|
core/taskmanager.h
|
||||||
@@ -316,6 +325,7 @@ set(HEADERS
|
|||||||
core/potranslator.h
|
core/potranslator.h
|
||||||
core/mimedata.h
|
core/mimedata.h
|
||||||
core/stylesheetloader.h
|
core/stylesheetloader.h
|
||||||
|
core/localredirectserver.h
|
||||||
|
|
||||||
engine/enginebase.h
|
engine/enginebase.h
|
||||||
engine/devicefinders.h
|
engine/devicefinders.h
|
||||||
@@ -324,8 +334,10 @@ set(HEADERS
|
|||||||
analyzer/analyzercontainer.h
|
analyzer/analyzercontainer.h
|
||||||
analyzer/blockanalyzer.h
|
analyzer/blockanalyzer.h
|
||||||
analyzer/boomanalyzer.h
|
analyzer/boomanalyzer.h
|
||||||
|
analyzer/turbineanalyzer.h
|
||||||
|
analyzer/sonogramanalyzer.h
|
||||||
|
analyzer/waverubberanalyzer.h
|
||||||
analyzer/rainbowanalyzer.h
|
analyzer/rainbowanalyzer.h
|
||||||
analyzer/sonogram.h
|
|
||||||
|
|
||||||
equalizer/equalizer.h
|
equalizer/equalizer.h
|
||||||
equalizer/equalizerslider.h
|
equalizer/equalizerslider.h
|
||||||
@@ -342,6 +354,7 @@ set(HEADERS
|
|||||||
collection/collectionviewcontainer.h
|
collection/collectionviewcontainer.h
|
||||||
collection/collectiondirectorymodel.h
|
collection/collectiondirectorymodel.h
|
||||||
collection/collectionfilterwidget.h
|
collection/collectionfilterwidget.h
|
||||||
|
collection/collectionfilter.h
|
||||||
collection/savedgroupingmanager.h
|
collection/savedgroupingmanager.h
|
||||||
collection/groupbydialog.h
|
collection/groupbydialog.h
|
||||||
|
|
||||||
@@ -415,7 +428,7 @@ set(HEADERS
|
|||||||
covermanager/deezercoverprovider.h
|
covermanager/deezercoverprovider.h
|
||||||
covermanager/qobuzcoverprovider.h
|
covermanager/qobuzcoverprovider.h
|
||||||
covermanager/musixmatchcoverprovider.h
|
covermanager/musixmatchcoverprovider.h
|
||||||
covermanager/spotifycoverprovider.h
|
covermanager/opentidalcoverprovider.h
|
||||||
|
|
||||||
lyrics/lyricsproviders.h
|
lyrics/lyricsproviders.h
|
||||||
lyrics/lyricsprovider.h
|
lyrics/lyricsprovider.h
|
||||||
@@ -431,12 +444,13 @@ set(HEADERS
|
|||||||
lyrics/songlyricscomlyricsprovider.h
|
lyrics/songlyricscomlyricsprovider.h
|
||||||
lyrics/azlyricscomlyricsprovider.h
|
lyrics/azlyricscomlyricsprovider.h
|
||||||
lyrics/elyricsnetlyricsprovider.h
|
lyrics/elyricsnetlyricsprovider.h
|
||||||
lyrics/lyricsmodecomlyricsprovider.h
|
lyrics/letraslyricsprovider.h
|
||||||
|
|
||||||
settings/settingsdialog.h
|
settings/settingsdialog.h
|
||||||
settings/settingspage.h
|
settings/settingspage.h
|
||||||
settings/behavioursettingspage.h
|
settings/behavioursettingspage.h
|
||||||
settings/collectionsettingspage.h
|
settings/collectionsettingspage.h
|
||||||
|
settings/collectionsettingsdirectorymodel.h
|
||||||
settings/backendsettingspage.h
|
settings/backendsettingspage.h
|
||||||
settings/playlistsettingspage.h
|
settings/playlistsettingspage.h
|
||||||
settings/scrobblersettingspage.h
|
settings/scrobblersettingspage.h
|
||||||
@@ -464,6 +478,8 @@ set(HEADERS
|
|||||||
widgets/busyindicator.h
|
widgets/busyindicator.h
|
||||||
widgets/clickablelabel.h
|
widgets/clickablelabel.h
|
||||||
widgets/fancytabwidget.h
|
widgets/fancytabwidget.h
|
||||||
|
widgets/fancytabbar.h
|
||||||
|
widgets/fancytabdata.h
|
||||||
widgets/favoritewidget.h
|
widgets/favoritewidget.h
|
||||||
widgets/fileview.h
|
widgets/fileview.h
|
||||||
widgets/fileviewlist.h
|
widgets/fileviewlist.h
|
||||||
@@ -486,22 +502,22 @@ set(HEADERS
|
|||||||
widgets/qsearchfield.h
|
widgets/qsearchfield.h
|
||||||
widgets/ratingwidget.h
|
widgets/ratingwidget.h
|
||||||
widgets/forcescrollperpixel.h
|
widgets/forcescrollperpixel.h
|
||||||
|
widgets/resizabletextedit.h
|
||||||
|
|
||||||
osd/osdbase.h
|
osd/osdbase.h
|
||||||
osd/osdpretty.h
|
osd/osdpretty.h
|
||||||
|
|
||||||
internet/internetservices.h
|
streaming/streamingservices.h
|
||||||
internet/internetservice.h
|
streaming/streamingservice.h
|
||||||
internet/internetsongmimedata.h
|
streaming/streamsongmimedata.h
|
||||||
internet/internetsearchmodel.h
|
streaming/streamingsearchmodel.h
|
||||||
internet/internetsearchsortmodel.h
|
streaming/streamingsearchsortmodel.h
|
||||||
internet/internetsearchitemdelegate.h
|
streaming/streamingsearchitemdelegate.h
|
||||||
internet/internetsearchview.h
|
streaming/streamingsearchview.h
|
||||||
internet/localredirectserver.h
|
streaming/streamingsongsview.h
|
||||||
internet/internetsongsview.h
|
streaming/streamingtabsview.h
|
||||||
internet/internettabsview.h
|
streaming/streamingcollectionview.h
|
||||||
internet/internetcollectionview.h
|
streaming/streamingcollectionviewcontainer.h
|
||||||
internet/internetcollectionviewcontainer.h
|
|
||||||
|
|
||||||
radios/radioservices.h
|
radios/radioservices.h
|
||||||
radios/radiobackend.h
|
radios/radiobackend.h
|
||||||
@@ -592,9 +608,9 @@ set(UI
|
|||||||
|
|
||||||
osd/osdpretty.ui
|
osd/osdpretty.ui
|
||||||
|
|
||||||
internet/internettabsview.ui
|
streaming/streamingtabsview.ui
|
||||||
internet/internetcollectionviewcontainer.ui
|
streaming/streamingcollectionviewcontainer.ui
|
||||||
internet/internetsearchview.ui
|
streaming/streamingsearchview.ui
|
||||||
|
|
||||||
radios/radioviewcontainer.ui
|
radios/radioviewcontainer.ui
|
||||||
|
|
||||||
@@ -850,7 +866,7 @@ optional_source(WIN32
|
|||||||
HEADERS
|
HEADERS
|
||||||
core/windows7thumbbar.h
|
core/windows7thumbbar.h
|
||||||
)
|
)
|
||||||
optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp)
|
optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp engine/asiodevicefinder.cpp)
|
||||||
|
|
||||||
optional_source(HAVE_SUBSONIC
|
optional_source(HAVE_SUBSONIC
|
||||||
SOURCES
|
SOURCES
|
||||||
@@ -896,6 +912,25 @@ optional_source(HAVE_TIDAL
|
|||||||
settings/tidalsettingspage.ui
|
settings/tidalsettingspage.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
optional_source(HAVE_SPOTIFY
|
||||||
|
SOURCES
|
||||||
|
spotify/spotifyservice.cpp
|
||||||
|
spotify/spotifybaserequest.cpp
|
||||||
|
spotify/spotifyrequest.cpp
|
||||||
|
spotify/spotifyfavoriterequest.cpp
|
||||||
|
settings/spotifysettingspage.cpp
|
||||||
|
covermanager/spotifycoverprovider.cpp
|
||||||
|
HEADERS
|
||||||
|
spotify/spotifyservice.h
|
||||||
|
spotify/spotifybaserequest.h
|
||||||
|
spotify/spotifyrequest.h
|
||||||
|
spotify/spotifyfavoriterequest.h
|
||||||
|
settings/spotifysettingspage.h
|
||||||
|
covermanager/spotifycoverprovider.h
|
||||||
|
UI
|
||||||
|
settings/spotifysettingspage.ui
|
||||||
|
)
|
||||||
|
|
||||||
optional_source(HAVE_QOBUZ
|
optional_source(HAVE_QOBUZ
|
||||||
SOURCES
|
SOURCES
|
||||||
qobuz/qobuzservice.cpp
|
qobuz/qobuzservice.cpp
|
||||||
@@ -971,7 +1006,7 @@ if(HAVE_TRANSLATIONS)
|
|||||||
|
|
||||||
add_pot(POT
|
add_pot(POT
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/translations/header
|
${CMAKE_CURRENT_SOURCE_DIR}/translations/header
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/translations/translations.pot
|
${CMAKE_CURRENT_BINARY_DIR}/translations/translations.pot
|
||||||
${SOURCES}
|
${SOURCES}
|
||||||
${MOC}
|
${MOC}
|
||||||
${UIC}
|
${UIC}
|
||||||
@@ -988,14 +1023,9 @@ link_directories(
|
|||||||
${SQLITE_LIBRARY_DIRS}
|
${SQLITE_LIBRARY_DIRS}
|
||||||
${PROTOBUF_LIBRARY_DIRS}
|
${PROTOBUF_LIBRARY_DIRS}
|
||||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||||
|
${ICU_LIBRARY_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(HAVE_ICU)
|
|
||||||
link_directories(${ICU_LIBRARY_DIRS})
|
|
||||||
else()
|
|
||||||
link_directories(${Iconv_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_ALSA)
|
if(HAVE_ALSA)
|
||||||
link_directories(${ALSA_LIBRARY_DIRS})
|
link_directories(${ALSA_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
@@ -1051,11 +1081,11 @@ if(HAVE_LIBMTP)
|
|||||||
link_directories(${LIBMTP_LIBRARY_DIRS})
|
link_directories(${LIBMTP_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(HAVE_TAGLIB)
|
||||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
if(HAVE_TAGPARSER)
|
||||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -1079,6 +1109,7 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
|
|||||||
${GOBJECT_INCLUDE_DIRS}
|
${GOBJECT_INCLUDE_DIRS}
|
||||||
${SQLITE_INCLUDE_DIRS}
|
${SQLITE_INCLUDE_DIRS}
|
||||||
${PROTOBUF_INCLUDE_DIRS}
|
${PROTOBUF_INCLUDE_DIRS}
|
||||||
|
${ICU_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||||
@@ -1101,6 +1132,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
${GOBJECT_LIBRARIES}
|
${GOBJECT_LIBRARIES}
|
||||||
${SQLITE_LIBRARIES}
|
${SQLITE_LIBRARIES}
|
||||||
|
${ICU_LIBRARIES}
|
||||||
Qt${QT_VERSION_MAJOR}::Core
|
Qt${QT_VERSION_MAJOR}::Core
|
||||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||||
Qt${QT_VERSION_MAJOR}::Gui
|
Qt${QT_VERSION_MAJOR}::Gui
|
||||||
@@ -1121,17 +1153,6 @@ if(HAVE_X11_GLOBALSHORTCUTS AND HAVE_X11EXTRAS)
|
|||||||
target_link_libraries(strawberry_lib PUBLIC Qt${QT_VERSION_MAJOR}::X11Extras)
|
target_link_libraries(strawberry_lib PUBLIC Qt${QT_VERSION_MAJOR}::X11Extras)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_ICU)
|
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ICU_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${ICU_LIBRARIES})
|
|
||||||
else()
|
|
||||||
if(FREEBSD AND NOT Iconv_LIBRARIES)
|
|
||||||
set(Iconv_LIBRARIES iconv)
|
|
||||||
endif()
|
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${Iconv_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${Iconv_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_ALSA)
|
if(HAVE_ALSA)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ void AnalyzerBase::ChangeTimeout(const int timeout) {
|
|||||||
void AnalyzerBase::transform(Scope &scope) {
|
void AnalyzerBase::transform(Scope &scope) {
|
||||||
|
|
||||||
QVector<float> aux(fht_->size());
|
QVector<float> aux(fht_->size());
|
||||||
if (static_cast<unsigned long int>(aux.size()) >= scope.size()) {
|
if (static_cast<quint64>(aux.size()) >= scope.size()) {
|
||||||
std::copy(scope.begin(), scope.end(), aux.begin());
|
std::copy(scope.begin(), scope.end(), aux.begin());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -108,7 +108,7 @@ void AnalyzerBase::paintEvent(QPaintEvent *e) {
|
|||||||
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
||||||
|
|
||||||
switch (engine_->state()) {
|
switch (engine_->state()) {
|
||||||
case EngineBase::State::Playing: {
|
case EngineBase::State::Playing:{
|
||||||
const EngineBase::Scope &thescope = engine_->scope(timeout_);
|
const EngineBase::Scope &thescope = engine_->scope(timeout_);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,14 @@
|
|||||||
#include "analyzerbase.h"
|
#include "analyzerbase.h"
|
||||||
#include "blockanalyzer.h"
|
#include "blockanalyzer.h"
|
||||||
#include "boomanalyzer.h"
|
#include "boomanalyzer.h"
|
||||||
|
#include "turbineanalyzer.h"
|
||||||
|
#include "sonogramanalyzer.h"
|
||||||
|
#include "waverubberanalyzer.h"
|
||||||
#include "rainbowanalyzer.h"
|
#include "rainbowanalyzer.h"
|
||||||
#include "sonogram.h"
|
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/shared_ptr.h"
|
#include "core/shared_ptr.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
@@ -52,10 +55,12 @@ const char *AnalyzerContainer::kSettingsGroup = "Analyzer";
|
|||||||
const char *AnalyzerContainer::kSettingsFramerate = "framerate";
|
const char *AnalyzerContainer::kSettingsFramerate = "framerate";
|
||||||
|
|
||||||
// Framerates
|
// Framerates
|
||||||
const int AnalyzerContainer::kLowFramerate = 20;
|
namespace {
|
||||||
const int AnalyzerContainer::kMediumFramerate = 25;
|
constexpr int kLowFramerate = 20;
|
||||||
const int AnalyzerContainer::kHighFramerate = 30;
|
constexpr int kMediumFramerate = 25;
|
||||||
const int AnalyzerContainer::kSuperHighFramerate = 60;
|
constexpr int kHighFramerate = 30;
|
||||||
|
constexpr int kSuperHighFramerate = 60;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
@@ -84,9 +89,11 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
|||||||
|
|
||||||
AddAnalyzerType<BlockAnalyzer>();
|
AddAnalyzerType<BlockAnalyzer>();
|
||||||
AddAnalyzerType<BoomAnalyzer>();
|
AddAnalyzerType<BoomAnalyzer>();
|
||||||
AddAnalyzerType<NyanCatAnalyzer>();
|
AddAnalyzerType<TurbineAnalyzer>();
|
||||||
|
AddAnalyzerType<SonogramAnalyzer>();
|
||||||
|
AddAnalyzerType<WaveRubberAnalyzer>();
|
||||||
AddAnalyzerType<RainbowDashAnalyzer>();
|
AddAnalyzerType<RainbowDashAnalyzer>();
|
||||||
AddAnalyzerType<Sonogram>();
|
AddAnalyzerType<NyanCatAnalyzer>();
|
||||||
|
|
||||||
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
||||||
disable_action_->setCheckable(true);
|
disable_action_->setCheckable(true);
|
||||||
@@ -178,9 +185,9 @@ void AnalyzerContainer::ChangeFramerate(int new_framerate) {
|
|||||||
|
|
||||||
void AnalyzerContainer::Load() {
|
void AnalyzerContainer::Load() {
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
QString type = s.value("type", "BlockAnalyzer").toString();
|
QString type = s.value("type", QStringLiteral("BlockAnalyzer")).toString();
|
||||||
current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt();
|
current_framerate_ = s.value(kSettingsFramerate, kMediumFramerate).toInt();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
@@ -191,12 +198,16 @@ void AnalyzerContainer::Load() {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int i = 0; i < analyzer_types_.count(); ++i) {
|
for (int i = 0; i < analyzer_types_.count(); ++i) {
|
||||||
if (type == analyzer_types_[i]->className()) {
|
if (type == QString::fromLatin1(analyzer_types_[i]->className())) {
|
||||||
ChangeAnalyzer(i);
|
ChangeAnalyzer(i);
|
||||||
actions_[i]->setChecked(true);
|
actions_[i]->setChecked(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!current_analyzer_) {
|
||||||
|
ChangeAnalyzer(0);
|
||||||
|
actions_[0]->setChecked(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Framerate
|
// Framerate
|
||||||
@@ -215,7 +226,7 @@ void AnalyzerContainer::SaveFramerate(const int framerate) {
|
|||||||
|
|
||||||
// For now, framerate is common for all analyzers. Maybe each analyzer should have its own framerate?
|
// For now, framerate is common for all analyzers. Maybe each analyzer should have its own framerate?
|
||||||
current_framerate_ = framerate;
|
current_framerate_ = framerate;
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
s.setValue(kSettingsFramerate, current_framerate_);
|
s.setValue(kSettingsFramerate, current_framerate_);
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -224,9 +235,9 @@ void AnalyzerContainer::SaveFramerate(const int framerate) {
|
|||||||
|
|
||||||
void AnalyzerContainer::Save() {
|
void AnalyzerContainer::Save() {
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
s.setValue("type", current_analyzer_ ? current_analyzer_->metaObject()->className() : QVariant());
|
s.setValue("type", current_analyzer_ ? QString::fromLatin1(current_analyzer_->metaObject()->className()) : QVariant());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ class AnalyzerContainer : public QWidget {
|
|||||||
explicit AnalyzerContainer(QWidget *parent);
|
explicit AnalyzerContainer(QWidget *parent);
|
||||||
|
|
||||||
void SetEngine(SharedPtr<EngineBase> engine);
|
void SetEngine(SharedPtr<EngineBase> engine);
|
||||||
void SetActions(QAction *visualisation);
|
|
||||||
|
|
||||||
static const char *kSettingsGroup;
|
static const char *kSettingsGroup;
|
||||||
static const char *kSettingsFramerate;
|
static const char *kSettingsFramerate;
|
||||||
@@ -55,7 +54,7 @@ class AnalyzerContainer : public QWidget {
|
|||||||
void WheelEvent(const int delta);
|
void WheelEvent(const int delta);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mouseReleaseEvent(QMouseEvent*) override;
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
void wheelEvent(QWheelEvent *e) override;
|
void wheelEvent(QWheelEvent *e) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@@ -65,11 +64,6 @@ class AnalyzerContainer : public QWidget {
|
|||||||
void ShowPopupMenu();
|
void ShowPopupMenu();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int kLowFramerate;
|
|
||||||
static const int kMediumFramerate;
|
|
||||||
static const int kHighFramerate;
|
|
||||||
static const int kSuperHighFramerate;
|
|
||||||
|
|
||||||
void Load();
|
void Load();
|
||||||
void Save();
|
void Save();
|
||||||
void SaveFramerate(const int framerate);
|
void SaveFramerate(const int framerate);
|
||||||
|
|||||||
@@ -36,12 +36,14 @@
|
|||||||
#include "analyzerbase.h"
|
#include "analyzerbase.h"
|
||||||
#include "fht.h"
|
#include "fht.h"
|
||||||
|
|
||||||
const int BlockAnalyzer::kHeight = 2;
|
namespace {
|
||||||
const int BlockAnalyzer::kWidth = 4;
|
constexpr int kHeight = 2;
|
||||||
const int BlockAnalyzer::kMinRows = 3; // arbitrary
|
constexpr int kWidth = 4;
|
||||||
const int BlockAnalyzer::kMinColumns = 32; // arbitrary
|
constexpr int kMinRows = 3; // arbitrary
|
||||||
const int BlockAnalyzer::kMaxColumns = 256; // must be 2**n
|
constexpr int kMinColumns = 32; // arbitrary
|
||||||
const int BlockAnalyzer::kFadeSize = 90;
|
constexpr int kMaxColumns = 256; // must be 2**n
|
||||||
|
constexpr int kFadeSize = 90;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
const char *BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
|
const char *BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
|
||||||
|
|
||||||
@@ -136,7 +138,7 @@ void BlockAnalyzer::transform(Scope &s) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockAnalyzer::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
void BlockAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
|
||||||
|
|
||||||
// y = 2 3 2 1 0 2
|
// y = 2 3 2 1 0 2
|
||||||
// . . . . # .
|
// . . . . # .
|
||||||
@@ -266,12 +268,12 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, int amount) {
|
|||||||
|
|
||||||
// value is the best measure of contrast
|
// value is the best measure of contrast
|
||||||
// if there is enough difference in value already, return fg unchanged
|
// if there is enough difference in value already, return fg unchanged
|
||||||
if (dv > static_cast<int>(amount)) return fg;
|
if (dv > amount) return fg;
|
||||||
|
|
||||||
int ds = abs(bs - fs);
|
int ds = abs(bs - fs);
|
||||||
|
|
||||||
// saturation is good enough too. But not as good. TODO adapt this a little
|
// saturation is good enough too. But not as good. TODO adapt this a little
|
||||||
if (ds > static_cast<int>(amount)) return fg;
|
if (ds > amount) return fg;
|
||||||
|
|
||||||
int dh = abs(bh - fh);
|
int dh = abs(bh - fh);
|
||||||
|
|
||||||
@@ -285,7 +287,7 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, int amount) {
|
|||||||
if (ds > amount / 2 && (bs > 125 && fs > 125)) {
|
if (ds > amount / 2 && (bs > 125 && fs > 125)) {
|
||||||
return fg;
|
return fg;
|
||||||
}
|
}
|
||||||
else if (dv > amount / 2 && (bv > 125 && fv > 125)) {
|
if (dv > amount / 2 && (bv > 125 && fv > 125)) {
|
||||||
return fg;
|
return fg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,7 +296,7 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, int amount) {
|
|||||||
// low saturation on a low saturation is sad
|
// low saturation on a low saturation is sad
|
||||||
const int tmp = 50 - fs;
|
const int tmp = 50 - fs;
|
||||||
fs = 50;
|
fs = 50;
|
||||||
if (static_cast<int>(amount) > tmp) {
|
if (amount > tmp) {
|
||||||
amount -= tmp;
|
amount -= tmp;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -310,25 +312,25 @@ QColor ensureContrast(const QColor &bg, const QColor &fg, int amount) {
|
|||||||
if (amount > 0) adjustToLimits(bs, fs, amount);
|
if (amount > 0) adjustToLimits(bs, fs, amount);
|
||||||
|
|
||||||
// see if we need to adjust the hue
|
// see if we need to adjust the hue
|
||||||
if (static_cast<int>(amount) > 0)
|
if (amount > 0)
|
||||||
fh += static_cast<int>(amount); // cycles around;
|
fh += amount; // cycles around;
|
||||||
|
|
||||||
return QColor::fromHsv(fh, fs, fv);
|
return QColor::fromHsv(fh, fs, fv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fv > bv && bv > static_cast<int>(amount)) {
|
if (fv > bv && bv > amount) {
|
||||||
return QColor::fromHsv(fh, fs, bv - static_cast<int>(amount));
|
return QColor::fromHsv(fh, fs, bv - amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fv < bv && fv > static_cast<int>(amount)) {
|
if (fv < bv && fv > amount) {
|
||||||
return QColor::fromHsv(fh, fs, fv - amount);
|
return QColor::fromHsv(fh, fs, fv - amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fv > bv && (255 - fv > static_cast<int>(amount))) {
|
if (fv > bv && (255 - fv > amount)) {
|
||||||
return QColor::fromHsv(fh, fs, fv + amount);
|
return QColor::fromHsv(fh, fs, fv + amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fv < bv && (255 - bv > static_cast<int>(amount))) {
|
if (fv < bv && (255 - bv > amount)) {
|
||||||
return QColor::fromHsv(fh, fs, bv + amount);
|
return QColor::fromHsv(fh, fs, bv + amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,18 +43,11 @@ class BlockAnalyzer : public AnalyzerBase {
|
|||||||
public:
|
public:
|
||||||
Q_INVOKABLE explicit BlockAnalyzer(QWidget*);
|
Q_INVOKABLE explicit BlockAnalyzer(QWidget*);
|
||||||
|
|
||||||
static const int kHeight;
|
|
||||||
static const int kWidth;
|
|
||||||
static const int kMinRows;
|
|
||||||
static const int kMinColumns;
|
|
||||||
static const int kMaxColumns;
|
|
||||||
static const int kFadeSize;
|
|
||||||
|
|
||||||
static const char *kName;
|
static const char *kName;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void transform(Scope&) override;
|
void transform(Scope&) override;
|
||||||
void analyze(QPainter &p, const Scope&, bool new_frame) override;
|
void analyze(QPainter &p, const Scope &s, const bool new_frame) override;
|
||||||
void resizeEvent(QResizeEvent*) override;
|
void resizeEvent(QResizeEvent*) override;
|
||||||
virtual void paletteChange(const QPalette&);
|
virtual void paletteChange(const QPalette&);
|
||||||
void framerateChanged() override;
|
void framerateChanged() override;
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ void BoomAnalyzer::resizeEvent(QResizeEvent *e) {
|
|||||||
bands_ = qMin(static_cast<int>(static_cast<double>(width() + 1) / (kColumnWidth + 1)) + 1, kMaxBandCount);
|
bands_ = qMin(static_cast<int>(static_cast<double>(width() + 1) / (kColumnWidth + 1)) + 1, kMaxBandCount);
|
||||||
scope_.resize(bands_);
|
scope_.resize(bands_);
|
||||||
|
|
||||||
F_ = static_cast<double>(HEIGHT) / (log10(256) * static_cast<double>(1.1) /*<- max. amplitude*/);
|
F_ = static_cast<double>(HEIGHT) / (log10(256) * 1.1 /*<- max. amplitude*/);
|
||||||
|
|
||||||
barPixmap_ = QPixmap(kColumnWidth - 2, HEIGHT);
|
barPixmap_ = QPixmap(kColumnWidth - 2, HEIGHT);
|
||||||
canvas_ = QPixmap(size());
|
canvas_ = QPixmap(size());
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class BoomAnalyzer : public AnalyzerBase {
|
|||||||
static const char *kName;
|
static const char *kName;
|
||||||
|
|
||||||
void transform(Scope &s) override;
|
void transform(Scope &s) override;
|
||||||
void analyze(QPainter &p, const Scope&, const bool new_frame) override;
|
void analyze(QPainter &p, const Scope &scope, const bool new_frame) override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void changeK_barHeight(int);
|
void changeK_barHeight(int);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QtMath>
|
#include <QtMath>
|
||||||
|
|
||||||
FHT::FHT(uint n) : num_((n < 3) ? 0 : 1 << n), exp2_((n < 3) ? static_cast<int>(-1) : static_cast<int>(n)) {
|
FHT::FHT(uint n) : num_((n < 3) ? 0 : 1 << n), exp2_((n < 3) ? -1 : static_cast<int>(n)) {
|
||||||
|
|
||||||
if (n > 3) {
|
if (n > 3) {
|
||||||
buf_vector_.resize(num_);
|
buf_vector_.resize(num_);
|
||||||
@@ -47,7 +47,7 @@ float *FHT::buf_() { return buf_vector_.data(); }
|
|||||||
float *FHT::tab_() { return tab_vector_.data(); }
|
float *FHT::tab_() { return tab_vector_.data(); }
|
||||||
int *FHT::log_() { return log_vector_.data(); }
|
int *FHT::log_() { return log_vector_.data(); }
|
||||||
|
|
||||||
void FHT::makeCasTable(void) {
|
void FHT::makeCasTable() {
|
||||||
|
|
||||||
float *costab = tab_();
|
float *costab = tab_();
|
||||||
float *sintab = tab_() + num_ / 2 + 1;
|
float *sintab = tab_() + num_ / 2 + 1;
|
||||||
|
|||||||
@@ -41,18 +41,21 @@
|
|||||||
#include "fht.h"
|
#include "fht.h"
|
||||||
#include "analyzerbase.h"
|
#include "analyzerbase.h"
|
||||||
|
|
||||||
|
const char *NyanCatAnalyzer::kName = "Nyanalyzer Cat";
|
||||||
|
const char *RainbowDashAnalyzer::kName = "Rainbow Dash";
|
||||||
|
|
||||||
|
RainbowAnalyzer::RainbowType RainbowAnalyzer::rainbowtype;
|
||||||
const int RainbowAnalyzer::kHeight[] = { 21, 33 };
|
const int RainbowAnalyzer::kHeight[] = { 21, 33 };
|
||||||
const int RainbowAnalyzer::kWidth[] = { 34, 53 };
|
const int RainbowAnalyzer::kWidth[] = { 34, 53 };
|
||||||
const int RainbowAnalyzer::kFrameCount[] = { 6, 16 };
|
const int RainbowAnalyzer::kFrameCount[] = { 6, 16 };
|
||||||
const int RainbowAnalyzer::kRainbowHeight[] = { 21, 16 };
|
|
||||||
const int RainbowAnalyzer::kRainbowOverlap[] = { 13, 15 };
|
|
||||||
const int RainbowAnalyzer::kSleepingHeight[] = { 24, 33 };
|
const int RainbowAnalyzer::kSleepingHeight[] = { 24, 33 };
|
||||||
|
|
||||||
const char *NyanCatAnalyzer::kName = "Nyanalyzer Cat";
|
namespace {
|
||||||
const char *RainbowDashAnalyzer::kName = "Rainbow Dash";
|
constexpr int kFrameIntervalMs = 150;
|
||||||
const float RainbowAnalyzer::kPixelScale = 0.02F;
|
constexpr int kRainbowHeight[] = { 21, 16 };
|
||||||
|
constexpr int kRainbowOverlap[] = { 13, 15 };
|
||||||
RainbowAnalyzer::RainbowType RainbowAnalyzer::rainbowtype;
|
constexpr float kPixelScale = 0.02F;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
|
RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
|
||||||
: AnalyzerBase(parent, 9),
|
: AnalyzerBase(parent, 9),
|
||||||
@@ -65,8 +68,8 @@ RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
|
|||||||
background_brush_(QColor(0x0f, 0x43, 0x73)) {
|
background_brush_(QColor(0x0f, 0x43, 0x73)) {
|
||||||
|
|
||||||
rainbowtype = rbtype;
|
rainbowtype = rbtype;
|
||||||
cat_dash_[0] = QPixmap(":/pictures/nyancat.png");
|
cat_dash_[0] = QPixmap(QStringLiteral(":/pictures/nyancat.png"));
|
||||||
cat_dash_[1] = QPixmap(":/pictures/rainbowdash.png");
|
cat_dash_[1] = QPixmap(QStringLiteral(":/pictures/rainbowdash.png"));
|
||||||
memset(history_, 0, sizeof(history_));
|
memset(history_, 0, sizeof(history_));
|
||||||
|
|
||||||
for (int i = 0; i < kRainbowBands; ++i) {
|
for (int i = 0; i < kRainbowBands; ++i) {
|
||||||
@@ -106,7 +109,7 @@ void RainbowAnalyzer::resizeEvent(QResizeEvent *e) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RainbowAnalyzer::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
void RainbowAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
|
||||||
|
|
||||||
// Discard the second half of the transform
|
// Discard the second half of the transform
|
||||||
const int scope_size = static_cast<int>(s.size() / 2);
|
const int scope_size = static_cast<int>(s.size() / 2);
|
||||||
|
|||||||
@@ -49,44 +49,37 @@ class RainbowAnalyzer : public AnalyzerBase {
|
|||||||
Dash = 1
|
Dash = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
RainbowAnalyzer(const RainbowType rbtype, QWidget *parent);
|
explicit RainbowAnalyzer(const RainbowType rbtype, QWidget *parent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void transform(Scope&) override;
|
void transform(Scope &s) override;
|
||||||
void analyze(QPainter &p, const Scope&, bool new_frame) override;
|
void analyze(QPainter &p, const Scope &s, const bool new_frame) override;
|
||||||
|
|
||||||
void timerEvent(QTimerEvent *e) override;
|
void timerEvent(QTimerEvent *e) override;
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static const int kRainbowBands = 6;
|
||||||
|
static const int kHistorySize = 128;
|
||||||
|
static RainbowType rainbowtype;
|
||||||
static const int kHeight[];
|
static const int kHeight[];
|
||||||
static const int kWidth[];
|
static const int kWidth[];
|
||||||
static const int kFrameCount[];
|
static const int kFrameCount[];
|
||||||
static const int kRainbowHeight[];
|
|
||||||
static const int kRainbowOverlap[];
|
|
||||||
static const int kSleepingHeight[];
|
static const int kSleepingHeight[];
|
||||||
|
|
||||||
static const int kHistorySize = 128;
|
inline QRect SourceRect(const RainbowType _rainbowtype) const {
|
||||||
static const int kRainbowBands = 6;
|
|
||||||
static const float kPixelScale;
|
|
||||||
|
|
||||||
static const int kFrameIntervalMs = 150;
|
|
||||||
|
|
||||||
static RainbowType rainbowtype;
|
|
||||||
|
|
||||||
inline QRect SourceRect(RainbowType _rainbowtype) const {
|
|
||||||
return QRect(0, kHeight[_rainbowtype] * frame_, kWidth[_rainbowtype], kHeight[_rainbowtype]);
|
return QRect(0, kHeight[_rainbowtype] * frame_, kWidth[_rainbowtype], kHeight[_rainbowtype]);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QRect SleepingSourceRect(RainbowType _rainbowtype) const {
|
inline QRect SleepingSourceRect(const RainbowType _rainbowtype) const {
|
||||||
return QRect(0, kHeight[_rainbowtype] * kFrameCount[_rainbowtype], kWidth[_rainbowtype], kSleepingHeight[_rainbowtype]);
|
return QRect(0, kHeight[_rainbowtype] * kFrameCount[_rainbowtype], kWidth[_rainbowtype], kSleepingHeight[_rainbowtype]);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QRect DestRect(RainbowType _rainbowtype) const {
|
inline QRect DestRect(const RainbowType _rainbowtype) const {
|
||||||
return QRect(width() - kWidth[_rainbowtype], (height() - kHeight[_rainbowtype]) / 2, kWidth[_rainbowtype], kHeight[_rainbowtype]);
|
return QRect(width() - kWidth[_rainbowtype], (height() - kHeight[_rainbowtype]) / 2, kWidth[_rainbowtype], kHeight[_rainbowtype]);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QRect SleepingDestRect(RainbowType _rainbowtype) const {
|
inline QRect SleepingDestRect(const RainbowType _rainbowtype) const {
|
||||||
return QRect(width() - kWidth[_rainbowtype], (height() - kSleepingHeight[_rainbowtype]) / 2, kWidth[_rainbowtype], kSleepingHeight[_rainbowtype]);
|
return QRect(width() - kWidth[_rainbowtype], (height() - kSleepingHeight[_rainbowtype]) / 2, kWidth[_rainbowtype], kSleepingHeight[_rainbowtype]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,14 @@
|
|||||||
|
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
|
|
||||||
#include "sonogram.h"
|
#include "sonogramanalyzer.h"
|
||||||
|
|
||||||
const char *Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
const char *SonogramAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
||||||
|
|
||||||
Sonogram::Sonogram(QWidget *parent)
|
SonogramAnalyzer::SonogramAnalyzer(QWidget *parent)
|
||||||
: AnalyzerBase(parent, 9) {}
|
: AnalyzerBase(parent, 9) {}
|
||||||
|
|
||||||
void Sonogram::resizeEvent(QResizeEvent *e) {
|
void SonogramAnalyzer::resizeEvent(QResizeEvent *e) {
|
||||||
|
|
||||||
Q_UNUSED(e)
|
Q_UNUSED(e)
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ void Sonogram::resizeEvent(QResizeEvent *e) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sonogram::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
void SonogramAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
|
||||||
|
|
||||||
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
|
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
|
||||||
p.drawPixmap(0, 0, canvas_);
|
p.drawPixmap(0, 0, canvas_);
|
||||||
@@ -81,7 +81,7 @@ void Sonogram::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sonogram::transform(Scope &scope) {
|
void SonogramAnalyzer::transform(Scope &scope) {
|
||||||
|
|
||||||
fht_->power2(scope.data());
|
fht_->power2(scope.data());
|
||||||
fht_->scale(scope.data(), 1.0 / 256);
|
fht_->scale(scope.data(), 1.0 / 256);
|
||||||
@@ -89,6 +89,6 @@ void Sonogram::transform(Scope &scope) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sonogram::demo(QPainter &p) {
|
void SonogramAnalyzer::demo(QPainter &p) {
|
||||||
analyze(p, Scope(fht_->size(), 0), new_frame_);
|
analyze(p, Scope(fht_->size(), 0), new_frame_);
|
||||||
}
|
}
|
||||||
@@ -21,24 +21,25 @@
|
|||||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SONOGRAM_H
|
#ifndef SONOGRAMANALYZER_H
|
||||||
#define SONOGRAM_H
|
#define SONOGRAMANALYZER_H
|
||||||
|
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#include "analyzerbase.h"
|
#include "analyzerbase.h"
|
||||||
|
|
||||||
class Sonogram : public AnalyzerBase {
|
class SonogramAnalyzer : public AnalyzerBase {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Q_INVOKABLE explicit Sonogram(QWidget *parent);
|
Q_INVOKABLE explicit SonogramAnalyzer(QWidget *parent);
|
||||||
|
|
||||||
static const char *kName;
|
static const char *kName;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
void analyze(QPainter &p, const Scope &s, bool new_frame) override;
|
void analyze(QPainter &p, const Scope &s, const bool new_frame) override;
|
||||||
void transform(Scope &scope) override;
|
void transform(Scope &scope) override;
|
||||||
void demo(QPainter &p) override;
|
void demo(QPainter &p) override;
|
||||||
|
|
||||||
@@ -46,4 +47,4 @@ class Sonogram : public AnalyzerBase {
|
|||||||
QPixmap canvas_;
|
QPixmap canvas_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SONOGRAM_H
|
#endif // SONOGRAMANALYZER_H
|
||||||
100
src/analyzer/turbineanalyzer.cpp
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
Strawberry Music Player
|
||||||
|
This file was part of Clementine.
|
||||||
|
Copyright 2003, Stanislav Karchebny <berkus@users.sf.net>
|
||||||
|
Copyright 2003, Max Howell <max.howell@methylblue.com>
|
||||||
|
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||||
|
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||||
|
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||||
|
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||||
|
|
||||||
|
Clementine 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.
|
||||||
|
|
||||||
|
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
#include "turbineanalyzer.h"
|
||||||
|
#include "engine/enginebase.h"
|
||||||
|
|
||||||
|
const char *TurbineAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Turbine");
|
||||||
|
|
||||||
|
TurbineAnalyzer::TurbineAnalyzer(QWidget *parent) : BoomAnalyzer(parent) {}
|
||||||
|
|
||||||
|
void TurbineAnalyzer::analyze(QPainter &p, const Scope &scope, const bool new_frame) {
|
||||||
|
|
||||||
|
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint hd2 = height() / 2;
|
||||||
|
const uint kMaxHeight = hd2 - 1;
|
||||||
|
|
||||||
|
QPainter canvas_painter(&canvas_);
|
||||||
|
canvas_.fill(palette().color(QPalette::Window));
|
||||||
|
|
||||||
|
AnalyzerBase::interpolate(scope, scope_);
|
||||||
|
|
||||||
|
for (uint i = 0, x = 0, y = 0; i < static_cast<uint>(bands_); ++i, x += kColumnWidth + 1) {
|
||||||
|
float h = static_cast<float>(std::min(log10(scope_[i] * 256.0) * F_ * 0.5, kMaxHeight * 1.0));
|
||||||
|
|
||||||
|
if (h > bar_height_[i]) {
|
||||||
|
bar_height_[i] = h;
|
||||||
|
if (h > peak_height_[i]) {
|
||||||
|
peak_height_[i] = h;
|
||||||
|
peak_speed_[i] = 0.01;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
goto peak_handling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (bar_height_[i] > 0.0) {
|
||||||
|
bar_height_[i] -= K_barHeight_; // 1.4
|
||||||
|
if (bar_height_[i] < 0.0) bar_height_[i] = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
peak_handling:
|
||||||
|
if (peak_height_[i] > 0.0) {
|
||||||
|
peak_height_[i] -= peak_speed_[i];
|
||||||
|
peak_speed_[i] *= F_peakSpeed_; // 1.12
|
||||||
|
peak_height_[i] = std::max(0.0, std::max(bar_height_[i], peak_height_[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
y = hd2 - static_cast<uint>(bar_height_[i]);
|
||||||
|
canvas_painter.drawPixmap(static_cast<int>(x + 1), static_cast<int>(y), barPixmap_, 0, static_cast<int>(y), -1, -1);
|
||||||
|
canvas_painter.drawPixmap(static_cast<int>(x + 1), static_cast<int>(hd2), barPixmap_, 0, static_cast<int>(bar_height_[i]), -1, -1);
|
||||||
|
|
||||||
|
canvas_painter.setPen(fg_);
|
||||||
|
if (bar_height_[i] > 0) {
|
||||||
|
canvas_painter.drawRect(static_cast<int>(x), static_cast<int>(y), kColumnWidth - 1, static_cast<int>(bar_height_[i]) * 2 - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint x2 = x + kColumnWidth - 1;
|
||||||
|
canvas_painter.setPen(palette().color(QPalette::Midlight));
|
||||||
|
y = hd2 - static_cast<uint>(peak_height_[i]);
|
||||||
|
canvas_painter.drawLine(static_cast<int>(x), static_cast<int>(y), static_cast<int>(x2), static_cast<int>(y));
|
||||||
|
y = hd2 + static_cast<uint>(peak_height_[i]);
|
||||||
|
canvas_painter.drawLine(static_cast<int>(x), static_cast<int>(y), static_cast<int>(x2), static_cast<int>(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
|
||||||
|
}
|
||||||
41
src/analyzer/turbineanalyzer.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Strawberry Music Player
|
||||||
|
This file was part of Clementine.
|
||||||
|
Copyright 2003, Stanislav Karchebny <berkus@users.sf.net>
|
||||||
|
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||||
|
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||||
|
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||||
|
|
||||||
|
Clementine 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.
|
||||||
|
|
||||||
|
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TURBINEANALYZER_H
|
||||||
|
#define TURBINEANALYZER_H
|
||||||
|
|
||||||
|
#include "boomanalyzer.h"
|
||||||
|
|
||||||
|
class QPainter;
|
||||||
|
|
||||||
|
class TurbineAnalyzer : public BoomAnalyzer {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE explicit TurbineAnalyzer(QWidget *parent);
|
||||||
|
|
||||||
|
void analyze(QPainter &p, const Scope &scope, const bool new_frame);
|
||||||
|
|
||||||
|
static const char *kName;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TURBINEANALYZER_H
|
||||||
92
src/analyzer/waverubberanalyzer.cpp
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Strawberry Music Player
|
||||||
|
Copyright 2024, Gustavo L Conte <suporte@gu.pro.br>
|
||||||
|
|
||||||
|
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 <QPainter>
|
||||||
|
#include <QResizeEvent>
|
||||||
|
#include "engine/enginebase.h"
|
||||||
|
#include "waverubberanalyzer.h"
|
||||||
|
|
||||||
|
const char *WaveRubberAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "WaveRubber");
|
||||||
|
|
||||||
|
WaveRubberAnalyzer::WaveRubberAnalyzer(QWidget *parent)
|
||||||
|
: AnalyzerBase(parent, 9) {}
|
||||||
|
|
||||||
|
void WaveRubberAnalyzer::resizeEvent(QResizeEvent *e) {
|
||||||
|
|
||||||
|
Q_UNUSED(e)
|
||||||
|
|
||||||
|
canvas_ = QPixmap(size());
|
||||||
|
canvas_.fill(palette().color(QPalette::AlternateBase));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveRubberAnalyzer::analyze(QPainter &p, const Scope &s, const bool new_frame) {
|
||||||
|
|
||||||
|
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the canvas
|
||||||
|
canvas_ = QPixmap(size());
|
||||||
|
canvas_.fill(palette().color(QPalette::Window));
|
||||||
|
|
||||||
|
QPainter canvas_painter(&canvas_);
|
||||||
|
|
||||||
|
// Set the pen color to the QT palette highlight color
|
||||||
|
canvas_painter.setPen(palette().color(QPalette::Highlight));
|
||||||
|
// Get pointer to amplitude data
|
||||||
|
const float *amplitude_data = s.data();
|
||||||
|
|
||||||
|
const int mid_y = height() / 4;
|
||||||
|
const int num_samples = static_cast<int>(s.size());
|
||||||
|
|
||||||
|
const float x_scale = static_cast<float>(width()) / static_cast<float>(num_samples);
|
||||||
|
float prev_y = static_cast<float>(mid_y);
|
||||||
|
|
||||||
|
// Draw the waveform
|
||||||
|
for (int i = 0; i < num_samples; ++i) {
|
||||||
|
|
||||||
|
// Normalize amplitude to 0-1 range
|
||||||
|
const float color_factor = amplitude_data[i] / 2.0F + 0.5F;
|
||||||
|
const int rgb_value = static_cast<int>(255 - color_factor * 255);
|
||||||
|
QColor highlight_color = palette().color(QPalette::Highlight);
|
||||||
|
// Blend blue and green with highlight color from QT palette based on amplitude
|
||||||
|
QColor blended_color = QColor(rgb_value, highlight_color.green(), highlight_color.blue());
|
||||||
|
canvas_painter.setPen(blended_color);
|
||||||
|
|
||||||
|
const int x = static_cast<int>(static_cast<float>(i) * x_scale);
|
||||||
|
const int y = static_cast<int>(static_cast<float>(mid_y) - (s[i] * static_cast<float>(mid_y)));
|
||||||
|
|
||||||
|
canvas_painter.drawLine(x, static_cast<int>(prev_y + static_cast<float>(mid_y)), static_cast<int>(static_cast<float>(x) + x_scale), static_cast<int>(static_cast<float>(y + mid_y))); // Draw
|
||||||
|
prev_y = static_cast<float>(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_painter.end();
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveRubberAnalyzer::transform(Scope &s) {
|
||||||
|
// No need transformation for waveform analyzer
|
||||||
|
Q_UNUSED(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaveRubberAnalyzer::demo(QPainter &p) {
|
||||||
|
analyze(p, Scope(fht_->size(), 0), new_frame_);
|
||||||
|
}
|
||||||
41
src/analyzer/waverubberanalyzer.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Strawberry Music Player
|
||||||
|
Copyright 2024, Gustavo L Conte <suporte@gu.pro.br>
|
||||||
|
|
||||||
|
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 <QPixmap>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
#include "analyzerbase.h"
|
||||||
|
|
||||||
|
class WaveRubberAnalyzer : public AnalyzerBase {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE explicit WaveRubberAnalyzer(QWidget *parent);
|
||||||
|
|
||||||
|
static const char *kName;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
void analyze(QPainter &p, const Scope &s, const bool new_frame) override;
|
||||||
|
void transform(Scope &scope) override;
|
||||||
|
void demo(QPainter &p) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPixmap canvas_;
|
||||||
|
};
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "core/thread.h"
|
#include "core/thread.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "utilities/threadutils.h"
|
#include "utilities/threadutils.h"
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
#include "collectionwatcher.h"
|
#include "collectionwatcher.h"
|
||||||
@@ -48,7 +49,6 @@
|
|||||||
using std::make_shared;
|
using std::make_shared;
|
||||||
|
|
||||||
const char *SCollection::kSongsTable = "songs";
|
const char *SCollection::kSongsTable = "songs";
|
||||||
const char *SCollection::kFtsTable = "songs_fts";
|
|
||||||
const char *SCollection::kDirsTable = "directories";
|
const char *SCollection::kDirsTable = "directories";
|
||||||
const char *SCollection::kSubdirsTable = "subdirectories";
|
const char *SCollection::kSubdirsTable = "subdirectories";
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||||||
backend()->moveToThread(app->database()->thread());
|
backend()->moveToThread(app->database()->thread());
|
||||||
qLog(Debug) << &*backend_ << "moved to thread" << app->database()->thread();
|
qLog(Debug) << &*backend_ << "moved to thread" << app->database()->thread();
|
||||||
|
|
||||||
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable);
|
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, QLatin1String(kSongsTable), QLatin1String(kDirsTable), QLatin1String(kSubdirsTable));
|
||||||
|
|
||||||
model_ = new CollectionModel(backend_, app_, this);
|
model_ = new CollectionModel(backend_, app_, this);
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ void SCollection::Init() {
|
|||||||
watcher_->set_task_manager(app_->task_manager());
|
watcher_->set_task_manager(app_->task_manager());
|
||||||
|
|
||||||
QObject::connect(&*backend_, &CollectionBackend::Error, this, &SCollection::Error);
|
QObject::connect(&*backend_, &CollectionBackend::Error, this, &SCollection::Error);
|
||||||
QObject::connect(&*backend_, &CollectionBackend::DirectoryDiscovered, watcher_, &CollectionWatcher::AddDirectory);
|
QObject::connect(&*backend_, &CollectionBackend::DirectoryAdded, watcher_, &CollectionWatcher::AddDirectory);
|
||||||
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
|
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
|
||||||
QObject::connect(&*backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged);
|
QObject::connect(&*backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged);
|
||||||
QObject::connect(&*backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged);
|
QObject::connect(&*backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged);
|
||||||
@@ -179,7 +179,7 @@ void SCollection::ReloadSettings() {
|
|||||||
watcher_->ReloadSettingsAsync();
|
watcher_->ReloadSettingsAsync();
|
||||||
model_->ReloadSettings();
|
model_->ReloadSettings();
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
||||||
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
||||||
@@ -206,8 +206,8 @@ void SCollection::SyncPlaycountAndRatingToFiles() {
|
|||||||
const qint64 nb_songs = songs.size();
|
const qint64 nb_songs = songs.size();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
TagReaderClient::Instance()->UpdateSongPlaycountBlocking(song);
|
(void)TagReaderClient::Instance()->SaveSongPlaycountBlocking(song.url().toLocalFile(), song.playcount());
|
||||||
TagReaderClient::Instance()->UpdateSongRatingBlocking(song);
|
(void)TagReaderClient::Instance()->SaveSongRatingBlocking(song.url().toLocalFile(), song.rating());
|
||||||
app_->task_manager()->SetTaskProgress(task_id, ++i, nb_songs);
|
app_->task_manager()->SetTaskProgress(task_id, ++i, nb_songs);
|
||||||
}
|
}
|
||||||
app_->task_manager()->SetTaskFinished(task_id);
|
app_->task_manager()->SetTaskFinished(task_id);
|
||||||
@@ -217,7 +217,7 @@ void SCollection::SyncPlaycountAndRatingToFiles() {
|
|||||||
void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
|
void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
|
||||||
|
|
||||||
if (save_tags || save_playcounts_to_files_) {
|
if (save_tags || save_playcounts_to_files_) {
|
||||||
app_->tag_reader_client()->UpdateSongsPlaycount(songs);
|
app_->tag_reader_client()->SaveSongsPlaycount(songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_t
|
|||||||
void SCollection::SongsRatingChanged(const SongList &songs, const bool save_tags) {
|
void SCollection::SongsRatingChanged(const SongList &songs, const bool save_tags) {
|
||||||
|
|
||||||
if (save_tags || save_ratings_to_files_) {
|
if (save_tags || save_ratings_to_files_) {
|
||||||
app_->tag_reader_client()->UpdateSongsRating(songs);
|
app_->tag_reader_client()->SaveSongsRating(songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -80,12 +80,13 @@ class CollectionBackendInterface : public QObject {
|
|||||||
using AlbumList = QList<Album>;
|
using AlbumList = QList<Album>;
|
||||||
|
|
||||||
virtual QString songs_table() const = 0;
|
virtual QString songs_table() const = 0;
|
||||||
virtual QString fts_table() const = 0;
|
|
||||||
|
|
||||||
virtual Song::Source source() const = 0;
|
virtual Song::Source source() const = 0;
|
||||||
|
|
||||||
virtual SharedPtr<Database> db() const = 0;
|
virtual SharedPtr<Database> db() const = 0;
|
||||||
|
|
||||||
|
virtual void GetAllSongsAsync(const int id = 0) = 0;
|
||||||
|
|
||||||
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||||
virtual void LoadDirectoriesAsync() = 0;
|
virtual void LoadDirectoriesAsync() = 0;
|
||||||
|
|
||||||
@@ -130,9 +131,10 @@ class CollectionBackendInterface : public QObject {
|
|||||||
// Returns a section of a song with the given filename and beginning. If the section is not present in collection, returns invalid song.
|
// Returns a section of a song with the given filename and beginning. If the section is not present in collection, returns invalid song.
|
||||||
// Using default beginning value is suitable when searching for single-section songs.
|
// Using default beginning value is suitable when searching for single-section songs.
|
||||||
virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0;
|
virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0;
|
||||||
|
virtual Song GetSongByUrlAndTrack(const QUrl &url, const int track) = 0;
|
||||||
|
|
||||||
virtual void AddDirectory(const QString &path) = 0;
|
virtual void AddDirectoryAsync(const QString &path) = 0;
|
||||||
virtual void RemoveDirectory(const CollectionDirectory &dir) = 0;
|
virtual void RemoveDirectoryAsync(const CollectionDirectory &dir) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CollectionBackend : public CollectionBackendInterface {
|
class CollectionBackend : public CollectionBackendInterface {
|
||||||
@@ -144,7 +146,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
|
|
||||||
~CollectionBackend();
|
~CollectionBackend();
|
||||||
|
|
||||||
void Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString());
|
void Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString());
|
||||||
|
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
void ExitAsync();
|
void ExitAsync();
|
||||||
@@ -156,10 +159,11 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
SharedPtr<Database> db() const override { return db_; }
|
SharedPtr<Database> db() const override { return db_; }
|
||||||
|
|
||||||
QString songs_table() const override { return songs_table_; }
|
QString songs_table() const override { return songs_table_; }
|
||||||
QString fts_table() const override { return fts_table_; }
|
|
||||||
QString dirs_table() const { return dirs_table_; }
|
QString dirs_table() const { return dirs_table_; }
|
||||||
QString subdirs_table() const { return subdirs_table_; }
|
QString subdirs_table() const { return subdirs_table_; }
|
||||||
|
|
||||||
|
void GetAllSongsAsync(const int id = 0) override;
|
||||||
|
|
||||||
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||||
void LoadDirectoriesAsync() override;
|
void LoadDirectoriesAsync() override;
|
||||||
|
|
||||||
@@ -203,9 +207,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
|
|
||||||
SongList GetSongsByUrl(const QUrl &url, const bool unavailable = false) override;
|
SongList GetSongsByUrl(const QUrl &url, const bool unavailable = false) override;
|
||||||
Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override;
|
Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override;
|
||||||
|
Song GetSongByUrlAndTrack(const QUrl &url, const int track) override;
|
||||||
|
|
||||||
void AddDirectory(const QString &path) override;
|
void AddDirectoryAsync(const QString &path) override;
|
||||||
void RemoveDirectory(const CollectionDirectory &dir) override;
|
void RemoveDirectoryAsync(const CollectionDirectory &dir) override;
|
||||||
|
|
||||||
bool ExecCollectionQuery(CollectionQuery *query, SongList &songs);
|
bool ExecCollectionQuery(CollectionQuery *query, SongList &songs);
|
||||||
bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs);
|
bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs);
|
||||||
@@ -233,10 +238,13 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void Exit();
|
void Exit();
|
||||||
|
void GetAllSongs(const int id);
|
||||||
void LoadDirectories();
|
void LoadDirectories();
|
||||||
void UpdateTotalSongCount();
|
void UpdateTotalSongCount();
|
||||||
void UpdateTotalArtistCount();
|
void UpdateTotalArtistCount();
|
||||||
void UpdateTotalAlbumCount();
|
void UpdateTotalAlbumCount();
|
||||||
|
void AddDirectory(const QString &path);
|
||||||
|
void RemoveDirectory(const CollectionDirectory &dir);
|
||||||
void AddOrUpdateSongs(const SongList &songs);
|
void AddOrUpdateSongs(const SongList &songs);
|
||||||
void UpdateSongsBySongID(const SongMap &new_songs);
|
void UpdateSongsBySongID(const SongMap &new_songs);
|
||||||
void UpdateMTimesOnly(const SongList &songs);
|
void UpdateMTimesOnly(const SongList &songs);
|
||||||
@@ -248,7 +256,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual);
|
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual);
|
||||||
void UnsetAlbumArt(const QString &effective_albumartist, const QString &album);
|
void UnsetAlbumArt(const QString &effective_albumartist, const QString &album);
|
||||||
void ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset);
|
void ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset);
|
||||||
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
void ForceCompilation(const QString &album, const QStringList &artists, const bool on);
|
||||||
void IncrementPlayCount(const int id);
|
void IncrementPlayCount(const int id);
|
||||||
void IncrementSkipCount(const int id, const float progress);
|
void IncrementSkipCount(const int id, const float progress);
|
||||||
void ResetPlayStatistics(const int id, const bool save_tags = false);
|
void ResetPlayStatistics(const int id, const bool save_tags = false);
|
||||||
@@ -268,11 +276,13 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void DirectoryDiscovered(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
void DirectoryAdded(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
||||||
void DirectoryDeleted(const CollectionDirectory &dir);
|
void DirectoryDeleted(const CollectionDirectory &dir);
|
||||||
|
|
||||||
void SongsDiscovered(const SongList &songs);
|
void GotSongs(const SongList &songs, const int id);
|
||||||
|
void SongsAdded(const SongList &songs);
|
||||||
void SongsDeleted(const SongList &songs);
|
void SongsDeleted(const SongList &songs);
|
||||||
|
void SongsChanged(const SongList &songs);
|
||||||
void SongsStatisticsChanged(const SongList &songs, const bool save_tags = false);
|
void SongsStatisticsChanged(const SongList &songs, const bool save_tags = false);
|
||||||
|
|
||||||
void DatabaseReset();
|
void DatabaseReset();
|
||||||
@@ -297,7 +307,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
int has_not_compilation_detected;
|
int has_not_compilation_detected;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool UpdateCompilations(const QSqlDatabase &db, SongList &deleted_songs, SongList &added_songs, const QUrl &url, const bool compilation_detected);
|
bool UpdateCompilations(const QSqlDatabase &db, SongList &changed_songs, const QUrl &url, const bool compilation_detected);
|
||||||
AlbumList GetAlbums(const QString &artist, const QString &album_artist, const bool compilation_required = false, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
AlbumList GetAlbums(const QString &artist, const QString &album_artist, const bool compilation_required = false, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||||
AlbumList GetAlbums(const QString &artist, const bool compilation_required, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
AlbumList GetAlbums(const QString &artist, const bool compilation_required, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||||
CollectionSubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
CollectionSubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
||||||
@@ -315,7 +325,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
QString songs_table_;
|
QString songs_table_;
|
||||||
QString dirs_table_;
|
QString dirs_table_;
|
||||||
QString subdirs_table_;
|
QString subdirs_table_;
|
||||||
QString fts_table_;
|
|
||||||
QThread *original_thread_;
|
QThread *original_thread_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -41,15 +42,18 @@ using std::make_shared;
|
|||||||
|
|
||||||
CollectionDirectoryModel::CollectionDirectoryModel(SharedPtr<CollectionBackend> backend, QObject *parent)
|
CollectionDirectoryModel::CollectionDirectoryModel(SharedPtr<CollectionBackend> backend, QObject *parent)
|
||||||
: QStandardItemModel(parent),
|
: QStandardItemModel(parent),
|
||||||
dir_icon_(IconLoader::Load("document-open-folder")),
|
dir_icon_(IconLoader::Load(QStringLiteral("document-open-folder"))),
|
||||||
backend_(backend) {
|
backend_(backend) {
|
||||||
|
|
||||||
QObject::connect(&*backend_, &CollectionBackend::DirectoryDiscovered, this, &CollectionDirectoryModel::DirectoryDiscovered);
|
QObject::connect(&*backend_, &CollectionBackend::DirectoryAdded, this, &CollectionDirectoryModel::AddDirectory);
|
||||||
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, this, &CollectionDirectoryModel::DirectoryDeleted);
|
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, this, &CollectionDirectoryModel::RemoveDirectory);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionDirectoryModel::DirectoryDiscovered(const CollectionDirectory &dir) {
|
void CollectionDirectoryModel::AddDirectory(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
|
directories_.insert(dir.id, dir);
|
||||||
|
paths_.append(dir.path);
|
||||||
|
|
||||||
QStandardItem *item = new QStandardItem(dir.path);
|
QStandardItem *item = new QStandardItem(dir.path);
|
||||||
item->setData(dir.id, kIdRole);
|
item->setData(dir.id, kIdRole);
|
||||||
@@ -59,7 +63,10 @@ void CollectionDirectoryModel::DirectoryDiscovered(const CollectionDirectory &di
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionDirectoryModel::DirectoryDeleted(const CollectionDirectory &dir) {
|
void CollectionDirectoryModel::RemoveDirectory(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
|
directories_.remove(dir.id);
|
||||||
|
paths_.removeAll(dir.path);
|
||||||
|
|
||||||
for (int i = 0; i < rowCount(); ++i) {
|
for (int i = 0; i < rowCount(); ++i) {
|
||||||
if (item(i, 0)->data(kIdRole).toInt() == dir.id) {
|
if (item(i, 0)->data(kIdRole).toInt() == dir.id) {
|
||||||
@@ -71,26 +78,6 @@ void CollectionDirectoryModel::DirectoryDeleted(const CollectionDirectory &dir)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionDirectoryModel::AddDirectory(const QString &path) {
|
|
||||||
|
|
||||||
if (!backend_) return;
|
|
||||||
|
|
||||||
backend_->AddDirectory(path);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionDirectoryModel::RemoveDirectory(const QModelIndex &idx) {
|
|
||||||
|
|
||||||
if (!backend_ || !idx.isValid()) return;
|
|
||||||
|
|
||||||
CollectionDirectory dir;
|
|
||||||
dir.path = idx.data().toString();
|
|
||||||
dir.id = idx.data(kIdRole).toInt();
|
|
||||||
|
|
||||||
backend_->RemoveDirectory(dir);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant CollectionDirectoryModel::data(const QModelIndex &idx, int role) const {
|
QVariant CollectionDirectoryModel::data(const QModelIndex &idx, int role) const {
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,15 +27,17 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
|
||||||
#include "core/shared_ptr.h"
|
#include "core/shared_ptr.h"
|
||||||
|
#include "collectiondirectory.h"
|
||||||
|
|
||||||
class QModelIndex;
|
class QModelIndex;
|
||||||
|
|
||||||
struct CollectionDirectory;
|
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class MusicStorage;
|
class MusicStorage;
|
||||||
|
|
||||||
@@ -44,22 +47,24 @@ class CollectionDirectoryModel : public QStandardItemModel {
|
|||||||
public:
|
public:
|
||||||
explicit CollectionDirectoryModel(SharedPtr<CollectionBackend> collection_backend, QObject *parent = nullptr);
|
explicit CollectionDirectoryModel(SharedPtr<CollectionBackend> collection_backend, QObject *parent = nullptr);
|
||||||
|
|
||||||
// To be called by GUIs
|
|
||||||
void AddDirectory(const QString &path);
|
|
||||||
void RemoveDirectory(const QModelIndex &idx);
|
|
||||||
|
|
||||||
QVariant data(const QModelIndex &idx, int role) const override;
|
QVariant data(const QModelIndex &idx, int role) const override;
|
||||||
|
|
||||||
|
SharedPtr<CollectionBackend> backend() const { return backend_; }
|
||||||
|
|
||||||
|
QMap<int, CollectionDirectory> directories() const { return directories_; }
|
||||||
|
QStringList paths() const { return paths_; }
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// To be called by the backend
|
void AddDirectory(const CollectionDirectory &directory);
|
||||||
void DirectoryDiscovered(const CollectionDirectory &directories);
|
void RemoveDirectory(const CollectionDirectory &directory);
|
||||||
void DirectoryDeleted(const CollectionDirectory &directories);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int kIdRole = Qt::UserRole + 1;
|
static const int kIdRole = Qt::UserRole + 1;
|
||||||
|
|
||||||
QIcon dir_icon_;
|
QIcon dir_icon_;
|
||||||
SharedPtr<CollectionBackend> backend_;
|
SharedPtr<CollectionBackend> backend_;
|
||||||
|
QMap<int, CollectionDirectory> directories_;
|
||||||
|
QStringList paths_;
|
||||||
QList<SharedPtr<MusicStorage>> storage_;
|
QList<SharedPtr<MusicStorage>> storage_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
136
src/collection/collectionfilter.cpp
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* 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
|
||||||
|
* 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 "config.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <QSet>
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
#include "filterparser/filterparser.h"
|
||||||
|
#include "filterparser/filtertree.h"
|
||||||
|
#include "playlist/songmimedata.h"
|
||||||
|
#include "playlist/playlistmanager.h"
|
||||||
|
#include "collectionbackend.h"
|
||||||
|
#include "collectionfilter.h"
|
||||||
|
#include "collectionmodel.h"
|
||||||
|
#include "collectionitem.h"
|
||||||
|
|
||||||
|
CollectionFilter::CollectionFilter(QObject *parent) : QSortFilterProxyModel(parent), query_hash_(0) {
|
||||||
|
|
||||||
|
setSortLocaleAware(true);
|
||||||
|
setDynamicSortFilter(true);
|
||||||
|
setRecursiveFilteringEnabled(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollectionFilter::filterAcceptsRow(const int source_row, const QModelIndex &source_parent) const {
|
||||||
|
|
||||||
|
CollectionModel *model = qobject_cast<CollectionModel*>(sourceModel());
|
||||||
|
if (!model) return false;
|
||||||
|
const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
|
||||||
|
if (!idx.isValid()) return false;
|
||||||
|
CollectionItem *item = model->IndexToItem(idx);
|
||||||
|
if (!item) return false;
|
||||||
|
|
||||||
|
if (filter_string_.isEmpty()) return true;
|
||||||
|
|
||||||
|
if (item->type != CollectionItem::Type::Song) {
|
||||||
|
return item->type == CollectionItem::Type::LoadingIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
const size_t hash = qHash(filter_string_);
|
||||||
|
#else
|
||||||
|
const uint hash = qHash(filter_string_);
|
||||||
|
#endif
|
||||||
|
if (hash != query_hash_) {
|
||||||
|
FilterParser p(filter_string_);
|
||||||
|
filter_tree_.reset(p.parse());
|
||||||
|
query_hash_ = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item->metadata.is_valid() && filter_tree_->accept(item->metadata);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionFilter::SetFilterString(const QString &filter_string) {
|
||||||
|
|
||||||
|
filter_string_ = filter_string;
|
||||||
|
setFilterFixedString(filter_string);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QMimeData *CollectionFilter::mimeData(const QModelIndexList &indexes) const {
|
||||||
|
|
||||||
|
if (indexes.isEmpty()) return nullptr;
|
||||||
|
|
||||||
|
CollectionModel *collection_model = qobject_cast<CollectionModel*>(sourceModel());
|
||||||
|
SongMimeData *data = new SongMimeData;
|
||||||
|
data->backend = collection_model->backend();
|
||||||
|
|
||||||
|
QSet<int> song_ids;
|
||||||
|
QList<QUrl> urls;
|
||||||
|
for (const QModelIndex &idx : indexes) {
|
||||||
|
const QModelIndex source_index = mapToSource(idx);
|
||||||
|
CollectionItem *item = collection_model->IndexToItem(source_index);
|
||||||
|
GetChildSongs(item, song_ids, urls, data->songs);
|
||||||
|
}
|
||||||
|
|
||||||
|
data->setUrls(urls);
|
||||||
|
data->name_for_new_playlist_ = PlaylistManager::GetNameForNewPlaylist(data->songs);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionFilter::GetChildSongs(CollectionItem *item, QSet<int> &song_ids, QList<QUrl> &urls, SongList &songs) const {
|
||||||
|
|
||||||
|
CollectionModel *collection_model = qobject_cast<CollectionModel*>(sourceModel());
|
||||||
|
|
||||||
|
switch (item->type) {
|
||||||
|
case CollectionItem::Type::Container:{
|
||||||
|
QList<CollectionItem*> children = item->children;
|
||||||
|
std::sort(children.begin(), children.end(), std::bind(&CollectionModel::CompareItems, collection_model, std::placeholders::_1, std::placeholders::_2));
|
||||||
|
for (CollectionItem *child : children) {
|
||||||
|
GetChildSongs(child, song_ids, urls, songs);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CollectionItem::Type::Song:{
|
||||||
|
const QModelIndex idx = collection_model->ItemToIndex(item);
|
||||||
|
if (filterAcceptsRow(idx.row(), idx.parent())) {
|
||||||
|
urls << item->metadata.url();
|
||||||
|
if (!song_ids.contains(item->metadata.id())) {
|
||||||
|
song_ids.insert(item->metadata.id());
|
||||||
|
songs << item->metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
62
src/collection/collectionfilter.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* 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
|
||||||
|
* 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 COLLECTIONFILTER_H
|
||||||
|
#define COLLECTIONFILTER_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QList>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
#include "filterparser/filtertree.h"
|
||||||
|
|
||||||
|
class CollectionItem;
|
||||||
|
|
||||||
|
class CollectionFilter : public QSortFilterProxyModel {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CollectionFilter(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void SetFilterString(const QString &filter_string);
|
||||||
|
QString filter_string() const { return filter_string_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool filterAcceptsRow(const int source_row, const QModelIndex &source_parent) const override;
|
||||||
|
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void GetChildSongs(CollectionItem *item, QSet<int> &song_ids, QList<QUrl> &urls, SongList &songs) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable QScopedPointer<FilterTree> filter_tree_;
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
mutable size_t query_hash_;
|
||||||
|
#else
|
||||||
|
mutable uint query_hash_;
|
||||||
|
#endif
|
||||||
|
QString filter_string_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COLLECTIONFILTER_H
|
||||||
@@ -29,7 +29,7 @@ CollectionFilterOptions::CollectionFilterOptions() : filter_mode_(FilterMode::Al
|
|||||||
bool CollectionFilterOptions::Matches(const Song &song) const {
|
bool CollectionFilterOptions::Matches(const Song &song) const {
|
||||||
|
|
||||||
if (max_age_ != -1) {
|
if (max_age_ != -1) {
|
||||||
const qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - max_age_;
|
const qint64 cutoff = QDateTime::currentSecsSinceEpoch() - max_age_;
|
||||||
if (song.ctime() <= cutoff) return false;
|
if (song.ctime() <= cutoff) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
@@ -46,8 +47,12 @@
|
|||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
|
#include "collectionfilter.h"
|
||||||
|
#include "collectionquery.h"
|
||||||
|
#include "filterparser/filterparser.h"
|
||||||
#include "savedgroupingmanager.h"
|
#include "savedgroupingmanager.h"
|
||||||
#include "collectionfilterwidget.h"
|
#include "collectionfilterwidget.h"
|
||||||
#include "groupbydialog.h"
|
#include "groupbydialog.h"
|
||||||
@@ -56,60 +61,37 @@
|
|||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "settings/appearancesettingspage.h"
|
#include "settings/appearancesettingspage.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int kFilterDelay = 500; // msec
|
||||||
|
}
|
||||||
|
|
||||||
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
ui_(new Ui_CollectionFilterWidget),
|
ui_(new Ui_CollectionFilterWidget),
|
||||||
model_(nullptr),
|
model_(nullptr),
|
||||||
|
filter_(nullptr),
|
||||||
group_by_dialog_(new GroupByDialog(this)),
|
group_by_dialog_(new GroupByDialog(this)),
|
||||||
groupings_manager_(nullptr),
|
groupings_manager_(nullptr),
|
||||||
filter_age_menu_(nullptr),
|
filter_age_menu_(nullptr),
|
||||||
group_by_menu_(nullptr),
|
group_by_menu_(nullptr),
|
||||||
collection_menu_(nullptr),
|
collection_menu_(nullptr),
|
||||||
group_by_group_(nullptr),
|
group_by_group_(nullptr),
|
||||||
filter_delay_(new QTimer(this)),
|
timer_filter_delay_(new QTimer(this)),
|
||||||
filter_applies_to_model_(true),
|
filter_applies_to_model_(true),
|
||||||
delay_behaviour_(DelayBehaviour::DelayedOnLargeLibraries) {
|
delay_behaviour_(DelayBehaviour::DelayedOnLargeLibraries) {
|
||||||
|
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
|
||||||
QString available_fields = Song::kFtsColumns.join(", ").replace(QRegularExpression("\\bfts"), "");
|
ui_->search_field->setToolTip(FilterParser::ToolTip());
|
||||||
available_fields += QString(", ") + Song::kNumericalColumns.join(", ");
|
|
||||||
|
|
||||||
ui_->search_field->setToolTip(
|
|
||||||
QString("<html><head/><body><p>") +
|
|
||||||
tr("Prefix a word with a field name to limit the search to that field, e.g.:") +
|
|
||||||
QString(" ") +
|
|
||||||
QString("<span style=\"font-weight:600;\">") +
|
|
||||||
tr("artist") +
|
|
||||||
QString(":</span><span style=\"font-style:italic;\">Strawbs</span> ") +
|
|
||||||
tr("searches the collection for all artists that contain the word %1. ").arg("Strawbs") +
|
|
||||||
QString("</p><p>") +
|
|
||||||
tr("Search terms for numerical fields can be prefixed with %1 or %2 to refine the search, e.g.: ")
|
|
||||||
.arg(" =, !=, <, >, <=", ">=") +
|
|
||||||
QString("<span style=\"font-weight:600;\">") +
|
|
||||||
tr("rating") +
|
|
||||||
QString("</span>") +
|
|
||||||
QString(":>=") +
|
|
||||||
QString("<span style=\"font-weight:italic;\">4</span>") +
|
|
||||||
|
|
||||||
QString("</p><p><span style=\"font-weight:600;\">") +
|
|
||||||
tr("Available fields") +
|
|
||||||
QString(": ") +
|
|
||||||
QString("</span>") +
|
|
||||||
QString("<span style=\"font-style:italic;\">") +
|
|
||||||
available_fields +
|
|
||||||
QString("</span>.") +
|
|
||||||
QString("</p></body></html>")
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(ui_->search_field, &QSearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed);
|
QObject::connect(ui_->search_field, &QSearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed);
|
||||||
QObject::connect(filter_delay_, &QTimer::timeout, this, &CollectionFilterWidget::FilterDelayTimeout);
|
QObject::connect(timer_filter_delay_, &QTimer::timeout, this, &CollectionFilterWidget::FilterDelayTimeout);
|
||||||
|
|
||||||
filter_delay_->setInterval(kFilterDelay);
|
timer_filter_delay_->setInterval(kFilterDelay);
|
||||||
filter_delay_->setSingleShot(true);
|
timer_filter_delay_->setSingleShot(true);
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
ui_->options->setIcon(IconLoader::Load("configure"));
|
ui_->options->setIcon(IconLoader::Load(QStringLiteral("configure")));
|
||||||
|
|
||||||
// Filter by age
|
// Filter by age
|
||||||
QActionGroup *filter_age_group = new QActionGroup(this);
|
QActionGroup *filter_age_group = new QActionGroup(this);
|
||||||
@@ -123,12 +105,12 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
|||||||
filter_age_menu_ = new QMenu(tr("Show"), this);
|
filter_age_menu_ = new QMenu(tr("Show"), this);
|
||||||
filter_age_menu_->addActions(filter_age_group->actions());
|
filter_age_menu_->addActions(filter_age_group->actions());
|
||||||
|
|
||||||
filter_ages_[ui_->filter_age_all] = -1;
|
filter_max_ages_[ui_->filter_age_all] = -1;
|
||||||
filter_ages_[ui_->filter_age_today] = 60 * 60 * 24;
|
filter_max_ages_[ui_->filter_age_today] = 60 * 60 * 24;
|
||||||
filter_ages_[ui_->filter_age_week] = 60 * 60 * 24 * 7;
|
filter_max_ages_[ui_->filter_age_week] = 60 * 60 * 24 * 7;
|
||||||
filter_ages_[ui_->filter_age_month] = 60 * 60 * 24 * 30;
|
filter_max_ages_[ui_->filter_age_month] = 60 * 60 * 24 * 30;
|
||||||
filter_ages_[ui_->filter_age_three_months] = 60 * 60 * 24 * 30 * 3;
|
filter_max_ages_[ui_->filter_age_three_months] = 60 * 60 * 24 * 30 * 3;
|
||||||
filter_ages_[ui_->filter_age_year] = 60 * 60 * 24 * 365;
|
filter_max_ages_[ui_->filter_age_year] = 60 * 60 * 24 * 365;
|
||||||
|
|
||||||
group_by_menu_ = new QMenu(tr("Group by"), this);
|
group_by_menu_ = new QMenu(tr("Group by"), this);
|
||||||
|
|
||||||
@@ -154,34 +136,35 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
|||||||
|
|
||||||
CollectionFilterWidget::~CollectionFilterWidget() { delete ui_; }
|
CollectionFilterWidget::~CollectionFilterWidget() { delete ui_; }
|
||||||
|
|
||||||
void CollectionFilterWidget::Init(CollectionModel *model) {
|
void CollectionFilterWidget::Init(CollectionModel *model, CollectionFilter *filter) {
|
||||||
|
|
||||||
if (model_) {
|
if (model_) {
|
||||||
QObject::disconnect(model_, nullptr, this, nullptr);
|
QObject::disconnect(model_, nullptr, this, nullptr);
|
||||||
QObject::disconnect(model_, nullptr, group_by_dialog_, nullptr);
|
QObject::disconnect(model_, nullptr, group_by_dialog_, nullptr);
|
||||||
QObject::disconnect(group_by_dialog_, nullptr, model_, nullptr);
|
QObject::disconnect(group_by_dialog_, nullptr, model_, nullptr);
|
||||||
QList<QAction*> filter_ages = filter_ages_.keys();
|
const QList<QAction*> actions = filter_max_ages_.keys();
|
||||||
for (QAction *action : filter_ages) {
|
for (QAction *action : actions) {
|
||||||
QObject::disconnect(action, &QAction::triggered, model_, nullptr);
|
QObject::disconnect(action, &QAction::triggered, model_, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model_ = model;
|
model_ = model;
|
||||||
|
filter_ = filter;
|
||||||
|
|
||||||
// Connect signals
|
// Connect signals
|
||||||
QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_, &GroupByDialog::CollectionGroupingChanged);
|
QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_, &GroupByDialog::CollectionGroupingChanged);
|
||||||
QObject::connect(model_, &CollectionModel::GroupingChanged, this, &CollectionFilterWidget::GroupingChanged);
|
QObject::connect(model_, &CollectionModel::GroupingChanged, this, &CollectionFilterWidget::GroupingChanged);
|
||||||
QObject::connect(group_by_dialog_, &GroupByDialog::Accepted, model_, &CollectionModel::SetGroupBy);
|
QObject::connect(group_by_dialog_, &GroupByDialog::Accepted, model_, &CollectionModel::SetGroupBy);
|
||||||
|
|
||||||
QList<QAction*> filter_ages = filter_ages_.keys();
|
const QList<QAction*> actions = filter_max_ages_.keys();
|
||||||
for (QAction *action : filter_ages) {
|
for (QAction *action : actions) {
|
||||||
int age = filter_ages_[action];
|
int filter_max_age = filter_max_ages_[action];
|
||||||
QObject::connect(action, &QAction::triggered, this, [this, age]() { model_->SetFilterAge(age); } );
|
QObject::connect(action, &QAction::triggered, this, [this, filter_max_age]() { model_->SetFilterMaxAge(filter_max_age); } );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load settings
|
// Load settings
|
||||||
if (!settings_group_.isEmpty()) {
|
if (!settings_group_.isEmpty()) {
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(settings_group_);
|
s.beginGroup(settings_group_);
|
||||||
int version = 0;
|
int version = 0;
|
||||||
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
|
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
|
||||||
@@ -215,9 +198,13 @@ void CollectionFilterWidget::SetSettingsPrefix(const QString &prefix) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollectionFilterWidget::setFilter(CollectionFilter *filter) {
|
||||||
|
filter_ = filter;
|
||||||
|
}
|
||||||
|
|
||||||
void CollectionFilterWidget::ReloadSettings() {
|
void CollectionFilterWidget::ReloadSettings() {
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
||||||
int iconsize = s.value(AppearanceSettingsPage::kIconSizeConfigureButtons, 20).toInt();
|
int iconsize = s.value(AppearanceSettingsPage::kIconSizeConfigureButtons, 20).toInt();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -229,23 +216,21 @@ void CollectionFilterWidget::ReloadSettings() {
|
|||||||
QString CollectionFilterWidget::group_by_version() const {
|
QString CollectionFilterWidget::group_by_version() const {
|
||||||
|
|
||||||
if (settings_prefix_.isEmpty()) {
|
if (settings_prefix_.isEmpty()) {
|
||||||
return "group_by_version";
|
return QStringLiteral("group_by_version");
|
||||||
}
|
|
||||||
else {
|
|
||||||
return QString("%1_group_by_version").arg(settings_prefix_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return QStringLiteral("%1_group_by_version").arg(settings_prefix_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionFilterWidget::group_by_key() const {
|
QString CollectionFilterWidget::group_by_key() const {
|
||||||
|
|
||||||
if (settings_prefix_.isEmpty()) {
|
if (settings_prefix_.isEmpty()) {
|
||||||
return "group_by";
|
return QStringLiteral("group_by");
|
||||||
}
|
|
||||||
else {
|
|
||||||
return QString("%1_group_by").arg(settings_prefix_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return QStringLiteral("%1_group_by").arg(settings_prefix_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionFilterWidget::group_by_key(const int number) const { return group_by_key() + QString::number(number); }
|
QString CollectionFilterWidget::group_by_key(const int number) const { return group_by_key() + QString::number(number); }
|
||||||
@@ -253,12 +238,11 @@ QString CollectionFilterWidget::group_by_key(const int number) const { return gr
|
|||||||
QString CollectionFilterWidget::separate_albums_by_grouping_key() const {
|
QString CollectionFilterWidget::separate_albums_by_grouping_key() const {
|
||||||
|
|
||||||
if (settings_prefix_.isEmpty()) {
|
if (settings_prefix_.isEmpty()) {
|
||||||
return "separate_albums_by_grouping";
|
return QStringLiteral("separate_albums_by_grouping");
|
||||||
}
|
|
||||||
else {
|
|
||||||
return QString("%1_separate_albums_by_grouping").arg(settings_prefix_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return QStringLiteral("%1_separate_albums_by_grouping").arg(settings_prefix_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionFilterWidget::UpdateGroupByActions() {
|
void CollectionFilterWidget::UpdateGroupByActions() {
|
||||||
@@ -306,13 +290,13 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
|
|||||||
ret->addAction(sep1);
|
ret->addAction(sep1);
|
||||||
|
|
||||||
// Read saved groupings
|
// Read saved groupings
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(saved_groupings_settings_group);
|
s.beginGroup(saved_groupings_settings_group);
|
||||||
int version = s.value("version").toInt();
|
int version = s.value("version").toInt();
|
||||||
if (version == 1) {
|
if (version == 1) {
|
||||||
QStringList saved = s.childKeys();
|
QStringList saved = s.childKeys();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
if (saved.at(i) == "version") continue;
|
if (saved.at(i) == QLatin1String("version")) continue;
|
||||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||||
CollectionModel::Grouping g;
|
CollectionModel::Grouping g;
|
||||||
@@ -323,7 +307,7 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
|
|||||||
else {
|
else {
|
||||||
QStringList saved = s.childKeys();
|
QStringList saved = s.childKeys();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
if (saved.at(i) == "version") continue;
|
if (saved.at(i) == QLatin1String("version")) continue;
|
||||||
s.remove(saved.at(i));
|
s.remove(saved.at(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,17 +345,17 @@ void CollectionFilterWidget::SaveGroupBy() {
|
|||||||
|
|
||||||
qLog(Debug) << "Saving current grouping to" << name;
|
qLog(Debug) << "Saving current grouping to" << name;
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
if (settings_group_.isEmpty() || settings_group_ == CollectionSettingsPage::kSettingsGroup) {
|
if (settings_group_.isEmpty() || settings_group_ == QLatin1String(CollectionSettingsPage::kSettingsGroup)) {
|
||||||
s.beginGroup(SavedGroupingManager::kSavedGroupingsSettingsGroup);
|
s.beginGroup(SavedGroupingManager::kSavedGroupingsSettingsGroup);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
s.beginGroup(QString(SavedGroupingManager::kSavedGroupingsSettingsGroup) + "_" + settings_group_);
|
s.beginGroup(QLatin1String(SavedGroupingManager::kSavedGroupingsSettingsGroup) + QLatin1Char('_') + settings_group_);
|
||||||
}
|
}
|
||||||
QByteArray buffer;
|
QByteArray buffer;
|
||||||
QDataStream datastream(&buffer, QIODevice::WriteOnly);
|
QDataStream datastream(&buffer, QIODevice::WriteOnly);
|
||||||
datastream << model_->GetGroupBy();
|
datastream << model_->GetGroupBy();
|
||||||
s.setValue("version", "1");
|
s.setValue("version", QStringLiteral("1"));
|
||||||
s.setValue(name, buffer);
|
s.setValue(name, buffer);
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
@@ -425,7 +409,7 @@ void CollectionFilterWidget::GroupByClicked(QAction *action) {
|
|||||||
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping) {
|
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping) {
|
||||||
|
|
||||||
if (!settings_group_.isEmpty()) {
|
if (!settings_group_.isEmpty()) {
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(settings_group_);
|
s.beginGroup(settings_group_);
|
||||||
s.setValue(group_by_version(), 1);
|
s.setValue(group_by_version(), 1);
|
||||||
s.setValue(group_by_key(1), static_cast<int>(g[0]));
|
s.setValue(group_by_key(1), static_cast<int>(g[0]));
|
||||||
@@ -446,7 +430,8 @@ void CollectionFilterWidget::CheckCurrentGrouping(const CollectionModel::Groupin
|
|||||||
UpdateGroupByActions();
|
UpdateGroupByActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (QAction *action : group_by_group_->actions()) {
|
const QList<QAction*> actions = group_by_group_->actions();
|
||||||
|
for (QAction *action : actions) {
|
||||||
if (action->property("group_by").isNull()) continue;
|
if (action->property("group_by").isNull()) continue;
|
||||||
|
|
||||||
if (g == action->property("group_by").value<CollectionModel::Grouping>()) {
|
if (g == action->property("group_by").value<CollectionModel::Grouping>()) {
|
||||||
@@ -456,7 +441,6 @@ void CollectionFilterWidget::CheckCurrentGrouping(const CollectionModel::Groupin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check the advanced action
|
// Check the advanced action
|
||||||
QList<QAction*> actions = group_by_group_->actions();
|
|
||||||
QAction *action = actions.last();
|
QAction *action = actions.last();
|
||||||
action->setChecked(true);
|
action->setChecked(true);
|
||||||
|
|
||||||
@@ -508,6 +492,9 @@ void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) {
|
|||||||
ui_->search_field->clear();
|
ui_->search_field->clear();
|
||||||
e->accept();
|
e->accept();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
QWidget::keyReleaseEvent(e);
|
QWidget::keyReleaseEvent(e);
|
||||||
@@ -516,16 +503,13 @@ void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) {
|
|||||||
|
|
||||||
void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
||||||
|
|
||||||
// Searching with one or two characters can be very expensive on the database even with FTS,
|
|
||||||
// so if there are a large number of songs in the database introduce a small delay before actually filtering the model,
|
|
||||||
// so if the user is typing the first few characters of something it will be quicker.
|
|
||||||
const bool delay = (delay_behaviour_ == DelayBehaviour::AlwaysDelayed) || (delay_behaviour_ == DelayBehaviour::DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
const bool delay = (delay_behaviour_ == DelayBehaviour::AlwaysDelayed) || (delay_behaviour_ == DelayBehaviour::DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
||||||
|
|
||||||
if (delay) {
|
if (delay) {
|
||||||
filter_delay_->start();
|
timer_filter_delay_->start();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
filter_delay_->stop();
|
timer_filter_delay_->stop();
|
||||||
FilterDelayTimeout();
|
FilterDelayTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,9 +517,8 @@ void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
|||||||
|
|
||||||
void CollectionFilterWidget::FilterDelayTimeout() {
|
void CollectionFilterWidget::FilterDelayTimeout() {
|
||||||
|
|
||||||
emit Filter(ui_->search_field->text());
|
|
||||||
if (filter_applies_to_model_) {
|
if (filter_applies_to_model_) {
|
||||||
model_->SetFilterText(ui_->search_field->text());
|
filter_->SetFilterString(ui_->search_field->text());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class QKeyEvent;
|
|||||||
|
|
||||||
class GroupByDialog;
|
class GroupByDialog;
|
||||||
class SavedGroupingManager;
|
class SavedGroupingManager;
|
||||||
|
class CollectionFilter;
|
||||||
class Ui_CollectionFilterWidget;
|
class Ui_CollectionFilterWidget;
|
||||||
|
|
||||||
class CollectionFilterWidget : public QWidget {
|
class CollectionFilterWidget : public QWidget {
|
||||||
@@ -50,15 +51,15 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
explicit CollectionFilterWidget(QWidget *parent = nullptr);
|
explicit CollectionFilterWidget(QWidget *parent = nullptr);
|
||||||
~CollectionFilterWidget() override;
|
~CollectionFilterWidget() override;
|
||||||
|
|
||||||
static const int kFilterDelay = 500; // msec
|
|
||||||
|
|
||||||
enum class DelayBehaviour {
|
enum class DelayBehaviour {
|
||||||
AlwaysInstant,
|
AlwaysInstant,
|
||||||
DelayedOnLargeLibraries,
|
DelayedOnLargeLibraries,
|
||||||
AlwaysDelayed,
|
AlwaysDelayed,
|
||||||
};
|
};
|
||||||
|
|
||||||
void Init(CollectionModel *model);
|
void Init(CollectionModel *model, CollectionFilter *filter);
|
||||||
|
|
||||||
|
void setFilter(CollectionFilter *filter);
|
||||||
|
|
||||||
static QActionGroup *CreateGroupByActions(const QString &saved_groupings_settings_group, QObject *parent);
|
static QActionGroup *CreateGroupByActions(const QString &saved_groupings_settings_group, QObject *parent);
|
||||||
|
|
||||||
@@ -94,7 +95,6 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
void UpPressed();
|
void UpPressed();
|
||||||
void DownPressed();
|
void DownPressed();
|
||||||
void ReturnPressed();
|
void ReturnPressed();
|
||||||
void Filter(const QString &text);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void keyReleaseEvent(QKeyEvent *e) override;
|
void keyReleaseEvent(QKeyEvent *e) override;
|
||||||
@@ -115,6 +115,7 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
private:
|
private:
|
||||||
Ui_CollectionFilterWidget *ui_;
|
Ui_CollectionFilterWidget *ui_;
|
||||||
CollectionModel *model_;
|
CollectionModel *model_;
|
||||||
|
CollectionFilter *filter_;
|
||||||
|
|
||||||
GroupByDialog *group_by_dialog_;
|
GroupByDialog *group_by_dialog_;
|
||||||
SavedGroupingManager *groupings_manager_;
|
SavedGroupingManager *groupings_manager_;
|
||||||
@@ -123,9 +124,9 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
QMenu *group_by_menu_;
|
QMenu *group_by_menu_;
|
||||||
QMenu *collection_menu_;
|
QMenu *collection_menu_;
|
||||||
QActionGroup *group_by_group_;
|
QActionGroup *group_by_group_;
|
||||||
QHash<QAction*, int> filter_ages_;
|
QHash<QAction*, int> filter_max_ages_;
|
||||||
|
|
||||||
QTimer *filter_delay_;
|
QTimer *timer_filter_delay_;
|
||||||
|
|
||||||
bool filter_applies_to_model_;
|
bool filter_applies_to_model_;
|
||||||
DelayBehaviour delay_behaviour_;
|
DelayBehaviour delay_behaviour_;
|
||||||
|
|||||||
@@ -38,6 +38,9 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QToolButton" name="options">
|
<widget class="QToolButton" name="options">
|
||||||
|
<property name="accessibleName">
|
||||||
|
<string>MenuPopupToolButton</string>
|
||||||
|
</property>
|
||||||
<property name="iconSize">
|
<property name="iconSize">
|
||||||
<size>
|
<size>
|
||||||
<width>16</width>
|
<width>16</width>
|
||||||
|
|||||||
@@ -29,24 +29,27 @@
|
|||||||
|
|
||||||
class CollectionItem : public SimpleTreeItem<CollectionItem> {
|
class CollectionItem : public SimpleTreeItem<CollectionItem> {
|
||||||
public:
|
public:
|
||||||
enum Type {
|
enum class Type {
|
||||||
Type_Root,
|
Root,
|
||||||
Type_Divider,
|
Divider,
|
||||||
Type_Container,
|
Container,
|
||||||
Type_Song,
|
Song,
|
||||||
Type_LoadingIndicator,
|
LoadingIndicator,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit CollectionItem(SimpleTreeModel<CollectionItem> *_model)
|
explicit CollectionItem(SimpleTreeModel<CollectionItem> *_model)
|
||||||
: SimpleTreeItem<CollectionItem>(Type_Root, _model),
|
: SimpleTreeItem<CollectionItem>(_model),
|
||||||
|
type(Type::Root),
|
||||||
container_level(-1),
|
container_level(-1),
|
||||||
compilation_artist_node_(nullptr) {}
|
compilation_artist_node_(nullptr) {}
|
||||||
|
|
||||||
explicit CollectionItem(Type _type, CollectionItem *_parent = nullptr)
|
explicit CollectionItem(const Type _type, CollectionItem *_parent = nullptr)
|
||||||
: SimpleTreeItem<CollectionItem>(_type, _parent),
|
: SimpleTreeItem<CollectionItem>(_parent),
|
||||||
|
type(_type),
|
||||||
container_level(-1),
|
container_level(-1),
|
||||||
compilation_artist_node_(nullptr) {}
|
compilation_artist_node_(nullptr) {}
|
||||||
|
|
||||||
|
Type type;
|
||||||
int container_level;
|
int container_level;
|
||||||
Song metadata;
|
Song metadata;
|
||||||
CollectionItem *compilation_artist_node_;
|
CollectionItem *compilation_artist_node_;
|
||||||
@@ -55,4 +58,6 @@ class CollectionItem : public SimpleTreeItem<CollectionItem> {
|
|||||||
Q_DISABLE_COPY(CollectionItem)
|
Q_DISABLE_COPY(CollectionItem)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(CollectionItem::Type)
|
||||||
|
|
||||||
#endif // COLLECTIONITEM_H
|
#endif // COLLECTIONITEM_H
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ bool CollectionItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *vie
|
|||||||
if (text.isEmpty()) return false;
|
if (text.isEmpty()) return false;
|
||||||
|
|
||||||
switch (event->type()) {
|
switch (event->type()) {
|
||||||
case QEvent::ToolTip: {
|
case QEvent::ToolTip:{
|
||||||
|
|
||||||
QSize real_text = sizeHint(option, idx);
|
QSize real_text = sizeHint(option, idx);
|
||||||
QRect displayed_text = view->visualRect(idx);
|
QRect displayed_text = view->visualRect(idx);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -44,6 +42,7 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QNetworkDiskCache>
|
#include <QNetworkDiskCache>
|
||||||
|
#include <QQueue>
|
||||||
|
|
||||||
#include "core/shared_ptr.h"
|
#include "core/shared_ptr.h"
|
||||||
#include "core/simpletreemodel.h"
|
#include "core/simpletreemodel.h"
|
||||||
@@ -51,15 +50,17 @@
|
|||||||
#include "core/sqlrow.h"
|
#include "core/sqlrow.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
#include "collectionmodelupdate.h"
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionqueryoptions.h"
|
|
||||||
#include "collectionitem.h"
|
#include "collectionitem.h"
|
||||||
|
|
||||||
class QSettings;
|
class QTimer;
|
||||||
|
class Settings;
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class CollectionDirectoryModel;
|
class CollectionDirectoryModel;
|
||||||
|
class CollectionFilter;
|
||||||
|
|
||||||
class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -69,20 +70,19 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
~CollectionModel() override;
|
~CollectionModel() override;
|
||||||
|
|
||||||
static const int kPrettyCoverSize;
|
static const int kPrettyCoverSize;
|
||||||
static const char *kPixmapDiskCacheDir;
|
|
||||||
|
|
||||||
enum Role {
|
enum Role {
|
||||||
Role_Type = Qt::UserRole + 1,
|
Role_Type = Qt::UserRole + 1,
|
||||||
Role_ContainerType,
|
Role_ContainerType,
|
||||||
Role_SortText,
|
Role_SortText,
|
||||||
Role_Key,
|
Role_ContainerKey,
|
||||||
Role_Artist,
|
Role_Artist,
|
||||||
Role_IsDivider,
|
Role_IsDivider,
|
||||||
Role_Editable,
|
Role_Editable,
|
||||||
LastRole
|
LastRole
|
||||||
};
|
};
|
||||||
|
|
||||||
// These values get saved in QSettings - don't change them
|
// These values get saved in Settings - don't change them
|
||||||
enum class GroupBy {
|
enum class GroupBy {
|
||||||
None = 0,
|
None = 0,
|
||||||
AlbumArtist = 1,
|
AlbumArtist = 1,
|
||||||
@@ -125,167 +125,176 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
bool operator!=(const Grouping other) const { return !(*this == other); }
|
bool operator!=(const Grouping other) const { return !(*this == other); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QueryResult {
|
struct Options {
|
||||||
QueryResult() : create_va(false) {}
|
Options() : group_by(GroupBy::AlbumArtist, GroupBy::AlbumDisc, GroupBy::None),
|
||||||
|
show_dividers(true),
|
||||||
|
show_pretty_covers(true),
|
||||||
|
show_various_artists(true),
|
||||||
|
sort_skips_articles(true),
|
||||||
|
separate_albums_by_grouping(false) {}
|
||||||
|
|
||||||
SqlRowList rows;
|
Grouping group_by;
|
||||||
bool create_va;
|
bool show_dividers;
|
||||||
|
bool show_pretty_covers;
|
||||||
|
bool show_various_artists;
|
||||||
|
bool sort_skips_articles;
|
||||||
|
bool separate_albums_by_grouping;
|
||||||
|
CollectionFilterOptions filter_options;
|
||||||
};
|
};
|
||||||
|
|
||||||
SharedPtr<CollectionBackend> backend() const { return backend_; }
|
SharedPtr<CollectionBackend> backend() const { return backend_; }
|
||||||
|
CollectionFilter *filter() const { return filter_; }
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
void ReloadSettings();
|
||||||
|
|
||||||
CollectionDirectoryModel *directory_model() const { return dir_model_; }
|
CollectionDirectoryModel *directory_model() const { return dir_model_; }
|
||||||
|
|
||||||
// Call before Init()
|
|
||||||
void set_show_various_artists(const bool show_various_artists) { show_various_artists_ = show_various_artists; }
|
|
||||||
|
|
||||||
// Get information about the collection
|
|
||||||
void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
|
|
||||||
SongList GetChildSongs(const QModelIndex &idx) const;
|
|
||||||
SongList GetChildSongs(const QModelIndexList &indexes) const;
|
|
||||||
|
|
||||||
// Might be accurate
|
|
||||||
int total_song_count() const { return total_song_count_; }
|
int total_song_count() const { return total_song_count_; }
|
||||||
int total_artist_count() const { return total_artist_count_; }
|
int total_artist_count() const { return total_artist_count_; }
|
||||||
int total_album_count() const { return total_album_count_; }
|
int total_album_count() const { return total_album_count_; }
|
||||||
|
|
||||||
// QAbstractItemModel
|
|
||||||
QVariant data(const QModelIndex &idx, const int role = Qt::DisplayRole) const override;
|
|
||||||
Qt::ItemFlags flags(const QModelIndex &idx) const override;
|
|
||||||
QStringList mimeTypes() const override;
|
|
||||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
|
||||||
bool canFetchMore(const QModelIndex &parent) const override;
|
|
||||||
|
|
||||||
// Whether or not to use album cover art, if it exists, in the collection view
|
|
||||||
void set_pretty_covers(const bool use_pretty_covers);
|
|
||||||
bool use_pretty_covers() const { return use_pretty_covers_; }
|
|
||||||
|
|
||||||
// Whether or not to show letters heading in the collection view
|
|
||||||
void set_show_dividers(const bool show_dividers);
|
|
||||||
|
|
||||||
// Reload settings.
|
|
||||||
void ReloadSettings();
|
|
||||||
|
|
||||||
// Utility functions for manipulating text
|
|
||||||
static QString TextOrUnknown(const QString &text);
|
|
||||||
static QString PrettyYearAlbum(const int year, const QString &album);
|
|
||||||
static QString PrettyAlbumDisc(const QString &album, const int disc);
|
|
||||||
static QString PrettyYearAlbumDisc(const int year, const QString &album, const int disc);
|
|
||||||
static QString PrettyDisc(const int disc);
|
|
||||||
static QString SortText(QString text);
|
|
||||||
static QString SortTextForNumber(const int number);
|
|
||||||
static QString SortTextForArtist(QString artist);
|
|
||||||
static QString SortTextForSong(const Song &song);
|
|
||||||
static QString SortTextForYear(const int year);
|
|
||||||
static QString SortTextForBitrate(const int bitrate);
|
|
||||||
|
|
||||||
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
||||||
|
|
||||||
|
const CollectionModel::Grouping GetGroupBy() const { return options_current_.group_by; }
|
||||||
|
void SetGroupBy(const CollectionModel::Grouping g, const std::optional<bool> separate_albums_by_grouping = std::optional<bool>());
|
||||||
|
|
||||||
static bool IsArtistGroupBy(const GroupBy group_by) {
|
static bool IsArtistGroupBy(const GroupBy group_by) {
|
||||||
return group_by == CollectionModel::GroupBy::Artist || group_by == CollectionModel::GroupBy::AlbumArtist;
|
return group_by == CollectionModel::GroupBy::Artist || group_by == CollectionModel::GroupBy::AlbumArtist;
|
||||||
}
|
}
|
||||||
static bool IsAlbumGroupBy(const GroupBy group_by) { return group_by == GroupBy::Album || group_by == GroupBy::YearAlbum || group_by == GroupBy::AlbumDisc || group_by == GroupBy::YearAlbumDisc || group_by == GroupBy::OriginalYearAlbum || group_by == GroupBy::OriginalYearAlbumDisc; }
|
static bool IsAlbumGroupBy(const GroupBy group_by) { return group_by == GroupBy::Album || group_by == GroupBy::YearAlbum || group_by == GroupBy::AlbumDisc || group_by == GroupBy::YearAlbumDisc || group_by == GroupBy::OriginalYearAlbum || group_by == GroupBy::OriginalYearAlbumDisc; }
|
||||||
|
|
||||||
void set_use_lazy_loading(const bool value) { use_lazy_loading_ = value; }
|
|
||||||
|
|
||||||
QMap<QString, CollectionItem*> container_nodes(const int i) { return container_nodes_[i]; }
|
QMap<QString, CollectionItem*> container_nodes(const int i) { return container_nodes_[i]; }
|
||||||
QList<CollectionItem*> song_nodes() const { return song_nodes_.values(); }
|
QList<CollectionItem*> song_nodes() const { return song_nodes_.values(); }
|
||||||
int divider_nodes_count() const { return divider_nodes_.count(); }
|
int divider_nodes_count() const { return divider_nodes_.count(); }
|
||||||
|
|
||||||
void ExpandAll(CollectionItem *item = nullptr) const;
|
// QAbstractItemModel
|
||||||
|
QVariant data(const QModelIndex &idx, const int role = Qt::DisplayRole) const override;
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &idx) const override;
|
||||||
|
QStringList mimeTypes() const override;
|
||||||
|
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||||
|
|
||||||
const CollectionModel::Grouping GetGroupBy() const { return group_by_; }
|
// Utility functions for manipulating text
|
||||||
void SetGroupBy(const CollectionModel::Grouping g, const std::optional<bool> separate_albums_by_grouping = std::optional<bool>());
|
static QString DisplayText(const GroupBy group_by, const Song &song);
|
||||||
|
static QString TextOrUnknown(const QString &text);
|
||||||
|
static QString PrettyYearAlbum(const int year, const QString &album);
|
||||||
|
static QString PrettyAlbumDisc(const QString &album, const int disc);
|
||||||
|
static QString PrettyYearAlbumDisc(const int year, const QString &album, const int disc);
|
||||||
|
static QString PrettyDisc(const int disc);
|
||||||
|
static QString PrettyFormat(const Song &song);
|
||||||
|
QString SortText(const GroupBy group_by, const int container_level, const Song &song, const bool sort_skips_articles);
|
||||||
|
static QString SortText(QString text);
|
||||||
|
static QString SortTextForNumber(const int number);
|
||||||
|
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);
|
||||||
|
static bool IsSongTitleDataChanged(const Song &song1, const Song &song2);
|
||||||
|
QString ContainerKey(const GroupBy group_by, const Song &song, bool &has_unique_album_identifier) const;
|
||||||
|
|
||||||
static QString ContainerKey(const GroupBy group_by, const bool separate_albums_by_grouping, const Song &song);
|
// Get information about the collection
|
||||||
|
void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
|
||||||
|
SongList GetChildSongs(const QModelIndex &idx) const;
|
||||||
|
SongList GetChildSongs(const QModelIndexList &indexes) const;
|
||||||
|
|
||||||
|
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void TotalSongCountUpdated(const int count);
|
void TotalSongCountUpdated(const int count);
|
||||||
void TotalArtistCountUpdated(const int count);
|
void TotalArtistCountUpdated(const int count);
|
||||||
void TotalAlbumCountUpdated(const int count);
|
void TotalAlbumCountUpdated(const int count);
|
||||||
void GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
void GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
||||||
|
void SongsAdded(const SongList &songs);
|
||||||
|
void SongsRemoved(const SongList &songs);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
void SetFilterMode(const CollectionFilterOptions::FilterMode filter_mode);
|
||||||
void SetFilterAge(const int filter_age);
|
void SetFilterMaxAge(const int filter_max_age);
|
||||||
void SetFilterText(const QString &filter_text);
|
|
||||||
|
|
||||||
void Init(const bool async = true);
|
void AddReAddOrUpdate(const SongList &songs);
|
||||||
void Reset();
|
void RemoveSongs(const SongList &songs);
|
||||||
void ResetAsync();
|
|
||||||
|
|
||||||
void SongsDiscovered(const SongList &songs);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void LazyPopulate(CollectionItem *item) override { LazyPopulate(item, true); }
|
|
||||||
void LazyPopulate(CollectionItem *parent, const bool signal);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
// From CollectionBackend
|
|
||||||
void SongsDeleted(const SongList &songs);
|
|
||||||
void SongsSlightlyChanged(const SongList &songs);
|
|
||||||
void TotalSongCountUpdatedSlot(const int count);
|
|
||||||
void TotalArtistCountUpdatedSlot(const int count);
|
|
||||||
void TotalAlbumCountUpdatedSlot(const int count);
|
|
||||||
static void ClearDiskCache();
|
|
||||||
|
|
||||||
// Called after ResetAsync
|
|
||||||
void ResetAsyncQueryFinished();
|
|
||||||
|
|
||||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Provides some optimizations for loading the list of items in the root.
|
void Clear();
|
||||||
// This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
|
|
||||||
CollectionQueryOptions PrepareQuery(CollectionItem *parent);
|
|
||||||
QueryResult RunQuery(const CollectionFilterOptions &filter_options = CollectionFilterOptions(), const CollectionQueryOptions &query_options = CollectionQueryOptions());
|
|
||||||
void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal);
|
|
||||||
|
|
||||||
bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options);
|
|
||||||
|
|
||||||
void BeginReset();
|
void BeginReset();
|
||||||
|
void EndReset();
|
||||||
|
|
||||||
// Functions for working with queries and creating items.
|
QVariant data(const CollectionItem *item, const int role) const;
|
||||||
// When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items.
|
|
||||||
// Filters are added for each parent item, restricting the songs returned to a particular album or artist for example.
|
|
||||||
static void SetQueryColumnSpec(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQueryOptions *query_options);
|
|
||||||
static void AddQueryWhere(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQueryOptions *query_options);
|
|
||||||
|
|
||||||
// Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
|
void ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs);
|
||||||
CollectionItem *ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
|
void ScheduleAddSongs(const SongList &songs);
|
||||||
CollectionItem *ItemFromSong(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level);
|
void ScheduleUpdateSongs(const SongList &songs);
|
||||||
|
void ScheduleRemoveSongs(const SongList &songs);
|
||||||
|
|
||||||
// The "Various Artists" node is an annoying special case.
|
void AddReAddOrUpdateSongsInternal(const SongList &songs);
|
||||||
CollectionItem *CreateCompilationArtistNode(const bool signal, CollectionItem *parent);
|
void AddSongsInternal(const SongList &songs);
|
||||||
|
void UpdateSongsInternal(const SongList &songs);
|
||||||
|
void RemoveSongsInternal(const SongList &songs);
|
||||||
|
|
||||||
// Helpers for ItemFromQuery and ItemFromSong
|
void CreateDividerItem(const QString ÷r_key, const QString &display_text, CollectionItem *parent);
|
||||||
CollectionItem *InitItem(const GroupBy group_by, const bool signal, CollectionItem *parent, const int container_level);
|
CollectionItem *CreateContainerItem(const GroupBy group_by, const int container_level, const QString &container_key, const Song &song, CollectionItem *parent);
|
||||||
void FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item);
|
void CreateSongItem(const Song &song, CollectionItem *parent);
|
||||||
|
void SetSongItemData(CollectionItem *item, const Song &song);
|
||||||
|
CollectionItem *CreateCompilationArtistNode(CollectionItem *parent);
|
||||||
|
|
||||||
static QString DividerKey(const GroupBy group_by, CollectionItem *item);
|
void LoadSongsFromSqlAsync();
|
||||||
|
SongList LoadSongsFromSql(const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||||
|
|
||||||
|
static QString DividerKey(const GroupBy group_by, const Song &song, const QString &sort_text);
|
||||||
static QString DividerDisplayText(const GroupBy group_by, const QString &key);
|
static QString DividerDisplayText(const GroupBy group_by, const QString &key);
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
|
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
|
||||||
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
||||||
QUrl AlbumIconPixmapDiskCacheKey(const QString &cache_key) const;
|
static QUrl AlbumIconPixmapDiskCacheKey(const QString &cache_key);
|
||||||
QVariant AlbumIcon(const QModelIndex &idx);
|
QVariant AlbumIcon(const QModelIndex &idx);
|
||||||
QVariant data(const CollectionItem *item, const int role) const;
|
void ClearItemPixmapCache(CollectionItem *item);
|
||||||
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
|
||||||
static qint64 MaximumCacheSize(QSettings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
|
|
||||||
|
private slots:
|
||||||
|
void Reload();
|
||||||
|
void ScheduleReset();
|
||||||
|
void ProcessUpdate();
|
||||||
|
void LoadSongsFromSqlAsyncFinished();
|
||||||
|
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||||
|
|
||||||
|
// From CollectionBackend
|
||||||
|
void TotalSongCountUpdatedSlot(const int count);
|
||||||
|
void TotalArtistCountUpdatedSlot(const int count);
|
||||||
|
void TotalAlbumCountUpdatedSlot(const int count);
|
||||||
|
|
||||||
|
static void ClearDiskCache();
|
||||||
|
|
||||||
|
void RowsInserted(const QModelIndex &parent, const int first, const int last);
|
||||||
|
void RowsRemoved(const QModelIndex &parent, const int first, const int last);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static QNetworkDiskCache *sIconCache;
|
||||||
SharedPtr<CollectionBackend> backend_;
|
SharedPtr<CollectionBackend> backend_;
|
||||||
Application *app_;
|
Application *app_;
|
||||||
CollectionDirectoryModel *dir_model_;
|
CollectionDirectoryModel *dir_model_;
|
||||||
bool show_various_artists_;
|
CollectionFilter *filter_;
|
||||||
|
QTimer *timer_reload_;
|
||||||
|
QTimer *timer_update_;
|
||||||
|
|
||||||
|
QPixmap pixmap_no_cover_;
|
||||||
|
QIcon icon_artist_;
|
||||||
|
|
||||||
|
Options options_current_;
|
||||||
|
Options options_active_;
|
||||||
|
|
||||||
|
bool use_disk_cache_;
|
||||||
|
AlbumCoverLoaderOptions::Types cover_types_;
|
||||||
|
|
||||||
int total_song_count_;
|
int total_song_count_;
|
||||||
int total_artist_count_;
|
int total_artist_count_;
|
||||||
int total_album_count_;
|
int total_album_count_;
|
||||||
|
|
||||||
CollectionFilterOptions filter_options_;
|
bool loading_;
|
||||||
Grouping group_by_;
|
|
||||||
bool separate_albums_by_grouping_;
|
QQueue<CollectionModelUpdate> updates_;
|
||||||
|
|
||||||
// Keyed on database ID
|
// Keyed on database ID
|
||||||
QMap<int, CollectionItem*> song_nodes_;
|
QMap<int, CollectionItem*> song_nodes_;
|
||||||
@@ -296,22 +305,6 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
// Keyed on a letter, a year, a century, etc.
|
// Keyed on a letter, a year, a century, etc.
|
||||||
QMap<QString, CollectionItem*> divider_nodes_;
|
QMap<QString, CollectionItem*> divider_nodes_;
|
||||||
|
|
||||||
QIcon artist_icon_;
|
|
||||||
QIcon album_icon_;
|
|
||||||
// Used as a generic icon to show when no cover art is found, fixed to the same size as the artwork (32x32)
|
|
||||||
QPixmap no_cover_icon_;
|
|
||||||
|
|
||||||
static QNetworkDiskCache *sIconCache;
|
|
||||||
|
|
||||||
int init_task_id_;
|
|
||||||
|
|
||||||
bool use_pretty_covers_;
|
|
||||||
bool show_dividers_;
|
|
||||||
bool use_disk_cache_;
|
|
||||||
bool use_lazy_loading_;
|
|
||||||
|
|
||||||
AlbumCoverLoaderOptions::Types cover_types_;
|
|
||||||
|
|
||||||
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
||||||
QMap<quint64, ItemAndCacheKey> pending_art_;
|
QMap<quint64, ItemAndCacheKey> pending_art_;
|
||||||
QSet<QString> pending_cache_keys_;
|
QSet<QString> pending_cache_keys_;
|
||||||
|
|||||||
23
src/collection/collectionmodelupdate.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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 "collectionmodelupdate.h"
|
||||||
|
|
||||||
|
CollectionModelUpdate::CollectionModelUpdate(const Type &_type, const SongList &_songs)
|
||||||
|
: type(_type), songs(_songs) {}
|
||||||
@@ -17,17 +17,22 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QVariant>
|
#ifndef COLLECTIONMODELUPDATE_H
|
||||||
#include <QString>
|
#define COLLECTIONMODELUPDATE_H
|
||||||
|
|
||||||
#include "collectionqueryoptions.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
CollectionQueryOptions::CollectionQueryOptions()
|
class CollectionModelUpdate {
|
||||||
: compilation_requirement_(CollectionQueryOptions::CompilationRequirement::None),
|
public:
|
||||||
query_have_compilations_(false) {}
|
enum class Type {
|
||||||
|
AddReAddOrUpdate,
|
||||||
|
Add,
|
||||||
|
Update,
|
||||||
|
Remove,
|
||||||
|
};
|
||||||
|
explicit CollectionModelUpdate(const Type &_type, const SongList &_songs);
|
||||||
|
Type type;
|
||||||
|
SongList songs;
|
||||||
|
};
|
||||||
|
|
||||||
void CollectionQueryOptions::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
#endif // COLLECTIONMODELUPDATE_H
|
||||||
|
|
||||||
where_clauses_ << Where(column, value, op);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -41,7 +41,11 @@ QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
|||||||
|
|
||||||
void CollectionPlaylistItem::Reload() {
|
void CollectionPlaylistItem::Reload() {
|
||||||
|
|
||||||
TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||||
|
if (!result.success()) {
|
||||||
|
qLog(Error) << "Could not reload file" << song_.url() << result.error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
UpdateTemporaryMetadata(song_);
|
UpdateTemporaryMetadata(song_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,141 +27,50 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QStringBuilder>
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
|
||||||
|
|
||||||
|
#include "core/sqlquery.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
#include "utilities/searchparserutils.h"
|
|
||||||
|
|
||||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options)
|
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options)
|
||||||
: QSqlQuery(db),
|
: SqlQuery(db),
|
||||||
songs_table_(songs_table),
|
songs_table_(songs_table),
|
||||||
fts_table_(fts_table),
|
|
||||||
include_unavailable_(false),
|
include_unavailable_(false),
|
||||||
join_with_fts_(false),
|
|
||||||
duplicates_only_(false),
|
duplicates_only_(false),
|
||||||
limit_(-1) {
|
limit_(-1) {
|
||||||
|
|
||||||
if (!filter_options.filter_text().isEmpty()) {
|
|
||||||
// We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS5:
|
|
||||||
// 1) Append * to all tokens.
|
|
||||||
// 2) Prefix "fts" to column names.
|
|
||||||
// 3) Remove colons which don't correspond to column names.
|
|
||||||
|
|
||||||
// Split on whitespace
|
|
||||||
QString filter_text = filter_options.filter_text().replace(QRegularExpression(":\\s+"), ":");
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
|
|
||||||
#else
|
|
||||||
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
|
||||||
#endif
|
|
||||||
QString query;
|
|
||||||
for (QString token : tokens) {
|
|
||||||
token.remove('(')
|
|
||||||
.remove(')')
|
|
||||||
.remove('"')
|
|
||||||
.replace('-', ' ');
|
|
||||||
|
|
||||||
if (token.contains(':')) {
|
|
||||||
const QString columntoken = token.section(':', 0, 0);
|
|
||||||
QString subtoken = token.section(':', 1, -1).replace(":", " ").trimmed();
|
|
||||||
if (subtoken.isEmpty()) continue;
|
|
||||||
if (Song::kFtsColumns.contains("fts" + columntoken, Qt::CaseInsensitive)) {
|
|
||||||
if (!query.isEmpty()) query.append(" ");
|
|
||||||
query += "fts" + columntoken + ":\"" + subtoken + "\"*";
|
|
||||||
}
|
|
||||||
else if (Song::kNumericalColumns.contains(columntoken, Qt::CaseInsensitive)) {
|
|
||||||
QString comparator = RemoveSqlOperator(subtoken);
|
|
||||||
if (columntoken.compare("rating", Qt::CaseInsensitive) == 0) {
|
|
||||||
AddWhereRating(subtoken, comparator);
|
|
||||||
}
|
|
||||||
else if (columntoken.compare("length", Qt::CaseInsensitive) == 0) {
|
|
||||||
// Time is saved in nanoseconds, so add 9 0's
|
|
||||||
QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + "000000000";
|
|
||||||
AddWhere(columntoken, parsedTime, comparator);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
AddWhere(columntoken, subtoken, comparator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Not a valid filter, remove
|
|
||||||
else {
|
|
||||||
token = token.replace(":", " ").trimmed();
|
|
||||||
if (!token.isEmpty()) {
|
|
||||||
if (!query.isEmpty()) query.append(" ");
|
|
||||||
query += "\"" + token + "\"*";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!query.isEmpty()) query.append(" ");
|
|
||||||
query += "\"" + token + "\"*";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!query.isEmpty()) {
|
|
||||||
where_clauses_ << "fts.%fts_table_noprefix MATCH ?";
|
|
||||||
bound_values_ << query;
|
|
||||||
join_with_fts_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter_options.max_age() != -1) {
|
if (filter_options.max_age() != -1) {
|
||||||
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
||||||
|
|
||||||
where_clauses_ << "ctime > ?";
|
where_clauses_ << QStringLiteral("ctime > ?");
|
||||||
bound_values_ << cutoff;
|
bound_values_ << cutoff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Currently you cannot use any FilterMode other than All and FTS at the same time.
|
|
||||||
// Joining songs, duplicated_songs and songs_fts all together takes a huge amount of time.
|
|
||||||
// The query takes about 20 seconds on my machine then. Why?
|
|
||||||
// Untagged mode could work with additional filtering but I'm disabling it just to be consistent
|
|
||||||
// this way filtering is available only in the All mode.
|
|
||||||
// Remember though that when you fix the Duplicates + FTS cooperation, enable the filtering in both Duplicates and Untagged modes.
|
|
||||||
duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates;
|
duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates;
|
||||||
|
|
||||||
if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) {
|
if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) {
|
||||||
where_clauses_ << "(artist = '' OR album = '' OR title ='')";
|
where_clauses_ << QStringLiteral("(artist = '' OR album = '' OR title ='')");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionQuery::RemoveSqlOperator(QString &token) {
|
|
||||||
|
|
||||||
QString op = "=";
|
|
||||||
static QRegularExpression rxOp("^(=|<[>=]?|>=?|!=)");
|
|
||||||
QRegularExpressionMatch match = rxOp.match(token);
|
|
||||||
if (match.hasMatch()) {
|
|
||||||
op = match.captured(0);
|
|
||||||
}
|
|
||||||
token.remove(rxOp);
|
|
||||||
|
|
||||||
if (op == "!=") {
|
|
||||||
op = "<>";
|
|
||||||
}
|
|
||||||
|
|
||||||
return op;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
||||||
|
|
||||||
// Ignore 'literal' for IN
|
// Ignore 'literal' for IN
|
||||||
if (op.compare("IN", Qt::CaseInsensitive) == 0) {
|
if (op.compare(QLatin1String("IN"), Qt::CaseInsensitive) == 0) {
|
||||||
QStringList values = value.toStringList();
|
QStringList values = value.toStringList();
|
||||||
QStringList final_values;
|
QStringList final_values;
|
||||||
final_values.reserve(values.count());
|
final_values.reserve(values.count());
|
||||||
for (const QString &single_value : values) {
|
for (const QString &single_value : values) {
|
||||||
final_values.append("?");
|
final_values.append(QStringLiteral("?"));
|
||||||
bound_values_ << single_value;
|
bound_values_ << single_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
where_clauses_ << QString("%1 IN (" + final_values.join(",") + ")").arg(column);
|
where_clauses_ << QStringLiteral("%1 IN (%2)").arg(column, final_values.join(QLatin1Char(',')));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
||||||
@@ -170,7 +79,7 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
|
|||||||
#else
|
#else
|
||||||
if (value.type() == QVariant::Int) {
|
if (value.type() == QVariant::Int) {
|
||||||
#endif
|
#endif
|
||||||
where_clauses_ << QString("%1 %2 %3").arg(column, op, value.toString());
|
where_clauses_ << QStringLiteral("%1 %2 %3").arg(column, op, value.toString());
|
||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
@@ -179,67 +88,26 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
|
|||||||
value.type() == QVariant::String
|
value.type() == QVariant::String
|
||||||
#endif
|
#endif
|
||||||
&& value.toString().isNull()) {
|
&& value.toString().isNull()) {
|
||||||
where_clauses_ << QString("%1 %2 ?").arg(column, op);
|
where_clauses_ << QStringLiteral("%1 %2 ?").arg(column, op);
|
||||||
bound_values_ << QString("");
|
bound_values_ << QLatin1String("");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
where_clauses_ << QString("%1 %2 ?").arg(column, op);
|
where_clauses_ << QStringLiteral("%1 %2 ?").arg(column, op);
|
||||||
bound_values_ << value;
|
bound_values_ << value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionQuery::AddWhereArtist(const QVariant &value) {
|
|
||||||
|
|
||||||
where_clauses_ << QString("((artist = ? AND albumartist = '') OR albumartist = ?)");
|
|
||||||
bound_values_ << value;
|
|
||||||
bound_values_ << value;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionQuery::AddWhereRating(const QVariant &value, const QString &op) {
|
|
||||||
|
|
||||||
float parsed_rating = Utilities::ParseSearchRating(value.toString());
|
|
||||||
|
|
||||||
// You can't query the database for a float, due to float precision errors,
|
|
||||||
// So we have to use a certain tolerance, so that the searched value is definetly included.
|
|
||||||
const float tolerance = 0.001F;
|
|
||||||
if (op == "<") {
|
|
||||||
AddWhere("rating", parsed_rating-tolerance, "<");
|
|
||||||
}
|
|
||||||
else if (op == ">") {
|
|
||||||
AddWhere("rating", parsed_rating+tolerance, ">");
|
|
||||||
}
|
|
||||||
else if (op == "<=") {
|
|
||||||
AddWhere("rating", parsed_rating+tolerance, "<=");
|
|
||||||
}
|
|
||||||
else if (op == ">=") {
|
|
||||||
AddWhere("rating", parsed_rating-tolerance, ">=");
|
|
||||||
}
|
|
||||||
else if (op == "<>") {
|
|
||||||
where_clauses_ << QString("(rating<? OR rating>?)");
|
|
||||||
bound_values_ << parsed_rating - tolerance;
|
|
||||||
bound_values_ << parsed_rating + tolerance;
|
|
||||||
}
|
|
||||||
else /* (op == "=") */ {
|
|
||||||
AddWhere("rating", parsed_rating+tolerance, "<");
|
|
||||||
AddWhere("rating", parsed_rating-tolerance, ">");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionQuery::AddCompilationRequirement(const bool compilation) {
|
void CollectionQuery::AddCompilationRequirement(const bool compilation) {
|
||||||
// The unary + is added to prevent sqlite from using the index idx_comp_artist.
|
// The unary + is added to prevent sqlite from using the index idx_comp_artist.
|
||||||
// When joining with fts, sqlite 3.8 has a tendency to use this index and thereby nesting the tables in an order which gives very poor performance
|
where_clauses_ << QStringLiteral("+compilation_effective = %1").arg(compilation ? 1 : 0);
|
||||||
|
|
||||||
where_clauses_ << QString("+compilation_effective = %1").arg(compilation ? 1 : 0);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionQuery::GetInnerQuery() const {
|
QString CollectionQuery::GetInnerQuery() const {
|
||||||
return duplicates_only_
|
return duplicates_only_
|
||||||
? QString(" INNER JOIN (select * from duplicated_songs) dsongs "
|
? QStringLiteral(" INNER JOIN (select * from duplicated_songs) dsongs "
|
||||||
"ON (%songs_table.artist = dsongs.dup_artist "
|
"ON (%songs_table.artist = dsongs.dup_artist "
|
||||||
"AND %songs_table.album = dsongs.dup_album "
|
"AND %songs_table.album = dsongs.dup_album "
|
||||||
"AND %songs_table.title = dsongs.dup_title) ")
|
"AND %songs_table.title = dsongs.dup_title) ")
|
||||||
@@ -248,29 +116,20 @@ QString CollectionQuery::GetInnerQuery() const {
|
|||||||
|
|
||||||
bool CollectionQuery::Exec() {
|
bool CollectionQuery::Exec() {
|
||||||
|
|
||||||
QString sql;
|
QString sql = QStringLiteral("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery());
|
||||||
|
|
||||||
if (join_with_fts_) {
|
|
||||||
sql = QString("SELECT %1 FROM %2 INNER JOIN %3 AS fts ON %2.ROWID = fts.ROWID").arg(column_spec_, songs_table_, fts_table_);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sql = QString("SELECT %1 FROM %2 %3").arg(column_spec_, songs_table_, GetInnerQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList where_clauses(where_clauses_);
|
QStringList where_clauses(where_clauses_);
|
||||||
if (!include_unavailable_) {
|
if (!include_unavailable_) {
|
||||||
where_clauses << "unavailable = 0";
|
where_clauses << QStringLiteral("unavailable = 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!where_clauses.isEmpty()) sql += " WHERE " + where_clauses.join(" AND ");
|
if (!where_clauses.isEmpty()) sql += QLatin1String(" WHERE ") + where_clauses.join(QLatin1String(" AND "));
|
||||||
|
|
||||||
if (!order_by_.isEmpty()) sql += " ORDER BY " + order_by_;
|
if (!order_by_.isEmpty()) sql += QLatin1String(" ORDER BY ") + order_by_;
|
||||||
|
|
||||||
if (limit_ != -1) sql += " LIMIT " + QString::number(limit_);
|
if (limit_ != -1) sql += QLatin1String(" LIMIT ") + QString::number(limit_);
|
||||||
|
|
||||||
sql.replace("%songs_table", songs_table_);
|
sql.replace(QLatin1String("%songs_table"), songs_table_);
|
||||||
sql.replace("%fts_table_noprefix", fts_table_.section('.', -1, -1));
|
|
||||||
sql.replace("%fts_table", fts_table_);
|
|
||||||
|
|
||||||
if (!QSqlQuery::prepare(sql)) return false;
|
if (!QSqlQuery::prepare(sql)) return false;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,19 +29,20 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
|
||||||
|
#include "core/sqlquery.h"
|
||||||
|
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
|
|
||||||
class CollectionQuery : public QSqlQuery {
|
class CollectionQuery : public SqlQuery {
|
||||||
public:
|
public:
|
||||||
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||||
|
|
||||||
QVariant Value(const int column) const;
|
QVariant Value(const int column) const;
|
||||||
QVariant value(const int column) const { return Value(column); }
|
QVariant value(const int column) const { return Value(column); }
|
||||||
|
|
||||||
bool Exec();
|
bool Exec();
|
||||||
bool exec() { return QSqlQuery::exec(); }
|
bool exec() { return SqlQuery::exec(); }
|
||||||
|
|
||||||
bool Next();
|
bool Next();
|
||||||
|
|
||||||
@@ -50,7 +51,6 @@ class CollectionQuery : public QSqlQuery {
|
|||||||
QStringList where_clauses() const { return where_clauses_; }
|
QStringList where_clauses() const { return where_clauses_; }
|
||||||
QVariantList bound_values() const { return bound_values_; }
|
QVariantList bound_values() const { return bound_values_; }
|
||||||
bool include_unavailable() const { return include_unavailable_; }
|
bool include_unavailable() const { return include_unavailable_; }
|
||||||
bool join_with_fts() const { return join_with_fts_; }
|
|
||||||
bool duplicates_only() const { return duplicates_only_; }
|
bool duplicates_only() const { return duplicates_only_; }
|
||||||
int limit() const { return limit_; }
|
int limit() const { return limit_; }
|
||||||
|
|
||||||
@@ -62,14 +62,9 @@ class CollectionQuery : public QSqlQuery {
|
|||||||
|
|
||||||
void SetWhereClauses(const QStringList &where_clauses) { where_clauses_ = where_clauses; }
|
void SetWhereClauses(const QStringList &where_clauses) { where_clauses_ = where_clauses; }
|
||||||
|
|
||||||
// Removes = < > <= >= <> from the beginning of the input string and returns the operator
|
|
||||||
// If the input String has no operator, returns "="
|
|
||||||
QString RemoveSqlOperator(QString &token);
|
|
||||||
// Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator.
|
// Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator.
|
||||||
// Please note that IN operator expects a QStringList as value.
|
// Please note that IN operator expects a QStringList as value.
|
||||||
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");
|
void AddWhere(const QString &column, const QVariant &value, const QString &op = QStringLiteral("="));
|
||||||
void AddWhereArtist(const QVariant &value);
|
|
||||||
void AddWhereRating(const QVariant &value, const QString &op = "=");
|
|
||||||
|
|
||||||
void SetBoundValues(const QVariantList &bound_values) { bound_values_ = bound_values; }
|
void SetBoundValues(const QVariantList &bound_values) { bound_values_ = bound_values; }
|
||||||
void SetDuplicatesOnly(const bool duplicates_only) { duplicates_only_ = duplicates_only; }
|
void SetDuplicatesOnly(const bool duplicates_only) { duplicates_only_ = duplicates_only; }
|
||||||
@@ -82,7 +77,6 @@ class CollectionQuery : public QSqlQuery {
|
|||||||
|
|
||||||
QSqlDatabase db_;
|
QSqlDatabase db_;
|
||||||
QString songs_table_;
|
QString songs_table_;
|
||||||
QString fts_table_;
|
|
||||||
|
|
||||||
QString column_spec_;
|
QString column_spec_;
|
||||||
QString order_by_;
|
QString order_by_;
|
||||||
@@ -90,7 +84,6 @@ class CollectionQuery : public QSqlQuery {
|
|||||||
QVariantList bound_values_;
|
QVariantList bound_values_;
|
||||||
|
|
||||||
bool include_unavailable_;
|
bool include_unavailable_;
|
||||||
bool join_with_fts_;
|
|
||||||
bool duplicates_only_;
|
bool duplicates_only_;
|
||||||
int limit_;
|
int limit_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,63 +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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef COLLECTIONQUERYOPTIONS_H
|
|
||||||
#define COLLECTIONQUERYOPTIONS_H
|
|
||||||
|
|
||||||
#include <QList>
|
|
||||||
#include <QVariant>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
class CollectionQueryOptions {
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit CollectionQueryOptions();
|
|
||||||
|
|
||||||
struct Where {
|
|
||||||
explicit Where(const QString &_column = QString(), const QVariant &_value = QString(), const QString &_op = QString()) : column(_column), value(_value), op(_op) {}
|
|
||||||
QString column;
|
|
||||||
QVariant value;
|
|
||||||
QString op;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class CompilationRequirement {
|
|
||||||
None,
|
|
||||||
On,
|
|
||||||
Off
|
|
||||||
};
|
|
||||||
|
|
||||||
QString column_spec() const { return column_spec_; }
|
|
||||||
CompilationRequirement compilation_requirement() const { return compilation_requirement_; }
|
|
||||||
bool query_have_compilations() const { return query_have_compilations_; }
|
|
||||||
|
|
||||||
void set_column_spec(const QString &column_spec) { column_spec_ = column_spec; }
|
|
||||||
void set_compilation_requirement(const CompilationRequirement compilation_requirement) { compilation_requirement_ = compilation_requirement; }
|
|
||||||
void set_query_have_compilations(const bool query_have_compilations) { query_have_compilations_ = query_have_compilations; }
|
|
||||||
|
|
||||||
QList<Where> where_clauses() const { return where_clauses_; }
|
|
||||||
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString column_spec_;
|
|
||||||
CompilationRequirement compilation_requirement_;
|
|
||||||
bool query_have_compilations_;
|
|
||||||
QList<Where> where_clauses_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // COLLECTIONQUERYOPTIONS_H
|
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
#include "core/mimedata.h"
|
#include "core/mimedata.h"
|
||||||
#include "core/musicstorage.h"
|
#include "core/musicstorage.h"
|
||||||
#include "core/deletefiles.h"
|
#include "core/deletefiles.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "utilities/filemanagerutils.h"
|
#include "utilities/filemanagerutils.h"
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
@@ -80,7 +82,7 @@ CollectionView::CollectionView(QWidget *parent)
|
|||||||
total_song_count_(-1),
|
total_song_count_(-1),
|
||||||
total_artist_count_(-1),
|
total_artist_count_(-1),
|
||||||
total_album_count_(-1),
|
total_album_count_(-1),
|
||||||
nomusic_(":/pictures/nomusic.png"),
|
nomusic_(QStringLiteral(":/pictures/nomusic.png")),
|
||||||
context_menu_(nullptr),
|
context_menu_(nullptr),
|
||||||
action_load_(nullptr),
|
action_load_(nullptr),
|
||||||
action_add_to_playlist_(nullptr),
|
action_add_to_playlist_(nullptr),
|
||||||
@@ -88,6 +90,7 @@ CollectionView::CollectionView(QWidget *parent)
|
|||||||
action_add_to_playlist_enqueue_next_(nullptr),
|
action_add_to_playlist_enqueue_next_(nullptr),
|
||||||
action_open_in_new_playlist_(nullptr),
|
action_open_in_new_playlist_(nullptr),
|
||||||
action_organize_(nullptr),
|
action_organize_(nullptr),
|
||||||
|
action_search_for_this_(nullptr),
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
action_copy_to_device_(nullptr),
|
action_copy_to_device_(nullptr),
|
||||||
#endif
|
#endif
|
||||||
@@ -109,7 +112,7 @@ CollectionView::CollectionView(QWidget *parent)
|
|||||||
setDragDropMode(QAbstractItemView::DragOnly);
|
setDragDropMode(QAbstractItemView::DragOnly);
|
||||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
|
||||||
setStyleSheet("QTreeView::item{padding-top:1px;}");
|
setStyleSheet(QStringLiteral("QTreeView::item{padding-top:1px;}"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,9 +120,14 @@ CollectionView::~CollectionView() = default;
|
|||||||
|
|
||||||
void CollectionView::SaveFocus() {
|
void CollectionView::SaveFocus() {
|
||||||
|
|
||||||
QModelIndex current = currentIndex();
|
const QModelIndex current = currentIndex();
|
||||||
QVariant type = model()->data(current, CollectionModel::Role_Type);
|
const QVariant role_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)) {
|
if (!role_type.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||||
|
if (item_type != CollectionItem::Type::Song && item_type != CollectionItem::Type::Container && item_type != CollectionItem::Type::Divider) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,8 +135,8 @@ void CollectionView::SaveFocus() {
|
|||||||
last_selected_song_ = Song();
|
last_selected_song_ = Song();
|
||||||
last_selected_container_ = QString();
|
last_selected_container_ = QString();
|
||||||
|
|
||||||
switch (type.toInt()) {
|
switch (item_type) {
|
||||||
case CollectionItem::Type_Song: {
|
case CollectionItem::Type::Song:{
|
||||||
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
||||||
SongList songs = app_->collection_model()->GetChildSongs(index);
|
SongList songs = app_->collection_model()->GetChildSongs(index);
|
||||||
if (!songs.isEmpty()) {
|
if (!songs.isEmpty()) {
|
||||||
@@ -137,8 +145,8 @@ void CollectionView::SaveFocus() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case CollectionItem::Type_Container:
|
case CollectionItem::Type::Container:
|
||||||
case CollectionItem::Type_Divider: {
|
case CollectionItem::Type::Divider:{
|
||||||
QString text = model()->data(current, CollectionModel::Role_SortText).toString();
|
QString text = model()->data(current, CollectionModel::Role_SortText).toString();
|
||||||
last_selected_container_ = text;
|
last_selected_container_ = text;
|
||||||
break;
|
break;
|
||||||
@@ -154,9 +162,14 @@ void CollectionView::SaveFocus() {
|
|||||||
|
|
||||||
void CollectionView::SaveContainerPath(const QModelIndex &child) {
|
void CollectionView::SaveContainerPath(const QModelIndex &child) {
|
||||||
|
|
||||||
QModelIndex current = model()->parent(child);
|
const QModelIndex current = model()->parent(child);
|
||||||
QVariant type = model()->data(current, CollectionModel::Role_Type);
|
const QVariant role_type = model()->data(current, CollectionModel::Role_Type);
|
||||||
if (!type.isValid() || (type.toInt() != CollectionItem::Type_Container && type.toInt() != CollectionItem::Type_Divider)) {
|
if (!role_type.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||||
|
if (item_type != CollectionItem::Type::Container && item_type != CollectionItem::Type::Divider) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,12 +193,17 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
|
|||||||
if (model()->canFetchMore(parent)) {
|
if (model()->canFetchMore(parent)) {
|
||||||
model()->fetchMore(parent);
|
model()->fetchMore(parent);
|
||||||
}
|
}
|
||||||
int rows = model()->rowCount(parent);
|
const int rows = model()->rowCount(parent);
|
||||||
for (int i = 0; i < rows; i++) {
|
for (int i = 0; i < rows; i++) {
|
||||||
QModelIndex current = model()->index(i, 0, parent);
|
QModelIndex current = model()->index(i, 0, parent);
|
||||||
QVariant type = model()->data(current, CollectionModel::Role_Type);
|
const QVariant role_type = model()->data(current, CollectionModel::Role_Type);
|
||||||
switch (type.toInt()) {
|
if (!role_type.isValid()) return false;
|
||||||
case CollectionItem::Type_Song:
|
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||||
|
switch (item_type) {
|
||||||
|
case CollectionItem::Type::Root:
|
||||||
|
case CollectionItem::Type::LoadingIndicator:
|
||||||
|
break;
|
||||||
|
case CollectionItem::Type::Song:
|
||||||
if (!last_selected_song_.url().isEmpty()) {
|
if (!last_selected_song_.url().isEmpty()) {
|
||||||
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
||||||
const SongList songs = app_->collection_model()->GetChildSongs(index);
|
const SongList songs = app_->collection_model()->GetChildSongs(index);
|
||||||
@@ -196,8 +214,8 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CollectionItem::Type_Container:
|
case CollectionItem::Type::Container:
|
||||||
case CollectionItem::Type_Divider: {
|
case CollectionItem::Type::Divider:{
|
||||||
QString text = model()->data(current, CollectionModel::Role_SortText).toString();
|
QString text = model()->data(current, CollectionModel::Role_SortText).toString();
|
||||||
if (!last_selected_container_.isEmpty() && last_selected_container_ == text) {
|
if (!last_selected_container_.isEmpty() && last_selected_container_ == text) {
|
||||||
expand(current);
|
expand(current);
|
||||||
@@ -224,18 +242,10 @@ bool CollectionView::RestoreLevelFocus(const QModelIndex &parent) {
|
|||||||
|
|
||||||
void CollectionView::ReloadSettings() {
|
void CollectionView::ReloadSettings() {
|
||||||
|
|
||||||
QSettings settings;
|
Settings settings;
|
||||||
|
|
||||||
settings.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
settings.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
SetAutoOpen(settings.value("auto_open", false).toBool());
|
SetAutoOpen(settings.value("auto_open", false).toBool());
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
delete_files_ = settings.value("delete_files", false).toBool();
|
delete_files_ = settings.value("delete_files", false).toBool();
|
||||||
|
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -343,29 +353,51 @@ 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;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoExpandingTreeView::keyPressEvent(e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
|
|
||||||
if (!context_menu_) {
|
if (!context_menu_) {
|
||||||
context_menu_ = new QMenu(this);
|
context_menu_ = new QMenu(this);
|
||||||
action_add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, &CollectionView::AddToPlaylist);
|
action_add_to_playlist_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-start")), tr("Append to current playlist"), this, &CollectionView::AddToPlaylist);
|
||||||
action_load_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, &CollectionView::Load);
|
action_load_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("media-playback-start")), tr("Replace current playlist"), this, &CollectionView::Load);
|
||||||
action_open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, &CollectionView::OpenInNewPlaylist);
|
action_open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("document-new")), tr("Open in new playlist"), this, &CollectionView::OpenInNewPlaylist);
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
context_menu_->addSeparator();
|
||||||
action_add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, &CollectionView::AddToPlaylistEnqueue);
|
action_add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("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);
|
action_add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("go-next")), tr("Queue to play next"), this, &CollectionView::AddToPlaylistEnqueueNext);
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
context_menu_->addSeparator();
|
||||||
action_organize_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, &CollectionView::Organize);
|
|
||||||
|
action_search_for_this_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-find")), tr("Search for this"), this, &CollectionView::SearchForThis);
|
||||||
|
|
||||||
|
context_menu_->addSeparator();
|
||||||
|
action_organize_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-copy")), tr("Organize files..."), this, &CollectionView::Organize);
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
action_copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, &CollectionView::CopyToDevice);
|
action_copy_to_device_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("device")), tr("Copy to device..."), this, &CollectionView::CopyToDevice);
|
||||||
#endif
|
#endif
|
||||||
action_delete_files_ = context_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, &CollectionView::Delete);
|
action_delete_files_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-delete")), tr("Delete from disk..."), this, &CollectionView::Delete);
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
context_menu_->addSeparator();
|
||||||
action_edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, &CollectionView::EditTracks);
|
action_edit_track_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-rename")), tr("Edit track information..."), this, &CollectionView::EditTracks);
|
||||||
action_edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, &CollectionView::EditTracks);
|
action_edit_tracks_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("edit-rename")), tr("Edit tracks information..."), this, &CollectionView::EditTracks);
|
||||||
action_show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, &CollectionView::ShowInBrowser);
|
action_show_in_browser_ = context_menu_->addAction(IconLoader::Load(QStringLiteral("document-open-folder")), tr("Show in file browser..."), this, &CollectionView::ShowInBrowser);
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
context_menu_->addSeparator();
|
||||||
|
|
||||||
@@ -391,7 +423,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
|
|
||||||
context_menu_index_ = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(context_menu_index_);
|
context_menu_index_ = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(context_menu_index_);
|
||||||
|
|
||||||
QModelIndexList selected_indexes = qobject_cast<QSortFilterProxyModel*>(model())->mapSelectionToSource(selectionModel()->selection()).indexes();
|
const QModelIndexList selected_indexes = qobject_cast<QSortFilterProxyModel*>(model())->mapSelectionToSource(selectionModel()->selection()).indexes();
|
||||||
|
|
||||||
int regular_elements = 0;
|
int regular_elements = 0;
|
||||||
int regular_editable = 0;
|
int regular_editable = 0;
|
||||||
@@ -462,7 +494,8 @@ void CollectionView::SetShowInVarious(const bool on) {
|
|||||||
// We put through "Various Artists" changes one album at a time,
|
// We put through "Various Artists" changes one album at a time,
|
||||||
// to make sure the old album node gets removed (due to all children removed), before the new one gets added
|
// to make sure the old album node gets removed (due to all children removed), before the new one gets added
|
||||||
QMultiMap<QString, QString> albums;
|
QMultiMap<QString, QString> albums;
|
||||||
for (const Song &song : GetSelectedSongs()) {
|
const SongList songs = GetSelectedSongs();
|
||||||
|
for (const Song &song : songs) {
|
||||||
if (albums.find(song.album(), song.artist()) == albums.end())
|
if (albums.find(song.album(), song.artist()) == albums.end())
|
||||||
albums.insert(song.album(), song.artist());
|
albums.insert(song.album(), song.artist());
|
||||||
}
|
}
|
||||||
@@ -472,7 +505,7 @@ void CollectionView::SetShowInVarious(const bool on) {
|
|||||||
if (on && albums.keys().count() == 1) {
|
if (on && albums.keys().count() == 1) {
|
||||||
const QStringList albums_list = albums.keys();
|
const QStringList albums_list = albums.keys();
|
||||||
const QString album = albums_list.first();
|
const QString album = albums_list.first();
|
||||||
SongList all_of_album = app_->collection_backend()->GetSongsByAlbum(album);
|
const SongList all_of_album = app_->collection_backend()->GetSongsByAlbum(album);
|
||||||
QSet<QString> other_artists;
|
QSet<QString> other_artists;
|
||||||
for (const Song &s : all_of_album) {
|
for (const Song &s : all_of_album) {
|
||||||
if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) {
|
if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) {
|
||||||
@@ -489,9 +522,9 @@ void CollectionView::SetShowInVarious(const bool on) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
QSet<QString> albums_set = QSet<QString>(albums.keyBegin(), albums.keyEnd());
|
const QSet<QString> albums_set = QSet<QString>(albums.keyBegin(), albums.keyEnd());
|
||||||
#else
|
#else
|
||||||
QSet<QString> albums_set = QSet<QString>::fromList(albums.keys());
|
const QSet<QString> albums_set = QSet<QString>::fromList(albums.keys());
|
||||||
#endif
|
#endif
|
||||||
for (const QString &album : albums_set) {
|
for (const QString &album : albums_set) {
|
||||||
app_->collection_backend()->ForceCompilation(album, albums.values(album), on);
|
app_->collection_backend()->ForceCompilation(album, albums.values(album), on);
|
||||||
@@ -545,6 +578,102 @@ void CollectionView::OpenInNewPlaylist() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollectionView::SearchForThis() {
|
||||||
|
|
||||||
|
const QModelIndex current = currentIndex();
|
||||||
|
const QVariant role_type = model()->data(current, CollectionModel::Role_Type);
|
||||||
|
if (!role_type.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||||
|
if (item_type != CollectionItem::Type::Song && item_type != CollectionItem::Type::Container && item_type != CollectionItem::Type::Divider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QString search;
|
||||||
|
QModelIndex index = qobject_cast<QSortFilterProxyModel*>(model())->mapToSource(current);
|
||||||
|
|
||||||
|
switch (item_type) {
|
||||||
|
case CollectionItem::Type::Song:{
|
||||||
|
SongList songs = app_->collection_model()->GetChildSongs(index);
|
||||||
|
if (!songs.isEmpty()) {
|
||||||
|
last_selected_song_ = songs.last();
|
||||||
|
}
|
||||||
|
search = QStringLiteral("title:\"%1\"").arg(last_selected_song_.title());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CollectionItem::Type::Divider:{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CollectionItem::Type::Container:{
|
||||||
|
CollectionItem *item = app_->collection_model()->IndexToItem(index);
|
||||||
|
const CollectionModel::GroupBy group_by = app_->collection_model()->GetGroupBy()[item->container_level];
|
||||||
|
while (!item->children.isEmpty()) {
|
||||||
|
item = item->children.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (group_by) {
|
||||||
|
case CollectionModel::GroupBy::AlbumArtist:
|
||||||
|
search = QStringLiteral("albumartist:\"%1\"").arg(item->metadata.effective_albumartist());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Artist:
|
||||||
|
search = QStringLiteral("artist:\"%1\"").arg(item->metadata.artist());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Album:
|
||||||
|
case CollectionModel::GroupBy::AlbumDisc:
|
||||||
|
search = QStringLiteral("album:\"%1\"").arg(item->metadata.album());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::YearAlbum:
|
||||||
|
case CollectionModel::GroupBy::YearAlbumDisc:
|
||||||
|
search = QStringLiteral("year:%1 album:\"%2\"").arg(item->metadata.year()).arg(item->metadata.album());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::OriginalYearAlbum:
|
||||||
|
case CollectionModel::GroupBy::OriginalYearAlbumDisc:
|
||||||
|
search = QStringLiteral("year:%1 album:\"%2\"").arg(item->metadata.effective_originalyear()).arg(item->metadata.album());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Year:
|
||||||
|
search = QStringLiteral("year:%1").arg(item->metadata.year());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::OriginalYear:
|
||||||
|
search = QStringLiteral("year:%1").arg(item->metadata.effective_originalyear());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Genre:
|
||||||
|
search = QStringLiteral("genre:\"%1\"").arg(item->metadata.genre());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Composer:
|
||||||
|
search = QStringLiteral("composer:\"%1\"").arg(item->metadata.composer());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Performer:
|
||||||
|
search = QStringLiteral("performer:\"%1\"").arg(item->metadata.performer());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Grouping:
|
||||||
|
search = QStringLiteral("grouping:\"%1\"").arg(item->metadata.grouping());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Samplerate:
|
||||||
|
search = QStringLiteral("samplerate:%1").arg(item->metadata.samplerate());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Bitdepth:
|
||||||
|
search = QStringLiteral("bitdepth:%1").arg(item->metadata.bitdepth());
|
||||||
|
break;
|
||||||
|
case CollectionModel::GroupBy::Bitrate:
|
||||||
|
search = QStringLiteral("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) {
|
void CollectionView::keyboardSearch(const QString &search) {
|
||||||
|
|
||||||
is_in_keyboard_search_ = true;
|
is_in_keyboard_search_ = true;
|
||||||
@@ -631,8 +760,11 @@ void CollectionView::FilterReturnPressed() {
|
|||||||
if (!currentIndex().isValid()) {
|
if (!currentIndex().isValid()) {
|
||||||
// Pick the first thing that isn't a divider
|
// Pick the first thing that isn't a divider
|
||||||
for (int row = 0; row < model()->rowCount(); ++row) {
|
for (int row = 0; row < model()->rowCount(); ++row) {
|
||||||
QModelIndex idx(model()->index(row, 0));
|
const QModelIndex idx = model()->index(row, 0);
|
||||||
if (idx.data(CollectionModel::Role_Type) != CollectionItem::Type_Divider) {
|
const QVariant role_type = idx.data(CollectionModel::Role_Type);
|
||||||
|
if (!role_type.isValid()) continue;
|
||||||
|
const CollectionItem::Type item_type = role_type.value<CollectionItem::Type>();
|
||||||
|
if (item_type != CollectionItem::Type::Divider) {
|
||||||
setCurrentIndex(idx);
|
setCurrentIndex(idx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -646,7 +778,7 @@ void CollectionView::FilterReturnPressed() {
|
|||||||
|
|
||||||
void CollectionView::ShowInBrowser() const {
|
void CollectionView::ShowInBrowser() const {
|
||||||
|
|
||||||
SongList songs = GetSelectedSongs();
|
const SongList songs = GetSelectedSongs();
|
||||||
QList<QUrl> urls;
|
QList<QUrl> urls;
|
||||||
urls.reserve(songs.count());
|
urls.reserve(songs.count());
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
@@ -671,7 +803,7 @@ void CollectionView::Delete() {
|
|||||||
|
|
||||||
if (!delete_files_) return;
|
if (!delete_files_) return;
|
||||||
|
|
||||||
SongList selected_songs = GetSelectedSongs();
|
const SongList selected_songs = GetSelectedSongs();
|
||||||
|
|
||||||
SongList songs;
|
SongList songs;
|
||||||
QStringList files;
|
QStringList files;
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
protected:
|
protected:
|
||||||
// QWidget
|
// QWidget
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||||
|
|
||||||
@@ -102,6 +103,7 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
void AddToPlaylistEnqueue();
|
void AddToPlaylistEnqueue();
|
||||||
void AddToPlaylistEnqueueNext();
|
void AddToPlaylistEnqueueNext();
|
||||||
void OpenInNewPlaylist();
|
void OpenInNewPlaylist();
|
||||||
|
void SearchForThis();
|
||||||
void Organize();
|
void Organize();
|
||||||
void CopyToDevice();
|
void CopyToDevice();
|
||||||
void EditTracks();
|
void EditTracks();
|
||||||
@@ -136,6 +138,8 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
QAction *action_add_to_playlist_enqueue_next_;
|
QAction *action_add_to_playlist_enqueue_next_;
|
||||||
QAction *action_open_in_new_playlist_;
|
QAction *action_open_in_new_playlist_;
|
||||||
QAction *action_organize_;
|
QAction *action_organize_;
|
||||||
|
QAction *action_search_for_this_;
|
||||||
|
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
QAction *action_copy_to_device_;
|
QAction *action_copy_to_device_;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
#include "core/taskmanager.h"
|
#include "core/taskmanager.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "utilities/imageutils.h"
|
#include "utilities/imageutils.h"
|
||||||
#include "utilities/timeconstants.h"
|
#include "utilities/timeconstants.h"
|
||||||
#include "collectiondirectory.h"
|
#include "collectiondirectory.h"
|
||||||
@@ -70,8 +71,8 @@
|
|||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
QStringList CollectionWatcher::sValidImages = QStringList() << "jpg" << "png" << "gif" << "jpeg";
|
QStringList CollectionWatcher::sValidImages = QStringList() << QStringLiteral("jpg") << QStringLiteral("png") << QStringLiteral("gif") << QStringLiteral("jpeg");
|
||||||
QStringList CollectionWatcher::kIgnoredExtensions = QStringList() << "tmp" << "tar" << "gz" << "bz2" << "xz" << "tbz" << "tgz" << "z" << "zip" << "rar";
|
const QStringList CollectionWatcher::kIgnoredExtensions = QStringList() << QStringLiteral("tmp") << QStringLiteral("tar") << QStringLiteral("gz") << QStringLiteral("bz2") << QStringLiteral("xz") << QStringLiteral("tbz") << QStringLiteral("tgz") << QStringLiteral("z") << QStringLiteral("zip") << QStringLiteral("rar");
|
||||||
|
|
||||||
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
@@ -105,7 +106,7 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
|||||||
periodic_scan_timer_->setInterval(86400 * kMsecPerSec);
|
periodic_scan_timer_->setInterval(86400 * kMsecPerSec);
|
||||||
periodic_scan_timer_->setSingleShot(false);
|
periodic_scan_timer_->setSingleShot(false);
|
||||||
|
|
||||||
QStringList image_formats = ImageUtils::SupportedImageFormats();
|
const QStringList image_formats = ImageUtils::SupportedImageFormats();
|
||||||
for (const QString &format : image_formats) {
|
for (const QString &format : image_formats) {
|
||||||
if (!sValidImages.contains(format)) {
|
if (!sValidImages.contains(format)) {
|
||||||
sValidImages.append(format);
|
sValidImages.append(format);
|
||||||
@@ -150,11 +151,11 @@ void CollectionWatcher::ReloadSettingsAsync() {
|
|||||||
void CollectionWatcher::ReloadSettings() {
|
void CollectionWatcher::ReloadSettings() {
|
||||||
|
|
||||||
const bool was_monitoring_before = monitor_;
|
const bool was_monitoring_before = monitor_;
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
||||||
monitor_ = s.value("monitor", true).toBool();
|
monitor_ = s.value("monitor", true).toBool();
|
||||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
const QStringList filters = s.value("cover_art_patterns", QStringList() << QStringLiteral("front") << QStringLiteral("cover")).toStringList();
|
||||||
if (source_ == Song::Source::Collection) {
|
if (source_ == Song::Source::Collection) {
|
||||||
song_tracking_ = s.value("song_tracking", false).toBool();
|
song_tracking_ = s.value("song_tracking", false).toBool();
|
||||||
song_ebur128_loudness_analysis_ = s.value("song_ebur128_loudness_analysis", false).toBool();
|
song_ebur128_loudness_analysis_ = s.value("song_ebur128_loudness_analysis", false).toBool();
|
||||||
@@ -182,7 +183,7 @@ void CollectionWatcher::ReloadSettings() {
|
|||||||
else if (monitor_ && !was_monitoring_before) {
|
else if (monitor_ && !was_monitoring_before) {
|
||||||
// Add all directories to all QFileSystemWatchers again
|
// Add all directories to all QFileSystemWatchers again
|
||||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
CollectionSubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
const CollectionSubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
||||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
AddWatch(dir, subdir.path);
|
AddWatch(dir, subdir.path);
|
||||||
}
|
}
|
||||||
@@ -192,7 +193,7 @@ void CollectionWatcher::ReloadSettings() {
|
|||||||
if (monitor_ && scan_on_startup_ && mark_songs_unavailable_ && !periodic_scan_timer_->isActive()) {
|
if (monitor_ && scan_on_startup_ && mark_songs_unavailable_ && !periodic_scan_timer_->isActive()) {
|
||||||
periodic_scan_timer_->start();
|
periodic_scan_timer_->start();
|
||||||
}
|
}
|
||||||
else if (!mark_songs_unavailable_ && periodic_scan_timer_->isActive()) {
|
else if ((!monitor_ || !scan_on_startup_ || !mark_songs_unavailable_) && periodic_scan_timer_->isActive()) {
|
||||||
periodic_scan_timer_->stop();
|
periodic_scan_timer_->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +288,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
|||||||
touched_subdirs.clear();
|
touched_subdirs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const CollectionSubdirectory &subdir : deleted_subdirs) {
|
for (const CollectionSubdirectory &subdir : std::as_const(deleted_subdirs)) {
|
||||||
if (watcher_->watched_dirs_.contains(dir_)) {
|
if (watcher_->watched_dirs_.contains(dir_)) {
|
||||||
watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir);
|
watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir);
|
||||||
}
|
}
|
||||||
@@ -296,7 +297,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
|||||||
|
|
||||||
if (watcher_->monitor_) {
|
if (watcher_->monitor_) {
|
||||||
// Watch the new subdirectories
|
// Watch the new subdirectories
|
||||||
for (const CollectionSubdirectory &subdir : new_subdirs) {
|
for (const CollectionSubdirectory &subdir : std::as_const(new_subdirs)) {
|
||||||
if (watcher_->watched_dirs_.contains(dir_)) {
|
if (watcher_->watched_dirs_.contains(dir_)) {
|
||||||
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
|
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
|
||||||
}
|
}
|
||||||
@@ -316,7 +317,7 @@ SongList CollectionWatcher::ScanTransaction::FindSongsInSubdirectory(const QStri
|
|||||||
if (cached_songs_dirty_) {
|
if (cached_songs_dirty_) {
|
||||||
const SongList songs = watcher_->backend_->FindSongsInDirectory(dir_);
|
const SongList songs = watcher_->backend_->FindSongsInDirectory(dir_);
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
const QString p = song.url().toLocalFile().section('/', 0, -2);
|
const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
|
||||||
cached_songs_.insert(p, song);
|
cached_songs_.insert(p, song);
|
||||||
}
|
}
|
||||||
cached_songs_dirty_ = false;
|
cached_songs_dirty_ = false;
|
||||||
@@ -325,7 +326,8 @@ SongList CollectionWatcher::ScanTransaction::FindSongsInSubdirectory(const QStri
|
|||||||
if (cached_songs_.contains(path)) {
|
if (cached_songs_.contains(path)) {
|
||||||
return cached_songs_.values(path);
|
return cached_songs_.values(path);
|
||||||
}
|
}
|
||||||
else return SongList();
|
|
||||||
|
return SongList();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +336,7 @@ bool CollectionWatcher::ScanTransaction::HasSongsWithMissingFingerprint(const QS
|
|||||||
if (cached_songs_missing_fingerprint_dirty_) {
|
if (cached_songs_missing_fingerprint_dirty_) {
|
||||||
const SongList songs = watcher_->backend_->SongsWithMissingFingerprint(dir_);
|
const SongList songs = watcher_->backend_->SongsWithMissingFingerprint(dir_);
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
const QString p = song.url().toLocalFile().section('/', 0, -2);
|
const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
|
||||||
cached_songs_missing_fingerprint_.insert(p, song);
|
cached_songs_missing_fingerprint_.insert(p, song);
|
||||||
}
|
}
|
||||||
cached_songs_missing_fingerprint_dirty_ = false;
|
cached_songs_missing_fingerprint_dirty_ = false;
|
||||||
@@ -349,7 +351,7 @@ bool CollectionWatcher::ScanTransaction::HasSongsWithMissingLoudnessCharacterist
|
|||||||
if (cached_songs_missing_loudness_characteristics_dirty_) {
|
if (cached_songs_missing_loudness_characteristics_dirty_) {
|
||||||
const SongList songs = watcher_->backend_->SongsWithMissingLoudnessCharacteristics(dir_);
|
const SongList songs = watcher_->backend_->SongsWithMissingLoudnessCharacteristics(dir_);
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
const QString p = song.url().toLocalFile().section('/', 0, -2);
|
const QString p = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
|
||||||
cached_songs_missing_loudness_characteristics_.insert(p, song);
|
cached_songs_missing_loudness_characteristics_.insert(p, song);
|
||||||
}
|
}
|
||||||
cached_songs_missing_loudness_characteristics_dirty_ = false;
|
cached_songs_missing_loudness_characteristics_dirty_ = false;
|
||||||
@@ -383,7 +385,7 @@ CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdi
|
|||||||
}
|
}
|
||||||
|
|
||||||
CollectionSubdirectoryList ret;
|
CollectionSubdirectoryList ret;
|
||||||
for (const CollectionSubdirectory &subdir : known_subdirs_) {
|
for (const CollectionSubdirectory &subdir : std::as_const(known_subdirs_)) {
|
||||||
if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) {
|
if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) {
|
||||||
ret << subdir;
|
ret << subdir;
|
||||||
}
|
}
|
||||||
@@ -416,9 +418,15 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
|
|||||||
transaction.SetKnownSubdirs(subdirs);
|
transaction.SetKnownSubdirs(subdirs);
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
ScanSubdirectory(dir.path, CollectionSubdirectory(), files_count, &transaction);
|
ScanSubdirectory(dir.path, CollectionSubdirectory(), files_count, &transaction);
|
||||||
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (monitor_) {
|
||||||
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
|
AddWatch(dir, subdir.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (scan_on_startup_) {
|
||||||
// We can do an incremental scan - looking at the mtimes of each subdirectory and only rescan if the directory has changed.
|
// We can do an incremental scan - looking at the mtimes of each subdirectory and only rescan if the directory has changed.
|
||||||
ScanTransaction transaction(this, dir.id, true, false, mark_songs_unavailable_);
|
ScanTransaction transaction(this, dir.id, true, false, mark_songs_unavailable_);
|
||||||
QMap<QString, quint64> subdir_files_count;
|
QMap<QString, quint64> subdir_files_count;
|
||||||
@@ -427,14 +435,12 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
|
|||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||||
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
}
|
||||||
|
if (!stop_requested_ && !abort_requested_) {
|
||||||
if (monitor_) AddWatch(dir, subdir.path);
|
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit CompilationsNeedUpdating();
|
emit CompilationsNeedUpdating();
|
||||||
@@ -480,7 +486,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
|
|
||||||
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore.
|
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore.
|
||||||
// If one has been removed, "rescan" it to get the deleted songs
|
// If one has been removed, "rescan" it to get the deleted songs
|
||||||
CollectionSubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
|
const CollectionSubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
|
||||||
for (const CollectionSubdirectory &prev_subdir : previous_subdirs) {
|
for (const CollectionSubdirectory &prev_subdir : previous_subdirs) {
|
||||||
if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) {
|
if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) {
|
||||||
ScanSubdirectory(prev_subdir.path, prev_subdir, 0, t, true);
|
ScanSubdirectory(prev_subdir.path, prev_subdir, 0, t, true);
|
||||||
@@ -510,7 +516,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
else {
|
else {
|
||||||
QString ext_part(ExtensionPart(child));
|
QString ext_part(ExtensionPart(child));
|
||||||
QString dir_part(DirectoryPart(child));
|
QString dir_part(DirectoryPart(child));
|
||||||
if (kIgnoredExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == "qt_temp") {
|
if (kIgnoredExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == QLatin1String("qt_temp")) {
|
||||||
t->AddToProgress(1);
|
t->AddToProgress(1);
|
||||||
}
|
}
|
||||||
else if (sValidImages.contains(ext_part)) {
|
else if (sValidImages.contains(ext_part)) {
|
||||||
@@ -612,7 +618,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
Chromaprinter chromaprinter(file);
|
Chromaprinter chromaprinter(file);
|
||||||
fingerprint = chromaprinter.CreateFingerprint();
|
fingerprint = chromaprinter.CreateFingerprint();
|
||||||
if (fingerprint.isEmpty()) {
|
if (fingerprint.isEmpty()) {
|
||||||
fingerprint = "NONE";
|
fingerprint = QLatin1String("NONE");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -639,11 +645,11 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
Chromaprinter chromaprinter(file);
|
Chromaprinter chromaprinter(file);
|
||||||
fingerprint = chromaprinter.CreateFingerprint();
|
fingerprint = chromaprinter.CreateFingerprint();
|
||||||
if (fingerprint.isEmpty()) {
|
if (fingerprint.isEmpty()) {
|
||||||
fingerprint = "NONE";
|
fingerprint = QLatin1String("NONE");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (song_tracking_ && !fingerprint.isEmpty() && fingerprint != "NONE" && FindSongsByFingerprint(file, fingerprint, &matching_songs)) {
|
if (song_tracking_ && !fingerprint.isEmpty() && fingerprint != QLatin1String("NONE") && FindSongsByFingerprint(file, fingerprint, &matching_songs)) {
|
||||||
|
|
||||||
// The song is in the database and still on disk.
|
// The song is in the database and still on disk.
|
||||||
// Check the mtime to see if it's been changed since it was added.
|
// Check the mtime to see if it's been changed since it was added.
|
||||||
@@ -657,7 +663,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
|
|
||||||
// Make sure the songs aren't deleted, as they still exist elsewhere with a different file path.
|
// Make sure the songs aren't deleted, as they still exist elsewhere with a different file path.
|
||||||
bool matching_songs_has_cue = false;
|
bool matching_songs_has_cue = false;
|
||||||
for (const Song &matching_song : matching_songs) {
|
for (const Song &matching_song : std::as_const(matching_songs)) {
|
||||||
QString matching_filename = matching_song.url().toLocalFile();
|
QString matching_filename = matching_song.url().toLocalFile();
|
||||||
if (!t->files_changed_path_.contains(matching_filename)) {
|
if (!t->files_changed_path_.contains(matching_filename)) {
|
||||||
t->files_changed_path_ << matching_filename;
|
t->files_changed_path_ << matching_filename;
|
||||||
@@ -690,7 +696,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
}
|
}
|
||||||
else { // The song is on disk but not in the DB
|
else { // The song is on disk but not in the DB
|
||||||
|
|
||||||
SongList songs = ScanNewFile(file, path, fingerprint, new_cue, &cues_processed);
|
const SongList songs = ScanNewFile(file, path, fingerprint, new_cue, &cues_processed);
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
t->AddToProgress(1);
|
t->AddToProgress(1);
|
||||||
continue;
|
continue;
|
||||||
@@ -712,7 +718,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Look for deleted songs
|
// Look for deleted songs
|
||||||
for (const Song &song : songs_in_db) {
|
for (const Song &song : std::as_const(songs_in_db)) {
|
||||||
QString file = song.url().toLocalFile();
|
QString file = song.url().toLocalFile();
|
||||||
if (!song.unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
|
if (!song.unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
|
||||||
qLog(Debug) << "Song deleted from disk:" << file;
|
qLog(Debug) << "Song deleted from disk:" << file;
|
||||||
@@ -738,7 +744,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recurse into the new subdirs that we found
|
// Recurse into the new subdirs that we found
|
||||||
for (const CollectionSubdirectory &my_new_subdir : my_new_subdirs) {
|
for (const CollectionSubdirectory &my_new_subdir : std::as_const(my_new_subdirs)) {
|
||||||
if (stop_requested_ || abort_requested_) return;
|
if (stop_requested_ || abort_requested_) return;
|
||||||
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
|
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
|
||||||
}
|
}
|
||||||
@@ -795,6 +801,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
|||||||
t->deleted_songs << old_cue;
|
t->deleted_songs << old_cue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||||
@@ -815,8 +822,8 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Song song_on_disk(source_);
|
Song song_on_disk(source_);
|
||||||
TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
|
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
|
||||||
if (song_on_disk.is_valid()) {
|
if (result.success() && song_on_disk.is_valid()) {
|
||||||
song_on_disk.set_source(source_);
|
song_on_disk.set_source(source_);
|
||||||
song_on_disk.set_directory_id(t->dir());
|
song_on_disk.set_directory_id(t->dir());
|
||||||
song_on_disk.set_id(matching_song.id());
|
song_on_disk.set_id(matching_song.id());
|
||||||
@@ -868,8 +875,8 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
|||||||
}
|
}
|
||||||
else { // It's a normal media file
|
else { // It's a normal media file
|
||||||
Song song(source_);
|
Song song(source_);
|
||||||
TagReaderClient::Instance()->ReadFileBlocking(file, &song);
|
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song);
|
||||||
if (song.is_valid()) {
|
if (result.success() && song.is_valid()) {
|
||||||
song.set_source(source_);
|
song.set_source(source_);
|
||||||
PerformEBUR128Analysis(song);
|
PerformEBUR128Analysis(song);
|
||||||
song.set_fingerprint(fingerprint);
|
song.set_fingerprint(fingerprint);
|
||||||
@@ -892,50 +899,50 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (matching_song.url() != new_song.url()) {
|
if (matching_song.url() != new_song.url()) {
|
||||||
changes << "file path";
|
changes << QStringLiteral("file path");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (matching_song.fingerprint() != new_song.fingerprint()) {
|
if (matching_song.fingerprint() != new_song.fingerprint()) {
|
||||||
changes << "fingerprint";
|
changes << QStringLiteral("fingerprint");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsMetadataEqual(new_song)) {
|
if (!matching_song.IsMetadataEqual(new_song)) {
|
||||||
changes << "metadata";
|
changes << QStringLiteral("metadata");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsPlayStatisticsEqual(new_song)) {
|
if (!matching_song.IsPlayStatisticsEqual(new_song)) {
|
||||||
changes << "play statistics";
|
changes << QStringLiteral("play statistics");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsRatingEqual(new_song)) {
|
if (!matching_song.IsRatingEqual(new_song)) {
|
||||||
changes << "rating";
|
changes << QStringLiteral("rating");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsArtEqual(new_song)) {
|
if (!matching_song.IsArtEqual(new_song)) {
|
||||||
changes << "album art";
|
changes << QStringLiteral("album art");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsAcoustIdEqual(new_song)) {
|
if (!matching_song.IsAcoustIdEqual(new_song)) {
|
||||||
changes << "acoustid";
|
changes << QStringLiteral("acoustid");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsMusicBrainzEqual(new_song)) {
|
if (!matching_song.IsMusicBrainzEqual(new_song)) {
|
||||||
changes << "musicbrainz";
|
changes << QStringLiteral("musicbrainz");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsEBUR128Equal(new_song)) {
|
if (!matching_song.IsEBUR128Equal(new_song)) {
|
||||||
changes << "ebur128 loudness characteristics";
|
changes << QStringLiteral("ebur128 loudness characteristics");
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (matching_song.mtime() != new_song.mtime()) {
|
if (matching_song.mtime() != new_song.mtime()) {
|
||||||
changes << "mtime";
|
changes << QStringLiteral("mtime");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes.isEmpty()) {
|
if (changes.isEmpty()) {
|
||||||
qLog(Debug) << "Song" << file << "unchanged.";
|
qLog(Debug) << "Song" << file << "unchanged.";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Debug) << "Song" << file << changes.join(", ") << "changed.";
|
qLog(Debug) << "Song" << file << changes.join(QLatin1String(", ")) << "changed.";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -990,7 +997,7 @@ void CollectionWatcher::AddWatch(const CollectionDirectory &dir, const QString &
|
|||||||
|
|
||||||
void CollectionWatcher::RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir) {
|
void CollectionWatcher::RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir) {
|
||||||
|
|
||||||
QStringList subdir_paths = subdir_mapping_.keys(dir);
|
const QStringList subdir_paths = subdir_mapping_.keys(dir);
|
||||||
for (const QString &subdir_path : subdir_paths) {
|
for (const QString &subdir_path : subdir_paths) {
|
||||||
if (subdir_path != subdir.path) continue;
|
if (subdir_path != subdir.path) continue;
|
||||||
fs_watcher_->RemovePath(subdir_path);
|
fs_watcher_->RemovePath(subdir_path);
|
||||||
@@ -1006,7 +1013,7 @@ void CollectionWatcher::RemoveDirectory(const CollectionDirectory &dir) {
|
|||||||
watched_dirs_.remove(dir.id);
|
watched_dirs_.remove(dir.id);
|
||||||
|
|
||||||
// Stop watching the directory's subdirectories
|
// Stop watching the directory's subdirectories
|
||||||
QStringList subdir_paths = subdir_mapping_.keys(dir);
|
const QStringList subdir_paths = subdir_mapping_.keys(dir);
|
||||||
for (const QString &subdir_path : subdir_paths) {
|
for (const QString &subdir_path : subdir_paths) {
|
||||||
fs_watcher_->RemovePath(subdir_path);
|
fs_watcher_->RemovePath(subdir_path);
|
||||||
subdir_mapping_.remove(subdir_path);
|
subdir_mapping_.remove(subdir_path);
|
||||||
@@ -1028,7 +1035,7 @@ bool CollectionWatcher::FindSongsByPath(const SongList &songs, const QString &pa
|
|||||||
|
|
||||||
bool CollectionWatcher::FindSongsByFingerprint(const QString &file, const QString &fingerprint, SongList *out) {
|
bool CollectionWatcher::FindSongsByFingerprint(const QString &file, const QString &fingerprint, SongList *out) {
|
||||||
|
|
||||||
SongList songs = backend_->GetSongsByFingerprint(fingerprint);
|
const SongList songs = backend_->GetSongsByFingerprint(fingerprint);
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
QString filename = song.url().toLocalFile();
|
QString filename = song.url().toLocalFile();
|
||||||
QFileInfo info(filename);
|
QFileInfo info(filename);
|
||||||
@@ -1077,19 +1084,21 @@ void CollectionWatcher::DirectoryChanged(const QString &subdir) {
|
|||||||
|
|
||||||
void CollectionWatcher::RescanPathsNow() {
|
void CollectionWatcher::RescanPathsNow() {
|
||||||
|
|
||||||
QList<int> dirs = rescan_queue_.keys();
|
const QList<int> dirs = rescan_queue_.keys();
|
||||||
for (const int dir : dirs) {
|
for (const int dir : dirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
ScanTransaction transaction(this, dir, false, false, mark_songs_unavailable_);
|
ScanTransaction transaction(this, dir, false, false, mark_songs_unavailable_);
|
||||||
|
|
||||||
|
const QStringList paths = rescan_queue_[dir];
|
||||||
|
|
||||||
QMap<QString, quint64> subdir_files_count;
|
QMap<QString, quint64> subdir_files_count;
|
||||||
for (const QString &path : rescan_queue_[dir]) {
|
for (const QString &path : paths) {
|
||||||
quint64 files_count = FilesCountForPath(&transaction, path);
|
quint64 files_count = FilesCountForPath(&transaction, path);
|
||||||
subdir_files_count[path] = files_count;
|
subdir_files_count[path] = files_count;
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const QString &path : rescan_queue_[dir]) {
|
for (const QString &path : paths) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
CollectionSubdirectory subdir;
|
CollectionSubdirectory subdir;
|
||||||
subdir.directory_id = dir;
|
subdir.directory_id = dir;
|
||||||
@@ -1112,7 +1121,7 @@ QString CollectionWatcher::PickBestArt(const QStringList &art_automatic_list) {
|
|||||||
|
|
||||||
QStringList filtered;
|
QStringList filtered;
|
||||||
|
|
||||||
for (const QString &filter_text : best_art_filters_) {
|
for (const QString &filter_text : std::as_const(best_art_filters_)) {
|
||||||
// The images in the images list are represented by a full path, so we need to isolate just the filename
|
// The images in the images list are represented by a full path, so we need to isolate just the filename
|
||||||
for (const QString &art_automatic : art_automatic_list) {
|
for (const QString &art_automatic : art_automatic_list) {
|
||||||
QFileInfo fileinfo(art_automatic);
|
QFileInfo fileinfo(art_automatic);
|
||||||
@@ -1134,7 +1143,7 @@ QString CollectionWatcher::PickBestArt(const QStringList &art_automatic_list) {
|
|||||||
int biggest_size = 0;
|
int biggest_size = 0;
|
||||||
QString biggest_path;
|
QString biggest_path;
|
||||||
|
|
||||||
for (const QString &path : filtered) {
|
for (const QString &path : std::as_const(filtered)) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
|
||||||
QImage image(path);
|
QImage image(path);
|
||||||
@@ -1197,7 +1206,7 @@ void CollectionWatcher::FullScanAsync() {
|
|||||||
|
|
||||||
void CollectionWatcher::IncrementalScanCheck() {
|
void CollectionWatcher::IncrementalScanCheck() {
|
||||||
|
|
||||||
qint64 duration = QDateTime::currentDateTime().toSecsSinceEpoch() - last_scan_time_;
|
qint64 duration = QDateTime::currentSecsSinceEpoch() - last_scan_time_;
|
||||||
if (duration >= 86400) {
|
if (duration >= 86400) {
|
||||||
qLog(Debug) << "Performing periodic incremental scan.";
|
qLog(Debug) << "Performing periodic incremental scan.";
|
||||||
IncrementalScanNow();
|
IncrementalScanNow();
|
||||||
@@ -1218,7 +1227,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
|||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
|
||||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
||||||
CollectionSubdirectoryList subdirs(transaction.GetAllSubdirs());
|
CollectionSubdirectoryList subdirs = transaction.GetAllSubdirs();
|
||||||
|
|
||||||
if (subdirs.isEmpty()) {
|
if (subdirs.isEmpty()) {
|
||||||
qLog(Debug) << "Collection directory wasn't in subdir list.";
|
qLog(Debug) << "Collection directory wasn't in subdir list.";
|
||||||
@@ -1232,14 +1241,14 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
|||||||
quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
|
|
||||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : std::as_const(subdirs)) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||||
|
|
||||||
emit CompilationsNeedUpdating();
|
emit CompilationsNeedUpdating();
|
||||||
|
|
||||||
@@ -1308,10 +1317,10 @@ void CollectionWatcher::RescanSongs(const SongList &songs) {
|
|||||||
QStringList scanned_paths;
|
QStringList scanned_paths;
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
const QString song_path = song.url().toLocalFile().section('/', 0, -2);
|
const QString song_path = song.url().toLocalFile().section(QLatin1Char('/'), 0, -2);
|
||||||
if (scanned_paths.contains(song_path)) continue;
|
if (scanned_paths.contains(song_path)) continue;
|
||||||
ScanTransaction transaction(this, song.directory_id(), false, true, mark_songs_unavailable_);
|
ScanTransaction transaction(this, song.directory_id(), false, true, mark_songs_unavailable_);
|
||||||
CollectionSubdirectoryList subdirs(transaction.GetAllSubdirs());
|
const CollectionSubdirectoryList subdirs = transaction.GetAllSubdirs();
|
||||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
if (subdir.path != song_path) continue;
|
if (subdir.path != song_path) continue;
|
||||||
|
|||||||
@@ -245,21 +245,21 @@ class CollectionWatcher : public QObject {
|
|||||||
CueParser *cue_parser_;
|
CueParser *cue_parser_;
|
||||||
|
|
||||||
static QStringList sValidImages;
|
static QStringList sValidImages;
|
||||||
static QStringList kIgnoredExtensions;
|
static const QStringList kIgnoredExtensions;
|
||||||
|
|
||||||
qint64 last_scan_time_;
|
qint64 last_scan_time_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline QString CollectionWatcher::NoExtensionPart(const QString &fileName) {
|
inline QString CollectionWatcher::NoExtensionPart(const QString &fileName) {
|
||||||
return fileName.contains('.') ? fileName.section('.', 0, -2) : "";
|
return fileName.contains(QLatin1Char('.')) ? fileName.section(QLatin1Char('.'), 0, -2) : QLatin1String("");
|
||||||
}
|
}
|
||||||
// Thanks Amarok
|
// Thanks Amarok
|
||||||
inline QString CollectionWatcher::ExtensionPart(const QString &fileName) {
|
inline QString CollectionWatcher::ExtensionPart(const QString &fileName) {
|
||||||
return fileName.contains( '.' ) ? fileName.mid( fileName.lastIndexOf('.') + 1 ).toLower() : "";
|
return fileName.contains(QLatin1Char('.')) ? fileName.mid(fileName.lastIndexOf(QLatin1Char('.')) + 1).toLower() : QLatin1String("");
|
||||||
}
|
}
|
||||||
inline QString CollectionWatcher::DirectoryPart(const QString &fileName) {
|
inline QString CollectionWatcher::DirectoryPart(const QString &fileName) {
|
||||||
return fileName.section('/', 0, -2);
|
return fileName.section(QLatin1Char('/'), 0, -2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // COLLECTIONWATCHER_H
|
#endif // COLLECTIONWATCHER_H
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include <QItemSelectionModel>
|
#include <QItemSelectionModel>
|
||||||
@@ -36,6 +38,7 @@
|
|||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "savedgroupingmanager.h"
|
#include "savedgroupingmanager.h"
|
||||||
@@ -56,7 +59,7 @@ SavedGroupingManager::SavedGroupingManager(const QString &saved_groupings_settin
|
|||||||
model_->setHorizontalHeaderItem(2, new QStandardItem(tr("Second Level")));
|
model_->setHorizontalHeaderItem(2, new QStandardItem(tr("Second Level")));
|
||||||
model_->setHorizontalHeaderItem(3, new QStandardItem(tr("Third Level")));
|
model_->setHorizontalHeaderItem(3, new QStandardItem(tr("Third Level")));
|
||||||
ui_->list->setModel(model_);
|
ui_->list->setModel(model_);
|
||||||
ui_->remove->setIcon(IconLoader::Load("edit-delete"));
|
ui_->remove->setIcon(IconLoader::Load(QStringLiteral("edit-delete")));
|
||||||
ui_->remove->setEnabled(false);
|
ui_->remove->setEnabled(false);
|
||||||
|
|
||||||
ui_->remove->setShortcut(QKeySequence::Delete);
|
ui_->remove->setShortcut(QKeySequence::Delete);
|
||||||
@@ -72,80 +75,79 @@ SavedGroupingManager::~SavedGroupingManager() {
|
|||||||
|
|
||||||
QString SavedGroupingManager::GetSavedGroupingsSettingsGroup(const QString &settings_group) {
|
QString SavedGroupingManager::GetSavedGroupingsSettingsGroup(const QString &settings_group) {
|
||||||
|
|
||||||
if (settings_group.isEmpty() || settings_group == CollectionSettingsPage::kSettingsGroup) {
|
if (settings_group.isEmpty() || settings_group == QLatin1String(CollectionSettingsPage::kSettingsGroup)) {
|
||||||
return kSavedGroupingsSettingsGroup;
|
return QLatin1String(kSavedGroupingsSettingsGroup);
|
||||||
}
|
|
||||||
else {
|
|
||||||
return QString(kSavedGroupingsSettingsGroup) + "_" + settings_group;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return QLatin1String(kSavedGroupingsSettingsGroup) + QLatin1Char('_') + settings_group;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g) {
|
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g) {
|
||||||
|
|
||||||
switch (g) {
|
switch (g) {
|
||||||
case CollectionModel::GroupBy::None:
|
case CollectionModel::GroupBy::None:
|
||||||
case CollectionModel::GroupBy::GroupByCount: {
|
case CollectionModel::GroupBy::GroupByCount:{
|
||||||
return tr("None");
|
return tr("None");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::AlbumArtist: {
|
case CollectionModel::GroupBy::AlbumArtist:{
|
||||||
return tr("Album artist");
|
return tr("Album artist");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Artist: {
|
case CollectionModel::GroupBy::Artist:{
|
||||||
return tr("Artist");
|
return tr("Artist");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Album: {
|
case CollectionModel::GroupBy::Album:{
|
||||||
return tr("Album");
|
return tr("Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::AlbumDisc: {
|
case CollectionModel::GroupBy::AlbumDisc:{
|
||||||
return tr("Album - Disc");
|
return tr("Album - Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::YearAlbum: {
|
case CollectionModel::GroupBy::YearAlbum:{
|
||||||
return tr("Year - Album");
|
return tr("Year - Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::YearAlbumDisc: {
|
case CollectionModel::GroupBy::YearAlbumDisc:{
|
||||||
return tr("Year - Album - Disc");
|
return tr("Year - Album - Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::OriginalYearAlbum: {
|
case CollectionModel::GroupBy::OriginalYearAlbum:{
|
||||||
return tr("Original year - Album");
|
return tr("Original year - Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::OriginalYearAlbumDisc: {
|
case CollectionModel::GroupBy::OriginalYearAlbumDisc:{
|
||||||
return tr("Original year - Album - Disc");
|
return tr("Original year - Album - Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Disc: {
|
case CollectionModel::GroupBy::Disc:{
|
||||||
return tr("Disc");
|
return tr("Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Year: {
|
case CollectionModel::GroupBy::Year:{
|
||||||
return tr("Year");
|
return tr("Year");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::OriginalYear: {
|
case CollectionModel::GroupBy::OriginalYear:{
|
||||||
return tr("Original year");
|
return tr("Original year");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Genre: {
|
case CollectionModel::GroupBy::Genre:{
|
||||||
return tr("Genre");
|
return tr("Genre");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Composer: {
|
case CollectionModel::GroupBy::Composer:{
|
||||||
return tr("Composer");
|
return tr("Composer");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Performer: {
|
case CollectionModel::GroupBy::Performer:{
|
||||||
return tr("Performer");
|
return tr("Performer");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Grouping: {
|
case CollectionModel::GroupBy::Grouping:{
|
||||||
return tr("Grouping");
|
return tr("Grouping");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::FileType: {
|
case CollectionModel::GroupBy::FileType:{
|
||||||
return tr("File type");
|
return tr("File type");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Format: {
|
case CollectionModel::GroupBy::Format:{
|
||||||
return tr("Format");
|
return tr("Format");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Samplerate: {
|
case CollectionModel::GroupBy::Samplerate:{
|
||||||
return tr("Sample rate");
|
return tr("Sample rate");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Bitdepth: {
|
case CollectionModel::GroupBy::Bitdepth:{
|
||||||
return tr("Bit depth");
|
return tr("Bit depth");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy::Bitrate: {
|
case CollectionModel::GroupBy::Bitrate:{
|
||||||
return tr("Bitrate");
|
return tr("Bitrate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,13 +159,13 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g)
|
|||||||
void SavedGroupingManager::UpdateModel() {
|
void SavedGroupingManager::UpdateModel() {
|
||||||
|
|
||||||
model_->setRowCount(0); // don't use clear, it deletes headers
|
model_->setRowCount(0); // don't use clear, it deletes headers
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(saved_groupings_settings_group_);
|
s.beginGroup(saved_groupings_settings_group_);
|
||||||
int version = s.value("version").toInt();
|
int version = s.value("version").toInt();
|
||||||
if (version == 1) {
|
if (version == 1) {
|
||||||
QStringList saved = s.childKeys();
|
QStringList saved = s.childKeys();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
if (saved.at(i) == "version") continue;
|
if (saved.at(i) == QLatin1String("version")) continue;
|
||||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||||
CollectionModel::Grouping g;
|
CollectionModel::Grouping g;
|
||||||
@@ -181,7 +183,7 @@ void SavedGroupingManager::UpdateModel() {
|
|||||||
else {
|
else {
|
||||||
QStringList saved = s.childKeys();
|
QStringList saved = s.childKeys();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
if (saved.at(i) == "version") continue;
|
if (saved.at(i) == QLatin1String("version")) continue;
|
||||||
s.remove(saved.at(i));
|
s.remove(saved.at(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,9 +194,10 @@ void SavedGroupingManager::UpdateModel() {
|
|||||||
void SavedGroupingManager::Remove() {
|
void SavedGroupingManager::Remove() {
|
||||||
|
|
||||||
if (ui_->list->selectionModel()->hasSelection()) {
|
if (ui_->list->selectionModel()->hasSelection()) {
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(saved_groupings_settings_group_);
|
s.beginGroup(saved_groupings_settings_group_);
|
||||||
for (const QModelIndex &idx : ui_->list->selectionModel()->selectedRows()) {
|
const QModelIndexList indexes = ui_->list->selectionModel()->selectedRows();
|
||||||
|
for (const QModelIndex &idx : indexes) {
|
||||||
if (idx.isValid()) {
|
if (idx.isValid()) {
|
||||||
qLog(Debug) << "Remove saved grouping: " << model_->item(idx.row(), 0)->text();
|
qLog(Debug) << "Remove saved grouping: " << model_->item(idx.row(), 0)->text();
|
||||||
s.remove(model_->item(idx.row(), 0)->text());
|
s.remove(model_->item(idx.row(), 0)->text());
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
#cmakedefine HAVE_MUSICBRAINZ
|
#cmakedefine HAVE_MUSICBRAINZ
|
||||||
#cmakedefine HAVE_GLOBALSHORTCUTS
|
#cmakedefine HAVE_GLOBALSHORTCUTS
|
||||||
#cmakedefine HAVE_X11_GLOBALSHORTCUTS
|
#cmakedefine HAVE_X11_GLOBALSHORTCUTS
|
||||||
#cmakedefine HAVE_ICU
|
|
||||||
|
|
||||||
#cmakedefine USE_INSTALL_PREFIX
|
#cmakedefine USE_INSTALL_PREFIX
|
||||||
|
|
||||||
@@ -30,6 +29,7 @@
|
|||||||
|
|
||||||
#cmakedefine HAVE_SUBSONIC
|
#cmakedefine HAVE_SUBSONIC
|
||||||
#cmakedefine HAVE_TIDAL
|
#cmakedefine HAVE_TIDAL
|
||||||
|
#cmakedefine HAVE_SPOTIFY
|
||||||
#cmakedefine HAVE_QOBUZ
|
#cmakedefine HAVE_QOBUZ
|
||||||
|
|
||||||
#cmakedefine HAVE_MOODBAR
|
#cmakedefine HAVE_MOODBAR
|
||||||
@@ -37,8 +37,10 @@
|
|||||||
#cmakedefine HAVE_KEYSYMDEF_H
|
#cmakedefine HAVE_KEYSYMDEF_H
|
||||||
#cmakedefine HAVE_XF86KEYSYM_H
|
#cmakedefine HAVE_XF86KEYSYM_H
|
||||||
|
|
||||||
|
#cmakedefine HAVE_TAGLIB
|
||||||
#cmakedefine HAVE_TAGLIB_DSFFILE
|
#cmakedefine HAVE_TAGLIB_DSFFILE
|
||||||
#cmakedefine HAVE_TAGLIB_DSDIFFFILE
|
#cmakedefine HAVE_TAGLIB_DSDIFFFILE
|
||||||
|
#cmakedefine HAVE_TAGPARSER
|
||||||
|
|
||||||
#cmakedefine USE_BUNDLE
|
#cmakedefine USE_BUNDLE
|
||||||
|
|
||||||
@@ -51,9 +53,6 @@
|
|||||||
|
|
||||||
#cmakedefine ENABLE_WIN32_CONSOLE
|
#cmakedefine ENABLE_WIN32_CONSOLE
|
||||||
|
|
||||||
#cmakedefine USE_TAGLIB
|
|
||||||
#cmakedefine USE_TAGPARSER
|
|
||||||
|
|
||||||
#cmakedefine HAVE_QX11APPLICATION
|
#cmakedefine HAVE_QX11APPLICATION
|
||||||
|
|
||||||
#cmakedefine HAVE_EBUR128
|
#cmakedefine HAVE_EBUR128
|
||||||
|
|||||||
@@ -47,7 +47,9 @@
|
|||||||
using std::make_unique;
|
using std::make_unique;
|
||||||
using std::make_shared;
|
using std::make_shared;
|
||||||
|
|
||||||
const int ContextAlbum::kFadeTimeLineMs = 1000;
|
namespace {
|
||||||
|
constexpr int kFadeTimeLineMs = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
ContextAlbum::ContextAlbum(QWidget *parent)
|
ContextAlbum::ContextAlbum(QWidget *parent)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
@@ -56,12 +58,12 @@ ContextAlbum::ContextAlbum(QWidget *parent)
|
|||||||
album_cover_choice_controller_(nullptr),
|
album_cover_choice_controller_(nullptr),
|
||||||
downloading_covers_(false),
|
downloading_covers_(false),
|
||||||
timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)),
|
timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)),
|
||||||
image_strawberry_(":/pictures/strawberry.png"),
|
image_strawberry_(QStringLiteral(":/pictures/strawberry.png")),
|
||||||
image_original_(image_strawberry_),
|
image_original_(image_strawberry_),
|
||||||
pixmap_current_opacity_(1.0),
|
pixmap_current_opacity_(1.0),
|
||||||
desired_height_(width()) {
|
desired_height_(width()) {
|
||||||
|
|
||||||
setObjectName("context-widget-album");
|
setObjectName(QStringLiteral("context-widget-album"));
|
||||||
|
|
||||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
@@ -139,11 +141,7 @@ void ContextAlbum::UpdateWidth(const int new_width) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextAlbum::SetImage(QImage image) {
|
void ContextAlbum::SetImage(const QImage &image) {
|
||||||
|
|
||||||
if (image.isNull()) {
|
|
||||||
image = image_strawberry_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloading_covers_) {
|
if (downloading_covers_) {
|
||||||
downloading_covers_ = false;
|
downloading_covers_ = false;
|
||||||
@@ -154,7 +152,13 @@ void ContextAlbum::SetImage(QImage image) {
|
|||||||
QPixmap pixmap_previous = pixmap_current_;
|
QPixmap pixmap_previous = pixmap_current_;
|
||||||
qreal opacity_previous = pixmap_current_opacity_;
|
qreal opacity_previous = pixmap_current_opacity_;
|
||||||
|
|
||||||
|
if (image.isNull()) {
|
||||||
|
image_original_ = image_strawberry_;
|
||||||
|
}
|
||||||
|
else {
|
||||||
image_original_ = image;
|
image_original_ = image;
|
||||||
|
}
|
||||||
|
|
||||||
pixmap_current_opacity_ = 0.0;
|
pixmap_current_opacity_ = 0.0;
|
||||||
ScaleCover();
|
ScaleCover();
|
||||||
|
|
||||||
@@ -231,6 +235,7 @@ void ContextAlbum::FadePreviousCover(SharedPtr<PreviousCover> previous_cover) {
|
|||||||
|
|
||||||
void ContextAlbum::FadePreviousCoverFinished(SharedPtr<PreviousCover> previous_cover) {
|
void ContextAlbum::FadePreviousCoverFinished(SharedPtr<PreviousCover> previous_cover) {
|
||||||
|
|
||||||
|
previous_cover->timeline.reset();
|
||||||
previous_covers_.removeAll(previous_cover);
|
previous_covers_.removeAll(previous_cover);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -266,7 +271,7 @@ void ContextAlbum::SearchCoverInProgress() {
|
|||||||
downloading_covers_ = true;
|
downloading_covers_ = true;
|
||||||
|
|
||||||
// Show a spinner animation
|
// Show a spinner animation
|
||||||
spinner_animation_ = make_unique<QMovie>(":/pictures/spinner.gif", QByteArray(), this);
|
spinner_animation_ = make_unique<QMovie>(QStringLiteral(":/pictures/spinner.gif"), QByteArray(), this);
|
||||||
QObject::connect(&*spinner_animation_, &QMovie::updated, this, &ContextAlbum::Update);
|
QObject::connect(&*spinner_animation_, &QMovie::updated, this, &ContextAlbum::Update);
|
||||||
spinner_animation_->start();
|
spinner_animation_->start();
|
||||||
update();
|
update();
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class ContextAlbum : public QWidget {
|
|||||||
explicit ContextAlbum(QWidget *parent = nullptr);
|
explicit ContextAlbum(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void Init(ContextView *context_view, AlbumCoverChoiceController *album_cover_choice_controller);
|
void Init(ContextView *context_view, AlbumCoverChoiceController *album_cover_choice_controller);
|
||||||
void SetImage(QImage image = QImage());
|
void SetImage(const QImage &image = QImage());
|
||||||
void UpdateWidth(const int width);
|
void UpdateWidth(const int width);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -63,7 +63,7 @@ class ContextAlbum : public QWidget {
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
struct PreviousCover {
|
struct PreviousCover {
|
||||||
PreviousCover() : opacity(0.0) {}
|
explicit PreviousCover() : opacity(0.0) {}
|
||||||
QImage image;
|
QImage image;
|
||||||
QPixmap pixmap;
|
QPixmap pixmap;
|
||||||
qreal opacity;
|
qreal opacity;
|
||||||
@@ -77,7 +77,6 @@ class ContextAlbum : public QWidget {
|
|||||||
void DrawPreviousCovers(QPainter *p);
|
void DrawPreviousCovers(QPainter *p);
|
||||||
void ScaleCover();
|
void ScaleCover();
|
||||||
void ScalePreviousCovers();
|
void ScalePreviousCovers();
|
||||||
void GetCoverAutomatically();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void FadeStopFinished();
|
void FadeStopFinished();
|
||||||
@@ -93,9 +92,6 @@ class ContextAlbum : public QWidget {
|
|||||||
public slots:
|
public slots:
|
||||||
void SearchCoverInProgress();
|
void SearchCoverInProgress();
|
||||||
|
|
||||||
private:
|
|
||||||
static const int kFadeTimeLineMs;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMenu *menu_;
|
QMenu *menu_;
|
||||||
ContextView *context_view_;
|
ContextView *context_view_;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "utilities/strutils.h"
|
#include "utilities/strutils.h"
|
||||||
#include "utilities/timeutils.h"
|
#include "utilities/timeutils.h"
|
||||||
#include "widgets/resizabletextedit.h"
|
#include "widgets/resizabletextedit.h"
|
||||||
@@ -64,7 +65,9 @@
|
|||||||
#include "contextview.h"
|
#include "contextview.h"
|
||||||
#include "contextalbum.h"
|
#include "contextalbum.h"
|
||||||
|
|
||||||
const int ContextView::kWidgetSpacing = 50;
|
namespace {
|
||||||
|
constexpr int kWidgetSpacing = 50;
|
||||||
|
}
|
||||||
|
|
||||||
ContextView::ContextView(QWidget *parent)
|
ContextView::ContextView(QWidget *parent)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
@@ -112,25 +115,25 @@ ContextView::ContextView(QWidget *parent)
|
|||||||
|
|
||||||
setLayout(layout_container_);
|
setLayout(layout_container_);
|
||||||
|
|
||||||
layout_container_->setObjectName("context-layout-container");
|
layout_container_->setObjectName(QStringLiteral("context-layout-container"));
|
||||||
layout_container_->setContentsMargins(0, 0, 0, 0);
|
layout_container_->setContentsMargins(0, 0, 0, 0);
|
||||||
layout_container_->addWidget(scrollarea_);
|
layout_container_->addWidget(scrollarea_);
|
||||||
|
|
||||||
scrollarea_->setObjectName("context-scrollarea");
|
scrollarea_->setObjectName(QStringLiteral("context-scrollarea"));
|
||||||
scrollarea_->setWidgetResizable(true);
|
scrollarea_->setWidgetResizable(true);
|
||||||
scrollarea_->setWidget(widget_scrollarea_);
|
scrollarea_->setWidget(widget_scrollarea_);
|
||||||
scrollarea_->setContentsMargins(0, 0, 0, 0);
|
scrollarea_->setContentsMargins(0, 0, 0, 0);
|
||||||
scrollarea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
scrollarea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
scrollarea_->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
scrollarea_->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||||
|
|
||||||
widget_scrollarea_->setObjectName("context-widget-scrollarea");
|
widget_scrollarea_->setObjectName(QStringLiteral("context-widget-scrollarea"));
|
||||||
widget_scrollarea_->setLayout(layout_scrollarea_);
|
widget_scrollarea_->setLayout(layout_scrollarea_);
|
||||||
widget_scrollarea_->setContentsMargins(0, 0, 0, 0);
|
widget_scrollarea_->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
textedit_top_->setReadOnly(true);
|
textedit_top_->setReadOnly(true);
|
||||||
textedit_top_->setFrameShape(QFrame::NoFrame);
|
textedit_top_->setFrameShape(QFrame::NoFrame);
|
||||||
|
|
||||||
layout_scrollarea_->setObjectName("context-layout-scrollarea");
|
layout_scrollarea_->setObjectName(QStringLiteral("context-layout-scrollarea"));
|
||||||
layout_scrollarea_->setContentsMargins(15, 15, 15, 15);
|
layout_scrollarea_->setContentsMargins(15, 15, 15, 15);
|
||||||
layout_scrollarea_->addWidget(textedit_top_);
|
layout_scrollarea_->addWidget(textedit_top_);
|
||||||
layout_scrollarea_->addWidget(widget_album_);
|
layout_scrollarea_->addWidget(widget_album_);
|
||||||
@@ -291,20 +294,20 @@ void ContextView::ReloadSettings() {
|
|||||||
|
|
||||||
QString default_font;
|
QString default_font;
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
if (QFontDatabase::families().contains(ContextSettingsPage::kDefaultFontFamily)) {
|
if (QFontDatabase::families().contains(QLatin1String(ContextSettingsPage::kDefaultFontFamily))) {
|
||||||
#else
|
#else
|
||||||
if (QFontDatabase().families().contains(ContextSettingsPage::kDefaultFontFamily)) {
|
if (QFontDatabase().families().contains(QLatin1String(ContextSettingsPage::kDefaultFontFamily))) {
|
||||||
#endif
|
#endif
|
||||||
default_font = ContextSettingsPage::kDefaultFontFamily;
|
default_font = QLatin1String(ContextSettingsPage::kDefaultFontFamily);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
default_font = font().family();
|
default_font = font().family();
|
||||||
}
|
}
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, QStringLiteral("%title% - %artist%")).toString();
|
||||||
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, QStringLiteral("%album%")).toString();
|
||||||
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool());
|
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool());
|
||||||
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool());
|
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool());
|
||||||
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], true).toBool());
|
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], true).toBool());
|
||||||
@@ -390,7 +393,7 @@ void ContextView::FadeStopFinished() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::SetLabelText(QLabel *label, int value, const QString &suffix, const QString &def) {
|
void ContextView::SetLabelText(QLabel *label, int value, const QString &suffix, const QString &def) {
|
||||||
label->setText(value <= 0 ? def : (QString::number(value) + " " + suffix));
|
label->setText(value <= 0 ? def : (QString::number(value) + QLatin1Char(' ') + suffix));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::UpdateNoSong() {
|
void ContextView::UpdateNoSong() {
|
||||||
@@ -409,15 +412,15 @@ void ContextView::NoSong() {
|
|||||||
QString html;
|
QString html;
|
||||||
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
|
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
|
||||||
else html += tr("%1 songs").arg(collectionview_->TotalSongs());
|
else html += tr("%1 songs").arg(collectionview_->TotalSongs());
|
||||||
html += "<br />";
|
html += QLatin1String("<br />");
|
||||||
|
|
||||||
if (collectionview_->TotalArtists() == 1) html += tr("%1 artist").arg(collectionview_->TotalArtists());
|
if (collectionview_->TotalArtists() == 1) html += tr("%1 artist").arg(collectionview_->TotalArtists());
|
||||||
else html += tr("%1 artists").arg(collectionview_->TotalArtists());
|
else html += tr("%1 artists").arg(collectionview_->TotalArtists());
|
||||||
html += "<br />";
|
html += QLatin1String("<br />");
|
||||||
|
|
||||||
if (collectionview_->TotalAlbums() == 1) html += tr("%1 album").arg(collectionview_->TotalAlbums());
|
if (collectionview_->TotalAlbums() == 1) html += tr("%1 album").arg(collectionview_->TotalAlbums());
|
||||||
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
||||||
html += "<br />";
|
html += QLatin1String("<br />");
|
||||||
|
|
||||||
label_stop_summary_->setFont(font_normal_);
|
label_stop_summary_->setFont(font_normal_);
|
||||||
label_stop_summary_->setText(html);
|
label_stop_summary_->setText(html);
|
||||||
@@ -438,7 +441,7 @@ void ContextView::UpdateFonts() {
|
|||||||
void ContextView::SetSong() {
|
void ContextView::SetSong() {
|
||||||
|
|
||||||
textedit_top_->setFont(font_headline_);
|
textedit_top_->setFont(font_headline_);
|
||||||
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
|
textedit_top_->SetText(QStringLiteral("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, QStringLiteral("<br />"), true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, QStringLiteral("<br />"), true)));
|
||||||
|
|
||||||
label_stop_summary_->clear();
|
label_stop_summary_->clear();
|
||||||
|
|
||||||
@@ -474,7 +477,7 @@ void ContextView::SetSong() {
|
|||||||
else {
|
else {
|
||||||
label_samplerate_title_->show();
|
label_samplerate_title_->show();
|
||||||
label_samplerate_->show();
|
label_samplerate_->show();
|
||||||
SetLabelText(label_samplerate_, song_playing_.samplerate(), "Hz");
|
SetLabelText(label_samplerate_, song_playing_.samplerate(), QStringLiteral("Hz"));
|
||||||
}
|
}
|
||||||
if (song_playing_.bitdepth() <= 0) {
|
if (song_playing_.bitdepth() <= 0) {
|
||||||
label_bitdepth_title_->hide();
|
label_bitdepth_title_->hide();
|
||||||
@@ -484,7 +487,7 @@ void ContextView::SetSong() {
|
|||||||
else {
|
else {
|
||||||
label_bitdepth_title_->show();
|
label_bitdepth_title_->show();
|
||||||
label_bitdepth_->show();
|
label_bitdepth_->show();
|
||||||
SetLabelText(label_bitdepth_, song_playing_.bitdepth(), "Bit");
|
SetLabelText(label_bitdepth_, song_playing_.bitdepth(), QStringLiteral("Bit"));
|
||||||
}
|
}
|
||||||
if (song_playing_.bitrate() <= 0) {
|
if (song_playing_.bitrate() <= 0) {
|
||||||
label_bitrate_title_->hide();
|
label_bitrate_title_->hide();
|
||||||
@@ -546,7 +549,10 @@ void ContextView::SetSong() {
|
|||||||
|
|
||||||
void ContextView::UpdateSong(const Song &song) {
|
void ContextView::UpdateSong(const Song &song) {
|
||||||
|
|
||||||
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true)));
|
const QString top_text = QStringLiteral("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, QStringLiteral("<br />"), true), Utilities::ReplaceMessage(summary_fmt_, song, QStringLiteral("<br />"), true));
|
||||||
|
if (top_text != textedit_top_->Text()) {
|
||||||
|
textedit_top_->SetText(top_text);
|
||||||
|
}
|
||||||
|
|
||||||
if (action_show_data_->isChecked()) {
|
if (action_show_data_->isChecked()) {
|
||||||
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
|
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
|
||||||
@@ -571,7 +577,7 @@ void ContextView::UpdateSong(const Song &song) {
|
|||||||
else {
|
else {
|
||||||
label_samplerate_title_->show();
|
label_samplerate_title_->show();
|
||||||
label_samplerate_->show();
|
label_samplerate_->show();
|
||||||
SetLabelText(label_samplerate_, song.samplerate(), "Hz");
|
SetLabelText(label_samplerate_, song.samplerate(), QStringLiteral("Hz"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (song.bitdepth() != song_playing_.bitdepth()) {
|
if (song.bitdepth() != song_playing_.bitdepth()) {
|
||||||
@@ -583,7 +589,7 @@ void ContextView::UpdateSong(const Song &song) {
|
|||||||
else {
|
else {
|
||||||
label_bitdepth_title_->show();
|
label_bitdepth_title_->show();
|
||||||
label_bitdepth_->show();
|
label_bitdepth_->show();
|
||||||
SetLabelText(label_bitdepth_, song.bitdepth(), "Bit");
|
SetLabelText(label_bitdepth_, song.bitdepth(), QStringLiteral("Bit"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (song.bitrate() != song_playing_.bitrate()) {
|
if (song.bitrate() != song_playing_.bitrate()) {
|
||||||
@@ -631,7 +637,12 @@ void ContextView::UpdateLyrics(const quint64 id, const QString &provider, const
|
|||||||
|
|
||||||
if (static_cast<qint64>(id) != lyrics_id_) return;
|
if (static_cast<qint64>(id) != lyrics_id_) return;
|
||||||
|
|
||||||
lyrics_ = lyrics + "\n\n(Lyrics from " + provider + ")\n";
|
if (lyrics.isEmpty()) {
|
||||||
|
lyrics_ = QLatin1String("No lyrics found.\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lyrics_ = lyrics + QLatin1String("\n\n(Lyrics from ") + provider + QLatin1String(")\n");
|
||||||
|
}
|
||||||
lyrics_id_ = -1;
|
lyrics_id_ = -1;
|
||||||
|
|
||||||
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
||||||
@@ -687,7 +698,7 @@ void ContextView::AlbumCoverLoaded(const Song &song, const QImage &image) {
|
|||||||
|
|
||||||
void ContextView::ActionShowAlbum() {
|
void ContextView::ActionShowAlbum() {
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], action_show_album_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], action_show_album_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -697,7 +708,7 @@ void ContextView::ActionShowAlbum() {
|
|||||||
|
|
||||||
void ContextView::ActionShowData() {
|
void ContextView::ActionShowData() {
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], action_show_data_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], action_show_data_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -707,7 +718,7 @@ void ContextView::ActionShowData() {
|
|||||||
|
|
||||||
void ContextView::ActionShowLyrics() {
|
void ContextView::ActionShowLyrics() {
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], action_show_lyrics_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], action_show_lyrics_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -720,7 +731,7 @@ void ContextView::ActionShowLyrics() {
|
|||||||
|
|
||||||
void ContextView::ActionSearchLyrics() {
|
void ContextView::ActionSearchLyrics() {
|
||||||
|
|
||||||
QSettings s;
|
Settings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], action_search_lyrics_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], action_search_lyrics_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|||||||
@@ -101,8 +101,6 @@ class ContextView : public QWidget {
|
|||||||
void AlbumCoverLoaded(const Song &song, const QImage &image);
|
void AlbumCoverLoaded(const Song &song, const QImage &image);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int kWidgetSpacing;
|
|
||||||
|
|
||||||
Application *app_;
|
Application *app_;
|
||||||
CollectionView *collectionview_;
|
CollectionView *collectionview_;
|
||||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
#include "covermanager/musicbrainzcoverprovider.h"
|
#include "covermanager/musicbrainzcoverprovider.h"
|
||||||
#include "covermanager/deezercoverprovider.h"
|
#include "covermanager/deezercoverprovider.h"
|
||||||
#include "covermanager/musixmatchcoverprovider.h"
|
#include "covermanager/musixmatchcoverprovider.h"
|
||||||
#include "covermanager/spotifycoverprovider.h"
|
#include "covermanager/opentidalcoverprovider.h"
|
||||||
|
|
||||||
#include "lyrics/lyricsproviders.h"
|
#include "lyrics/lyricsproviders.h"
|
||||||
#include "lyrics/geniuslyricsprovider.h"
|
#include "lyrics/geniuslyricsprovider.h"
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
#include "lyrics/songlyricscomlyricsprovider.h"
|
#include "lyrics/songlyricscomlyricsprovider.h"
|
||||||
#include "lyrics/azlyricscomlyricsprovider.h"
|
#include "lyrics/azlyricscomlyricsprovider.h"
|
||||||
#include "lyrics/elyricsnetlyricsprovider.h"
|
#include "lyrics/elyricsnetlyricsprovider.h"
|
||||||
#include "lyrics/lyricsmodecomlyricsprovider.h"
|
#include "lyrics/letraslyricsprovider.h"
|
||||||
|
|
||||||
#include "scrobbler/audioscrobbler.h"
|
#include "scrobbler/audioscrobbler.h"
|
||||||
#include "scrobbler/lastfmscrobbler.h"
|
#include "scrobbler/lastfmscrobbler.h"
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
# include "scrobbler/subsonicscrobbler.h"
|
# include "scrobbler/subsonicscrobbler.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "internet/internetservices.h"
|
#include "streaming/streamingservices.h"
|
||||||
|
|
||||||
#ifdef HAVE_SUBSONIC
|
#ifdef HAVE_SUBSONIC
|
||||||
# include "subsonic/subsonicservice.h"
|
# include "subsonic/subsonicservice.h"
|
||||||
@@ -89,6 +89,11 @@
|
|||||||
# include "covermanager/tidalcoverprovider.h"
|
# include "covermanager/tidalcoverprovider.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
# include "spotify/spotifyservice.h"
|
||||||
|
# include "covermanager/spotifycoverprovider.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_QOBUZ
|
#ifdef HAVE_QOBUZ
|
||||||
# include "qobuz/qobuzservice.h"
|
# include "qobuz/qobuzservice.h"
|
||||||
# include "covermanager/qobuzcoverprovider.h"
|
# include "covermanager/qobuzcoverprovider.h"
|
||||||
@@ -142,10 +147,13 @@ class ApplicationImpl {
|
|||||||
cover_providers->AddProvider(new DiscogsCoverProvider(app, app->network()));
|
cover_providers->AddProvider(new DiscogsCoverProvider(app, app->network()));
|
||||||
cover_providers->AddProvider(new DeezerCoverProvider(app, app->network()));
|
cover_providers->AddProvider(new DeezerCoverProvider(app, app->network()));
|
||||||
cover_providers->AddProvider(new MusixmatchCoverProvider(app, app->network()));
|
cover_providers->AddProvider(new MusixmatchCoverProvider(app, app->network()));
|
||||||
cover_providers->AddProvider(new SpotifyCoverProvider(app, app->network()));
|
cover_providers->AddProvider(new OpenTidalCoverProvider(app, app->network()));
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
cover_providers->AddProvider(new TidalCoverProvider(app, app->network()));
|
cover_providers->AddProvider(new TidalCoverProvider(app, app->network()));
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
cover_providers->AddProvider(new SpotifyCoverProvider(app, app->network()));
|
||||||
|
#endif
|
||||||
#ifdef HAVE_QOBUZ
|
#ifdef HAVE_QOBUZ
|
||||||
cover_providers->AddProvider(new QobuzCoverProvider(app, app->network()));
|
cover_providers->AddProvider(new QobuzCoverProvider(app, app->network()));
|
||||||
#endif
|
#endif
|
||||||
@@ -169,22 +177,25 @@ class ApplicationImpl {
|
|||||||
lyrics_providers->AddProvider(new SongLyricsComLyricsProvider(app->network()));
|
lyrics_providers->AddProvider(new SongLyricsComLyricsProvider(app->network()));
|
||||||
lyrics_providers->AddProvider(new AzLyricsComLyricsProvider(app->network()));
|
lyrics_providers->AddProvider(new AzLyricsComLyricsProvider(app->network()));
|
||||||
lyrics_providers->AddProvider(new ElyricsNetLyricsProvider(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();
|
lyrics_providers->ReloadSettings();
|
||||||
return lyrics_providers;
|
return lyrics_providers;
|
||||||
}),
|
}),
|
||||||
internet_services_([app]() {
|
streaming_services_([app]() {
|
||||||
InternetServices *internet_services = new InternetServices();
|
StreamingServices *streaming_services = new StreamingServices();
|
||||||
#ifdef HAVE_SUBSONIC
|
#ifdef HAVE_SUBSONIC
|
||||||
internet_services->AddService(make_shared<SubsonicService>(app));
|
streaming_services->AddService(make_shared<SubsonicService>(app));
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
internet_services->AddService(make_shared<TidalService>(app));
|
streaming_services->AddService(make_shared<TidalService>(app));
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
streaming_services->AddService(make_shared<SpotifyService>(app));
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_QOBUZ
|
#ifdef HAVE_QOBUZ
|
||||||
internet_services->AddService(make_shared<QobuzService>(app));
|
streaming_services->AddService(make_shared<QobuzService>(app));
|
||||||
#endif
|
#endif
|
||||||
return internet_services;
|
return streaming_services;
|
||||||
}),
|
}),
|
||||||
radio_services_([app]() { return new RadioServices(app); }),
|
radio_services_([app]() { return new RadioServices(app); }),
|
||||||
scrobbler_([app]() {
|
scrobbler_([app]() {
|
||||||
@@ -220,7 +231,7 @@ class ApplicationImpl {
|
|||||||
Lazy<AlbumCoverLoader> album_cover_loader_;
|
Lazy<AlbumCoverLoader> album_cover_loader_;
|
||||||
Lazy<CurrentAlbumCoverLoader> current_albumcover_loader_;
|
Lazy<CurrentAlbumCoverLoader> current_albumcover_loader_;
|
||||||
Lazy<LyricsProviders> lyrics_providers_;
|
Lazy<LyricsProviders> lyrics_providers_;
|
||||||
Lazy<InternetServices> internet_services_;
|
Lazy<StreamingServices> streaming_services_;
|
||||||
Lazy<RadioServices> radio_services_;
|
Lazy<RadioServices> radio_services_;
|
||||||
Lazy<AudioScrobbler> scrobbler_;
|
Lazy<AudioScrobbler> scrobbler_;
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
@@ -285,7 +296,7 @@ void Application::Exit() {
|
|||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
<< &*device_manager()
|
<< &*device_manager()
|
||||||
#endif
|
#endif
|
||||||
<< &*internet_services()
|
<< &*streaming_services()
|
||||||
<< &*radio_services()->radio_backend();
|
<< &*radio_services()->radio_backend();
|
||||||
|
|
||||||
QObject::connect(&*tag_reader_client(), &TagReaderClient::ExitFinished, this, &Application::ExitReceived);
|
QObject::connect(&*tag_reader_client(), &TagReaderClient::ExitFinished, this, &Application::ExitReceived);
|
||||||
@@ -305,8 +316,8 @@ void Application::Exit() {
|
|||||||
device_manager()->Exit();
|
device_manager()->Exit();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QObject::connect(&*internet_services(), &InternetServices::ExitFinished, this, &Application::ExitReceived);
|
QObject::connect(&*streaming_services(), &StreamingServices::ExitFinished, this, &Application::ExitReceived);
|
||||||
internet_services()->Exit();
|
streaming_services()->Exit();
|
||||||
|
|
||||||
QObject::connect(&*radio_services()->radio_backend(), &RadioBackend::ExitFinished, this, &Application::ExitReceived);
|
QObject::connect(&*radio_services()->radio_backend(), &RadioBackend::ExitFinished, this, &Application::ExitReceived);
|
||||||
radio_services()->radio_backend()->ExitAsync();
|
radio_services()->radio_backend()->ExitAsync();
|
||||||
@@ -351,7 +362,7 @@ SharedPtr<CurrentAlbumCoverLoader> Application::current_albumcover_loader() cons
|
|||||||
SharedPtr<LyricsProviders> Application::lyrics_providers() const { return p_->lyrics_providers_.ptr(); }
|
SharedPtr<LyricsProviders> Application::lyrics_providers() const { return p_->lyrics_providers_.ptr(); }
|
||||||
SharedPtr<PlaylistBackend> Application::playlist_backend() const { return p_->playlist_backend_.ptr(); }
|
SharedPtr<PlaylistBackend> Application::playlist_backend() const { return p_->playlist_backend_.ptr(); }
|
||||||
SharedPtr<PlaylistManager> Application::playlist_manager() const { return p_->playlist_manager_.ptr(); }
|
SharedPtr<PlaylistManager> Application::playlist_manager() const { return p_->playlist_manager_.ptr(); }
|
||||||
SharedPtr<InternetServices> Application::internet_services() const { return p_->internet_services_.ptr(); }
|
SharedPtr<StreamingServices> Application::streaming_services() const { return p_->streaming_services_.ptr(); }
|
||||||
SharedPtr<RadioServices> Application::radio_services() const { return p_->radio_services_.ptr(); }
|
SharedPtr<RadioServices> Application::radio_services() const { return p_->radio_services_.ptr(); }
|
||||||
SharedPtr<AudioScrobbler> Application::scrobbler() const { return p_->scrobbler_.ptr(); }
|
SharedPtr<AudioScrobbler> Application::scrobbler() const { return p_->scrobbler_.ptr(); }
|
||||||
SharedPtr<LastFMImport> Application::lastfm_import() const { return p_->lastfm_import_.ptr(); }
|
SharedPtr<LastFMImport> Application::lastfm_import() const { return p_->lastfm_import_.ptr(); }
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class CoverProviders;
|
|||||||
class LyricsProviders;
|
class LyricsProviders;
|
||||||
class AudioScrobbler;
|
class AudioScrobbler;
|
||||||
class LastFMImport;
|
class LastFMImport;
|
||||||
class InternetServices;
|
class StreamingServices;
|
||||||
class RadioServices;
|
class RadioServices;
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
class MoodbarController;
|
class MoodbarController;
|
||||||
@@ -97,7 +97,7 @@ class Application : public QObject {
|
|||||||
|
|
||||||
SharedPtr<AudioScrobbler> scrobbler() const;
|
SharedPtr<AudioScrobbler> scrobbler() const;
|
||||||
|
|
||||||
SharedPtr<InternetServices> internet_services() const;
|
SharedPtr<StreamingServices> streaming_services() const;
|
||||||
SharedPtr<RadioServices> radio_services() const;
|
SharedPtr<RadioServices> radio_services() const;
|
||||||
|
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -45,7 +46,9 @@
|
|||||||
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
|
||||||
const char *CommandlineOptions::kHelpText =
|
namespace {
|
||||||
|
|
||||||
|
constexpr char kHelpText[] =
|
||||||
"%1: strawberry [%2] [%3]\n"
|
"%1: strawberry [%2] [%3]\n"
|
||||||
"\n"
|
"\n"
|
||||||
"%4:\n"
|
"%4:\n"
|
||||||
@@ -82,7 +85,9 @@ const char *CommandlineOptions::kHelpText =
|
|||||||
" --log-levels <levels> %33\n"
|
" --log-levels <levels> %33\n"
|
||||||
" --version %34\n";
|
" --version %34\n";
|
||||||
|
|
||||||
const char *CommandlineOptions::kVersionText = "Strawberry %1";
|
constexpr char kVersionText[] = "Strawberry %1";
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||||
: argc_(argc),
|
: argc_(argc),
|
||||||
@@ -100,7 +105,7 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
|||||||
play_track_at_(-1),
|
play_track_at_(-1),
|
||||||
show_osd_(false),
|
show_osd_(false),
|
||||||
toggle_pretty_osd_(false),
|
toggle_pretty_osd_(false),
|
||||||
log_levels_(logging::kDefaultLogLevels) {
|
log_levels_(QLatin1String(logging::kDefaultLogLevels)) {
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
Q_UNUSED(argv);
|
Q_UNUSED(argv);
|
||||||
@@ -108,11 +113,11 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
|||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
// Remove -psn_xxx option that Mac passes when opened from Finder.
|
// Remove -psn_xxx option that Mac passes when opened from Finder.
|
||||||
RemoveArg("-psn", 1);
|
RemoveArg(QStringLiteral("-psn"), 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Remove the -session option that KDE passes
|
// Remove the -session option that KDE passes
|
||||||
RemoveArg("-session", 2);
|
RemoveArg(QStringLiteral("-session"), 2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,9 +217,9 @@ bool CommandlineOptions::Parse() {
|
|||||||
if (c == -1) break;
|
if (c == -1) break;
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'h': {
|
case 'h':{
|
||||||
QString translated_help_text =
|
QString translated_help_text =
|
||||||
QString(kHelpText)
|
QString::fromUtf8(kHelpText)
|
||||||
.arg(QObject::tr("Usage"), QObject::tr("options"), QObject::tr("URL(s)"),
|
.arg(QObject::tr("Usage"), QObject::tr("options"), QObject::tr("URL(s)"),
|
||||||
QObject::tr("Player options"),
|
QObject::tr("Player options"),
|
||||||
QObject::tr("Start the playlist currently playing"),
|
QObject::tr("Start the playlist currently playing"),
|
||||||
@@ -301,16 +306,16 @@ bool CommandlineOptions::Parse() {
|
|||||||
volume_modifier_ = -4;
|
volume_modifier_ = -4;
|
||||||
break;
|
break;
|
||||||
case LongOptions::Quiet:
|
case LongOptions::Quiet:
|
||||||
log_levels_ = "1";
|
log_levels_ = QStringLiteral("1");
|
||||||
break;
|
break;
|
||||||
case LongOptions::Verbose:
|
case LongOptions::Verbose:
|
||||||
log_levels_ = "3";
|
log_levels_ = QStringLiteral("3");
|
||||||
break;
|
break;
|
||||||
case LongOptions::LogLevels:
|
case LongOptions::LogLevels:
|
||||||
log_levels_ = OptArgToString(optarg);
|
log_levels_ = OptArgToString(optarg);
|
||||||
break;
|
break;
|
||||||
case LongOptions::Version: {
|
case LongOptions::Version:{
|
||||||
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
QString version_text = QString::fromUtf8(kVersionText).arg(QLatin1String(STRAWBERRY_VERSION_DISPLAY));
|
||||||
std::cout << version_text.toLocal8Bit().constData() << std::endl;
|
std::cout << version_text.toLocal8Bit().constData() << std::endl;
|
||||||
std::exit(0);
|
std::exit(0);
|
||||||
}
|
}
|
||||||
@@ -364,7 +369,7 @@ bool CommandlineOptions::Parse() {
|
|||||||
const QString value = DecodeName(argv_[i]);
|
const QString value = DecodeName(argv_[i]);
|
||||||
QFileInfo fileinfo(value);
|
QFileInfo fileinfo(value);
|
||||||
if (fileinfo.exists()) {
|
if (fileinfo.exists()) {
|
||||||
urls_ << QUrl::fromLocalFile(fileinfo.canonicalFilePath());
|
urls_ << QUrl::fromLocalFile(QDir::cleanPath(fileinfo.filePath()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
urls_ << QUrl::fromUserInput(value);
|
urls_ << QUrl::fromUserInput(value);
|
||||||
@@ -416,7 +421,7 @@ void CommandlineOptions::Load(const QByteArray &serialized) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
QString CommandlineOptions::OptArgToString(wchar_t *opt) {
|
QString CommandlineOptions::OptArgToString(const wchar_t *opt) {
|
||||||
|
|
||||||
return QString::fromWCharArray(opt);
|
return QString::fromWCharArray(opt);
|
||||||
|
|
||||||
@@ -427,9 +432,9 @@ QString CommandlineOptions::DecodeName(wchar_t *opt) {
|
|||||||
return QString::fromWCharArray(opt);
|
return QString::fromWCharArray(opt);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
QString CommandlineOptions::OptArgToString(char *opt) {
|
QString CommandlineOptions::OptArgToString(const char *opt) {
|
||||||
|
|
||||||
return QString(opt);
|
return QString::fromUtf8(opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CommandlineOptions::DecodeName(char *opt) {
|
QString CommandlineOptions::DecodeName(char *opt) {
|
||||||
|
|||||||
@@ -41,9 +41,6 @@ class CommandlineOptions {
|
|||||||
public:
|
public:
|
||||||
explicit CommandlineOptions(int argc = 0, char **argv = nullptr);
|
explicit CommandlineOptions(int argc = 0, char **argv = nullptr);
|
||||||
|
|
||||||
static const char *kHelpText;
|
|
||||||
static const char *kVersionText;
|
|
||||||
|
|
||||||
// Don't change the values or order, these get serialised and sent to
|
// Don't change the values or order, these get serialised and sent to
|
||||||
// possibly a different version of Strawberry
|
// possibly a different version of Strawberry
|
||||||
enum class UrlListAction {
|
enum class UrlListAction {
|
||||||
@@ -108,10 +105,10 @@ class CommandlineOptions {
|
|||||||
void RemoveArg(const QString &starts_with, int count);
|
void RemoveArg(const QString &starts_with, int count);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
static QString OptArgToString(wchar_t *opt);
|
static QString OptArgToString(const wchar_t *opt);
|
||||||
static QString DecodeName(wchar_t *opt);
|
static QString DecodeName(wchar_t *opt);
|
||||||
#else
|
#else
|
||||||
static QString OptArgToString(char *opt);
|
static QString OptArgToString(const char *opt);
|
||||||
static QString DecodeName(char *opt);
|
static QString DecodeName(char *opt);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||