Compare commits
511 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beefdabc64 | ||
|
|
91e06cadf3 | ||
|
|
4ea5eb8292 | ||
|
|
af06f8e70a | ||
|
|
3238e295ba | ||
|
|
26fbd5c144 | ||
|
|
e64a2b98c4 | ||
|
|
42417e5554 | ||
|
|
82e613206e | ||
|
|
f1038152e9 | ||
|
|
2597a8faed | ||
|
|
8008ec895a | ||
|
|
d85d25b931 | ||
|
|
de62552ad1 | ||
|
|
155485173b | ||
|
|
82079fcf70 | ||
|
|
3b2eea5292 | ||
|
|
1f2ad9c177 | ||
|
|
cc6e5a6c69 | ||
|
|
c77c7a247a | ||
|
|
552440f50e | ||
|
|
2a9ccd7480 | ||
|
|
f265c055a5 | ||
|
|
837e5388ea | ||
|
|
9883dc6925 | ||
|
|
e3ad00e930 | ||
|
|
6428ae8b3a | ||
|
|
07c182d5b8 | ||
|
|
64aa15842c | ||
|
|
19c8da06e6 | ||
|
|
7dd959f4a1 | ||
|
|
148ae530d8 | ||
|
|
e75698ee9a | ||
|
|
80bea31b98 | ||
|
|
4ea57d1181 | ||
|
|
854847ca8a | ||
|
|
bd39e7cb0d | ||
|
|
20ef621a20 | ||
|
|
145c276c97 | ||
|
|
1c5d0dceb1 | ||
|
|
359f320b06 | ||
|
|
108d522dcf | ||
|
|
96e746c508 | ||
|
|
b2c862e7d5 | ||
|
|
9334fe9f24 | ||
|
|
b964385024 | ||
|
|
3f3059c98b | ||
|
|
8da616491d | ||
|
|
cb0db8750f | ||
|
|
08224443e3 | ||
|
|
5c2989196f | ||
|
|
4c4c84e104 | ||
|
|
232399ea28 | ||
|
|
9d22e4ec07 | ||
|
|
ee5bc16e47 | ||
|
|
74f0f885b9 | ||
|
|
b914d9aaba | ||
|
|
136f150d67 | ||
|
|
5b50cbb61b | ||
|
|
cb3a9bf195 | ||
|
|
cb847951e6 | ||
|
|
11228f0634 | ||
|
|
a0889d60f1 | ||
|
|
0055ebe8a7 | ||
|
|
58c7a9b9cc | ||
|
|
6afc081ff0 | ||
|
|
2c0ad2fc88 | ||
|
|
77e934beab | ||
|
|
368022ec43 | ||
|
|
69f8ca95bc | ||
|
|
dde8661e93 | ||
|
|
2604e1a0ff | ||
|
|
e8471bcc66 | ||
|
|
d230dd7365 | ||
|
|
74dce24e91 | ||
|
|
bc667a6474 | ||
|
|
a2cae06582 | ||
|
|
5212587055 | ||
|
|
efd42bc68f | ||
|
|
ebaa2e7918 | ||
|
|
7ebcc73a49 | ||
|
|
be09011bb7 | ||
|
|
2778a55e8e | ||
|
|
9b5fe3bfd6 | ||
|
|
91eef0d695 | ||
|
|
88704efad8 | ||
|
|
f4e4483392 | ||
|
|
63102bce23 | ||
|
|
8890a3dd0f | ||
|
|
3d9dec2c27 | ||
|
|
c96ad61c80 | ||
|
|
acd74d5b54 | ||
|
|
4808188964 | ||
|
|
1bb045b3b0 | ||
|
|
bdca60c0ad | ||
|
|
8d9c135498 | ||
|
|
0f76482916 | ||
|
|
38eb86bdee | ||
|
|
cbce9892d5 | ||
|
|
f624b7a331 | ||
|
|
1ebcd61a75 | ||
|
|
358da72ffe | ||
|
|
9666feca37 | ||
|
|
03eb52eac8 | ||
|
|
6562cc710c | ||
|
|
222001bc13 | ||
|
|
e93f14248a | ||
|
|
7119f1bc81 | ||
|
|
548fa3f6ee | ||
|
|
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 | ||
|
|
b6c9ef4a15 | ||
|
|
20e550bc7d | ||
|
|
a4b7766947 | ||
|
|
8d1a0071b6 | ||
|
|
a3c00e607b | ||
|
|
de7ca8b736 | ||
|
|
04e593dc62 | ||
|
|
2294c38aa9 | ||
|
|
6f41d39a9c | ||
|
|
7c4e33b676 | ||
|
|
4cc66bccad | ||
|
|
55b1d34f48 | ||
|
|
4da0e8d8ad | ||
|
|
b95bfba676 | ||
|
|
1ff2bfd390 | ||
|
|
a35fa5b158 | ||
|
|
22169bda0d | ||
|
|
faf5f6c69d | ||
|
|
1ae01d4078 |
586
.github/workflows/build.yml
vendored
134
.gitignore
vendored
@@ -1,120 +1,18 @@
|
|||||||
# 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
|
||||||
|
/dist/macos/Info.plist
|
||||||
|
|||||||
17
3rdparty/README.md
vendored
@@ -2,20 +2,27 @@
|
|||||||
============================================
|
============================================
|
||||||
|
|
||||||
KDSingleApplication
|
KDSingleApplication
|
||||||
-----------------
|
-------------------
|
||||||
This is a small static library used by Strawberry to prevent it from starting twice per user session.
|
A small library used by Strawberry to prevent it from starting twice per user session.
|
||||||
If the user tries to start strawberry twice, the main window will maximize instead of starting another instance.
|
If the user tries to start strawberry twice, the main window will maximize instead of starting another instance.
|
||||||
It is also used to pass command-line options through to the first instance.
|
It is also used to pass command-line options through to the first instance.
|
||||||
|
This 3rdparty copy is used only if KDSingleApplication 1.1 or higher is not found on the system.
|
||||||
|
|
||||||
URL: https://github.com/KDAB/KDSingleApplication/
|
URL: https://github.com/KDAB/KDSingleApplication/
|
||||||
|
|
||||||
|
|
||||||
SPMediaKeyTap
|
SPMediaKeyTap
|
||||||
-------------
|
-------------
|
||||||
Used on macOS to exclusively enable strawberry to grab global media shortcuts.
|
A library used on macOS to exclusively grab global media shortcuts.
|
||||||
Can safely be deleted on other platforms.
|
|
||||||
|
|
||||||
|
The library is no longer maintained by the original author.
|
||||||
|
|
||||||
|
The directory can safely be deleted on other platforms.
|
||||||
|
|
||||||
getopt
|
getopt
|
||||||
------
|
------
|
||||||
getopt included only when compiling on Windows.
|
getopt included on Windows for command line options parsing with Unicode support .
|
||||||
|
|
||||||
|
The directory can safely be deleted on other platforms.
|
||||||
|
|
||||||
|
URL: https://github.com/ludvikjerabek/getopt-win
|
||||||
|
|||||||
2
3rdparty/kdsingleapplication/CMakeLists.txt
vendored
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
set(SOURCES KDSingleApplication/src/kdsingleapplication.cpp KDSingleApplication/src/kdsingleapplication_localsocket.cpp)
|
set(SOURCES KDSingleApplication/src/kdsingleapplication.cpp KDSingleApplication/src/kdsingleapplication_localsocket.cpp)
|
||||||
set(HEADERS KDSingleApplication/src/kdsingleapplication.h KDSingleApplication/src/kdsingleapplication_localsocket_p.h)
|
set(HEADERS KDSingleApplication/src/kdsingleapplication.h KDSingleApplication/src/kdsingleapplication_localsocket_p.h)
|
||||||
qt_wrap_cpp(MOC ${HEADERS})
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
project(strawberry)
|
project(strawberry)
|
||||||
|
|
||||||
@@ -84,6 +84,13 @@ add_compile_options(${COMPILE_OPTIONS})
|
|||||||
|
|
||||||
if(CMAKE_BUILD_TYPE MATCHES "Release")
|
if(CMAKE_BUILD_TYPE MATCHES "Release")
|
||||||
add_definitions(-DNDEBUG)
|
add_definitions(-DNDEBUG)
|
||||||
|
set(ENABLE_DEBUG_OUTPUT_DEFAULT OFF)
|
||||||
|
else()
|
||||||
|
set(ENABLE_DEBUG_OUTPUT_DEFAULT ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
option(ENABLE_DEBUG_OUTPUT "Enable debug output" ${ENABLE_DEBUG_OUTPUT_DEFAULT})
|
||||||
|
if(NOT ENABLE_DEBUG_OUTPUT)
|
||||||
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -92,6 +99,8 @@ if(USE_RPATH)
|
|||||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(QT_NO_SHOW_OLD_QT_WRAP_CPP_WARNING ON)
|
||||||
|
|
||||||
find_program(CCACHE_EXECUTABLE NAMES ccache)
|
find_program(CCACHE_EXECUTABLE NAMES ccache)
|
||||||
if(CCACHE_EXECUTABLE)
|
if(CCACHE_EXECUTABLE)
|
||||||
message(STATUS "ccache found: will be used for compilation and linkage")
|
message(STATUS "ccache found: will be used for compilation and linkage")
|
||||||
@@ -99,23 +108,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 CONFIG)
|
||||||
|
if(NOT Boost_FOUND)
|
||||||
find_package(Boost REQUIRED)
|
find_package(Boost REQUIRED)
|
||||||
if(USE_ICU)
|
endif()
|
||||||
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
||||||
if(ICU_FOUND)
|
|
||||||
set(HAVE_ICU ON)
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
find_package(Iconv)
|
|
||||||
endif()
|
|
||||||
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 +269,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()
|
||||||
|
|
||||||
@@ -305,18 +308,13 @@ if(QT_VERSION_MAJOR EQUAL 5)
|
|||||||
else()
|
else()
|
||||||
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication-qt${QT_VERSION_MAJOR}")
|
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication-qt${QT_VERSION_MAJOR}")
|
||||||
endif()
|
endif()
|
||||||
find_package(${KDSINGLEAPPLICATION_NAME})
|
find_package(${KDSINGLEAPPLICATION_NAME} 1.1.0)
|
||||||
if(TARGET KDAB::kdsingleapplication)
|
if(TARGET KDAB::kdsingleapplication)
|
||||||
if(QT_VERSION_MAJOR EQUAL 5)
|
if(QT_VERSION_MAJOR EQUAL 5)
|
||||||
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication_VERSION}")
|
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication_VERSION}")
|
||||||
elseif(QT_VERSION_MAJOR EQUAL 6)
|
elseif(QT_VERSION_MAJOR EQUAL 6)
|
||||||
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication-qt6_VERSION}")
|
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication-qt6_VERSION}")
|
||||||
endif()
|
endif()
|
||||||
if(KDSINGLEAPPLICATION_VERSION VERSION_GREATER_EQUAL 1.0.95)
|
|
||||||
set(HAVE_KDSINGLEAPPLICATION_OPTIONS ON)
|
|
||||||
else()
|
|
||||||
set(HAVE_KDSINGLEAPPLICATION_OPTIONS OFF)
|
|
||||||
endif()
|
|
||||||
message(STATUS "Using system KDSingleApplication (Version ${KDSINGLEAPPLICATION_VERSION})")
|
message(STATUS "Using system KDSingleApplication (Version ${KDSINGLEAPPLICATION_VERSION})")
|
||||||
set(SINGLEAPPLICATION_LIBRARIES KDAB::kdsingleapplication)
|
set(SINGLEAPPLICATION_LIBRARIES KDAB::kdsingleapplication)
|
||||||
else()
|
else()
|
||||||
@@ -450,6 +448,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"
|
||||||
@@ -474,9 +473,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; }
|
||||||
@@ -487,25 +488,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
|
||||||
@@ -519,6 +501,10 @@ 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
|
||||||
|
-DQT_NO_KEYWORDS
|
||||||
|
-DQT_NO_SIGNALS_SLOTS_KEYWORDS
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
@@ -564,15 +550,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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
115
Changelog
@@ -2,6 +2,121 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
Version 1.1.2 (2024.09.12):
|
||||||
|
|
||||||
|
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).
|
||||||
|
* Fixed play (-p) command line option not working on startup (#1465).
|
||||||
|
* Fixed scan transaction being started when "Update the collection when Strawberry starts" option is unchecked (#1469)
|
||||||
|
* Fixed Spotify bitrate being limited 128kbit/s.
|
||||||
|
* Fixed Spotify returning too many artists and albums.
|
||||||
|
* Fixed manually switching Spotify songs blocking UI.
|
||||||
|
* Fixed analyzer not being set.
|
||||||
|
* Fixed context top text being updated causing selected text to be unselected.
|
||||||
|
* Fixed filter search to use filename for songs with empty title.
|
||||||
|
* Fixed missing developer in Appstream appdata file.
|
||||||
|
* Fixed MPRIS2 DesktopEntry to return desktop file entry without ".desktop" (#1516)
|
||||||
|
* Fixed WavPack .wvc accepted as valid audio files (#1525).
|
||||||
|
* Fixed dynamic playlist controls not following system colors (#1483).
|
||||||
|
* Fixed freeze on playlist right click (#1478).
|
||||||
|
* Fixed copying songs to a iPod device keeping too many files open (#1527).
|
||||||
|
* Fixed MBIDs from MP4 being parsed incorrectly causing ListenBrainz errors (#1531).
|
||||||
|
* Fixed playlist sorting after filename (#1538).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Improved volume adjustment and track seeking using touchpad (#1498).
|
||||||
|
* Use own thread for lyrics parsing.
|
||||||
|
* Added url and filename columns to collection and playlist filter search.
|
||||||
|
* (macOS) Added Spotify.
|
||||||
|
|
||||||
|
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):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed possible duplication of song entries after organizing (#1341).
|
||||||
|
* Fixed possible crash when connecting devices (#1313).
|
||||||
|
* Fixed playlist sorting of original year (#1349).
|
||||||
|
* (macOS) Fixed crash when adding collection directory (QTBUG-120469) (#1350).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Treat all stream errors as non-fatal (#1347).
|
||||||
|
* Require KDSingleApplication 1.1.0.
|
||||||
|
* Fix logging of restored unavailable songs.
|
||||||
|
|
||||||
Version 1.0.22 (2023.12.09):
|
Version 1.0.22 (2023.12.09):
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|||||||
19
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)
|
||||||
@@ -14,12 +14,11 @@ Resources:
|
|||||||
* Wiki: https://wiki.strawberrymusicplayer.org/
|
* Wiki: https://wiki.strawberrymusicplayer.org/
|
||||||
* Forum: https://forum.strawberrymusicplayer.org/
|
* Forum: https://forum.strawberrymusicplayer.org/
|
||||||
* Github: https://github.com/strawberrymusicplayer/strawberry
|
* Github: https://github.com/strawberrymusicplayer/strawberry
|
||||||
* Buildbot: https://buildbot.strawberrymusicplayer.org/
|
|
||||||
* Latest builds: https://builds.strawberrymusicplayer.org/
|
* Latest builds: https://builds.strawberrymusicplayer.org/
|
||||||
* 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
|
||||||
|
|
||||||
@@ -32,9 +31,9 @@ Resources:
|
|||||||
### :moneybag: Sponsoring
|
### :moneybag: Sponsoring
|
||||||
|
|
||||||
The program is free software, released under GPL. If you like this program and can make use of it, consider sponsoring or donating to help fund the project.
|
The program is free software, released under GPL. If you like this program and can make use of it, consider sponsoring or donating to help fund the project.
|
||||||
There are currently 3 options for sponsoring:
|
There are currently 4 options for sponsoring:
|
||||||
|
|
||||||
1. [GitHub Sponsors](https://github.com/sponsors/jonaski)
|
1. [GitHub](https://github.com/sponsors/jonaski)
|
||||||
2. [Patreon](https://www.patreon.com/jonaskvinge)
|
2. [Patreon](https://www.patreon.com/jonaskvinge)
|
||||||
3. [Ko-fi](https://ko-fi.com/jonaskvinge)
|
3. [Ko-fi](https://ko-fi.com/jonaskvinge)
|
||||||
4. [PayPal](https://paypal.me/jonaskvinge)
|
4. [PayPal](https://paypal.me/jonaskvinge)
|
||||||
@@ -54,18 +53,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 macOS releases require a developer account, Apple hardware and maintaining all libraries strawberry depends on. If you are sponsoring strawberry, e-mail support@strawberrymusicplayer.org for access to downloads.**
|
**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 +76,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 +119,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,19 +1,31 @@
|
|||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -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} -C --omit-header --no-location --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 22)
|
set(STRAWBERRY_VERSION_PATCH 2)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
set(INCLUDE_GIT_REVISION OFF)
|
set(INCLUDE_GIT_REVISION OFF)
|
||||||
|
|
||||||
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,7 @@
|
|||||||
<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>
|
||||||
|
<file>style/dynamicplaylistcontrols.css</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"
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|||||||
13
data/style/dynamicplaylistcontrols.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#container {
|
||||||
|
background: %background;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(200, 200, 200, 75%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#label1 {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#label2 {
|
||||||
|
font-size: 7.5pt;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
<summary>A music player and collection organizer</summary>
|
<summary>A music player and collection organizer</summary>
|
||||||
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
|
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
|
||||||
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
|
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
|
||||||
<translation type="qt">strawberry</translation>
|
<developer id="net.jkvinge.jonas">
|
||||||
|
<name>Jonas Kvinge</name>
|
||||||
|
</developer>
|
||||||
|
<translation type="gettext">strawberry</translation>
|
||||||
<content_rating type="oars-1.1" />
|
<content_rating type="oars-1.1" />
|
||||||
<description>
|
<description>
|
||||||
<p>
|
<p>
|
||||||
@@ -29,13 +32,12 @@
|
|||||||
<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>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>
|
||||||
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
||||||
<li>Streaming support for Subsonic-compatible servers</li>
|
<li>Streaming support for Subsonic-compatible servers</li>
|
||||||
<li>Unofficial streaming support for Tidal and Qobuz</li>
|
<li>Unofficial streaming support for Tidal, Spotify and Qobuz</li>
|
||||||
</ul>
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
<screenshots>
|
<screenshots>
|
||||||
@@ -50,6 +52,10 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="1.1.2" date="2024-09-12"/>
|
||||||
|
<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.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"/>
|
||||||
<release version="1.0.20" date="2023-09-24"/>
|
<release version="1.0.20" date="2023-09-24"/>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
133
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"
|
||||||
@@ -282,8 +286,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"
|
||||||
@@ -323,12 +329,12 @@ Section "Strawberry" Strawberry
|
|||||||
File "libtasn1-6.dll"
|
File "libtasn1-6.dll"
|
||||||
File "libtwolame-0.dll"
|
File "libtwolame-0.dll"
|
||||||
File "libunistring-5.dll"
|
File "libunistring-5.dll"
|
||||||
|
File "libutf8_validity.dll"
|
||||||
File "libvorbis-0.dll"
|
File "libvorbis-0.dll"
|
||||||
File "libvorbisenc-2.dll"
|
File "libvorbisenc-2.dll"
|
||||||
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 +428,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"
|
||||||
@@ -444,6 +452,7 @@ Section "Strawberry" Strawberry
|
|||||||
File "liblzma.dll"
|
File "liblzma.dll"
|
||||||
File "libmp3lame.dll"
|
File "libmp3lame.dll"
|
||||||
File "libopenmpt.dll"
|
File "libopenmpt.dll"
|
||||||
|
File "utf8_validity.dll"
|
||||||
File "mpcdec.dll"
|
File "mpcdec.dll"
|
||||||
File "mpg123.dll"
|
File "mpg123.dll"
|
||||||
File "nghttp2.dll"
|
File "nghttp2.dll"
|
||||||
@@ -465,7 +474,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 +484,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 +500,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 +508,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 +518,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"
|
||||||
@@ -520,13 +529,13 @@ Section "Strawberry" Strawberry
|
|||||||
File "Qt6Widgets.dll"
|
File "Qt6Widgets.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
File "avcodec-60.dll"
|
File "avcodec-61.dll"
|
||||||
File "avfilter-9.dll"
|
File "avfilter-10.dll"
|
||||||
File "avformat-60.dll"
|
File "avformat-61.dll"
|
||||||
File "avutil-58.dll"
|
File "avutil-59.dll"
|
||||||
File "postproc-57.dll"
|
File "postproc-58.dll"
|
||||||
File "swresample-4.dll"
|
File "swresample-5.dll"
|
||||||
File "swscale-7.dll"
|
File "swscale-8.dll"
|
||||||
|
|
||||||
; Register Strawberry with Default Programs
|
; Register Strawberry with Default Programs
|
||||||
Var /GLOBAL AppIcon
|
Var /GLOBAL AppIcon
|
||||||
@@ -584,9 +593,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 +636,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 +645,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 +667,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 +693,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 +701,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 +733,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 +759,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
|
||||||
@@ -839,8 +859,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"
|
||||||
@@ -880,12 +902,12 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libtasn1-6.dll"
|
Delete "$INSTDIR\libtasn1-6.dll"
|
||||||
Delete "$INSTDIR\libtwolame-0.dll"
|
Delete "$INSTDIR\libtwolame-0.dll"
|
||||||
Delete "$INSTDIR\libunistring-5.dll"
|
Delete "$INSTDIR\libunistring-5.dll"
|
||||||
|
Delete "$INSTDIR\libutf8_validity.dll"
|
||||||
Delete "$INSTDIR\libvorbis-0.dll"
|
Delete "$INSTDIR\libvorbis-0.dll"
|
||||||
Delete "$INSTDIR\libvorbisenc-2.dll"
|
Delete "$INSTDIR\libvorbisenc-2.dll"
|
||||||
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 +1001,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"
|
||||||
@@ -1001,6 +1025,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\liblzma.dll"
|
Delete "$INSTDIR\liblzma.dll"
|
||||||
Delete "$INSTDIR\libmp3lame.dll"
|
Delete "$INSTDIR\libmp3lame.dll"
|
||||||
Delete "$INSTDIR\libopenmpt.dll"
|
Delete "$INSTDIR\libopenmpt.dll"
|
||||||
|
Delete "$INSTDIR\utf8_validity.dll"
|
||||||
Delete "$INSTDIR\mpcdec.dll"
|
Delete "$INSTDIR\mpcdec.dll"
|
||||||
Delete "$INSTDIR\mpg123.dll"
|
Delete "$INSTDIR\mpg123.dll"
|
||||||
Delete "$INSTDIR\nghttp2.dll"
|
Delete "$INSTDIR\nghttp2.dll"
|
||||||
@@ -1022,7 +1047,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 +1057,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 +1072,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 +1080,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 +1090,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"
|
||||||
@@ -1076,13 +1101,13 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\Qt6Widgets.dll"
|
Delete "$INSTDIR\Qt6Widgets.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
Delete "$INSTDIR\avcodec-60.dll"
|
Delete "$INSTDIR\avcodec-61.dll"
|
||||||
Delete "$INSTDIR\avfilter-9.dll"
|
Delete "$INSTDIR\avfilter-10.dll"
|
||||||
Delete "$INSTDIR\avformat-60.dll"
|
Delete "$INSTDIR\avformat-61.dll"
|
||||||
Delete "$INSTDIR\avutil-58.dll"
|
Delete "$INSTDIR\avutil-59.dll"
|
||||||
Delete "$INSTDIR\postproc-57.dll"
|
Delete "$INSTDIR\postproc-58.dll"
|
||||||
Delete "$INSTDIR\swresample-4.dll"
|
Delete "$INSTDIR\swresample-5.dll"
|
||||||
Delete "$INSTDIR\swscale-7.dll"
|
Delete "$INSTDIR\swscale-8.dll"
|
||||||
|
|
||||||
!ifdef mingw
|
!ifdef mingw
|
||||||
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
||||||
@@ -1095,7 +1120,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 +1129,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 +1141,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 +1150,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 +1172,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 +1198,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 +1208,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 +1240,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 +1267,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"
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
set(SOURCES gstfastspectrum.cpp gstmoodbarplugin.cpp)
|
set(SOURCES gstfastspectrum.cpp gstmoodbarplugin.cpp)
|
||||||
|
|
||||||
link_directories(
|
|
||||||
${GLIB_LIBRARY_DIRS}
|
|
||||||
${GOBJECT_LIBRARY_DIRS}
|
|
||||||
${GSTREAMER_LIBRARY_DIRS}
|
|
||||||
${GSTREAMER_BASE_LIBRARY_DIRS}
|
|
||||||
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
|
||||||
${FFTW3_LIBRARY_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(gstmoodbar STATIC ${SOURCES})
|
add_library(gstmoodbar STATIC ${SOURCES})
|
||||||
|
|
||||||
target_include_directories(gstmoodbar SYSTEM PRIVATE
|
target_include_directories(gstmoodbar SYSTEM PRIVATE
|
||||||
@@ -24,6 +15,15 @@ target_include_directories(gstmoodbar SYSTEM PRIVATE
|
|||||||
|
|
||||||
target_include_directories(gstmoodbar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(gstmoodbar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
target_link_directories(gstmoodbar PRIVATE
|
||||||
|
${GLIB_LIBRARY_DIRS}
|
||||||
|
${GOBJECT_LIBRARY_DIRS}
|
||||||
|
${GSTREAMER_LIBRARY_DIRS}
|
||||||
|
${GSTREAMER_BASE_LIBRARY_DIRS}
|
||||||
|
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
||||||
|
${FFTW3_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(gstmoodbar PRIVATE
|
target_link_libraries(gstmoodbar PRIVATE
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
${GOBJECT_LIBRARIES}
|
${GOBJECT_LIBRARIES}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
core/logging.cpp
|
core/logging.cpp
|
||||||
@@ -16,8 +16,6 @@ set(HEADERS
|
|||||||
|
|
||||||
qt_wrap_cpp(MOC ${HEADERS})
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
|
|
||||||
link_directories(${GLIB_LIBRARY_DIRS})
|
|
||||||
|
|
||||||
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
|
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
|
||||||
|
|
||||||
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${GLIB_INCLUDE_DIRS})
|
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${GLIB_INCLUDE_DIRS})
|
||||||
@@ -31,6 +29,8 @@ if(Backtrace_FOUND)
|
|||||||
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${Backtrace_INCLUDE_DIRS})
|
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${Backtrace_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
target_link_directories(libstrawberry-common PRIVATE ${GLIB_LIBRARY_DIRS})
|
||||||
|
|
||||||
target_link_libraries(libstrawberry-common PRIVATE
|
target_link_libraries(libstrawberry-common PRIVATE
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <utility>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
# include <cxxabi.h>
|
# include <cxxabi.h>
|
||||||
@@ -67,8 +69,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 +137,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,12 +159,13 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const QString &line : message.split('\n')) {
|
const QStringList lines = message.split(QLatin1Char('\n'));
|
||||||
BufferedDebug d = CreateLogger<BufferedDebug>(level, "unknown", -1, nullptr);
|
for (const QString &line : lines) {
|
||||||
|
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();
|
||||||
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().data());
|
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().constData());
|
||||||
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,8 +196,9 @@ void SetLevels(const QString &levels) {
|
|||||||
|
|
||||||
if (!sClassLevels) return;
|
if (!sClassLevels) return;
|
||||||
|
|
||||||
for (const QString &item : levels.split(',')) {
|
const QStringList items = levels.split(QLatin1Char(','));
|
||||||
const QStringList class_level = item.split(':');
|
for (const QString &item : items) {
|
||||||
|
const QStringList class_level = item.split(QLatin1Char(':'));
|
||||||
|
|
||||||
QString class_name;
|
QString class_name;
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
@@ -212,7 +216,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 +229,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 +241,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 +263,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 +276,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 +288,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,8 +314,8 @@ 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("\\(([^+]+)");
|
static const QRegularExpression regex_symbol(QStringLiteral("\\(([^+]+)"));
|
||||||
QRegularExpressionMatch match = regex.match(symbol);
|
QRegularExpressionMatch match = regex_symbol.match(symbol);
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
@@ -326,9 +330,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);
|
||||||
@@ -370,20 +374,49 @@ void DumpStackTrace() {
|
|||||||
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It doesn't override any behavior that should be needed after return.
|
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It doesn't override any behavior that should be needed after return.
|
||||||
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
|
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
|
||||||
|
|
||||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
|
QDebug CreateLoggerFatal(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Fatal); }
|
||||||
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Fatal); }
|
QDebug CreateLoggerError(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Error); }
|
||||||
QDebug CreateLoggerError(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Error); }
|
|
||||||
|
#ifdef QT_NO_INFO_OUTPUT
|
||||||
|
QNoDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category) {
|
||||||
|
|
||||||
|
Q_UNUSED(line)
|
||||||
|
Q_UNUSED(pretty_function)
|
||||||
|
Q_UNUSED(category)
|
||||||
|
|
||||||
|
return QNoDebug();
|
||||||
|
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
QDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
|
||||||
|
#endif // QT_NO_INFO_OUTPUT
|
||||||
|
|
||||||
#ifdef QT_NO_WARNING_OUTPUT
|
#ifdef QT_NO_WARNING_OUTPUT
|
||||||
QNoDebug CreateLoggerWarning(int, const char*, const char*) { return QNoDebug(); }
|
QNoDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category) {
|
||||||
|
|
||||||
|
Q_UNUSED(line)
|
||||||
|
Q_UNUSED(pretty_function)
|
||||||
|
Q_UNUSED(category)
|
||||||
|
|
||||||
|
return QNoDebug();
|
||||||
|
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
|
QDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
|
||||||
#endif // QT_NO_WARNING_OUTPUT
|
#endif // QT_NO_WARNING_OUTPUT
|
||||||
|
|
||||||
#ifdef QT_NO_DEBUG_OUTPUT
|
#ifdef QT_NO_DEBUG_OUTPUT
|
||||||
QNoDebug CreateLoggerDebug(int, const char*, const char*) { return QNoDebug(); }
|
QNoDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category) {
|
||||||
|
|
||||||
|
Q_UNUSED(line)
|
||||||
|
Q_UNUSED(pretty_function)
|
||||||
|
Q_UNUSED(category)
|
||||||
|
|
||||||
|
return QNoDebug();
|
||||||
|
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
|
QDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
|
||||||
#endif // QT_NO_DEBUG_OUTPUT
|
#endif // QT_NO_DEBUG_OUTPUT
|
||||||
|
|
||||||
} // namespace logging
|
} // namespace logging
|
||||||
@@ -392,7 +425,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
|
||||||
|
|||||||
@@ -72,20 +72,25 @@ enum Level {
|
|||||||
|
|
||||||
void DumpStackTrace();
|
void DumpStackTrace();
|
||||||
|
|
||||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category);
|
QDebug CreateLoggerFatal(const int line, const char *pretty_function, const char *category);
|
||||||
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char *category);
|
QDebug CreateLoggerError(const int line, const char *pretty_function, const char *category);
|
||||||
QDebug CreateLoggerError(int line, const char *pretty_function, const char *category);
|
|
||||||
|
#ifdef QT_NO_INFO_OUTPUT
|
||||||
|
QNoDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category);
|
||||||
|
#else
|
||||||
|
QDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category);
|
||||||
|
#endif // QT_NO_INFO_OUTPUT
|
||||||
|
|
||||||
#ifdef QT_NO_WARNING_OUTPUT
|
#ifdef QT_NO_WARNING_OUTPUT
|
||||||
QNoDebug CreateLoggerWarning(int, const char*, const char*);
|
QNoDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category);
|
||||||
#else
|
#else
|
||||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category);
|
QDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category);
|
||||||
#endif // QT_NO_WARNING_OUTPUT
|
#endif // QT_NO_WARNING_OUTPUT
|
||||||
|
|
||||||
#ifdef QT_NO_DEBUG_OUTPUT
|
#ifdef QT_NO_DEBUG_OUTPUT
|
||||||
QNoDebug CreateLoggerDebug(int, const char*, const char*);
|
QNoDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category);
|
||||||
#else
|
#else
|
||||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category);
|
QDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category);
|
||||||
#endif // QT_NO_DEBUG_OUTPUT
|
#endif // QT_NO_DEBUG_OUTPUT
|
||||||
|
|
||||||
void GLog(const char *domain, int level, const char *message, void *user_data);
|
void GLog(const char *domain, int level, const char *message, void *user_data);
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class _MessageHandlerBase : public QObject {
|
|||||||
// After this is true, messages cannot be sent to the handler any more.
|
// After this is true, messages cannot be sent to the handler any more.
|
||||||
bool is_device_closed() const { return is_device_closed_; }
|
bool is_device_closed() const { return is_device_closed_; }
|
||||||
|
|
||||||
protected slots:
|
protected Q_SLOTS:
|
||||||
void WriteMessage(const QByteArray &data);
|
void WriteMessage(const QByteArray &data);
|
||||||
void DeviceReadyRead();
|
void DeviceReadyRead();
|
||||||
virtual void DeviceClosed();
|
virtual void DeviceClosed();
|
||||||
@@ -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())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ void _MessageReplyBase::Abort() {
|
|||||||
finished_ = true;
|
finished_ = true;
|
||||||
success_ = false;
|
success_ = false;
|
||||||
|
|
||||||
emit Finished();
|
Q_EMIT Finished();
|
||||||
qLog(Debug) << "Releasing ID" << id() << "(aborted)";
|
qLog(Debug) << "Releasing ID" << id() << "(aborted)";
|
||||||
semaphore_.release();
|
semaphore_.release();
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class _MessageReplyBase : public QObject {
|
|||||||
|
|
||||||
void Abort();
|
void Abort();
|
||||||
|
|
||||||
signals:
|
Q_SIGNALS:
|
||||||
void Finished();
|
void Finished();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -51,11 +52,11 @@ class _WorkerPoolBase : public QObject {
|
|||||||
public:
|
public:
|
||||||
explicit _WorkerPoolBase(QObject *parent = nullptr);
|
explicit _WorkerPoolBase(QObject *parent = nullptr);
|
||||||
|
|
||||||
signals:
|
Q_SIGNALS:
|
||||||
// Emitted when a worker failed to start. This usually happens when the worker wasn't found, or couldn't be executed.
|
// Emitted when a worker failed to start. This usually happens when the worker wasn't found, or couldn't be executed.
|
||||||
void WorkerFailedToStart();
|
void WorkerFailedToStart();
|
||||||
|
|
||||||
protected slots:
|
protected Q_SLOTS:
|
||||||
virtual void DoStart() {}
|
virtual void DoStart() {}
|
||||||
virtual void NewConnection() {}
|
virtual void NewConnection() {}
|
||||||
virtual void ProcessReadyReadStandardOutput() {}
|
virtual void ProcessReadyReadStandardOutput() {}
|
||||||
@@ -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;
|
||||||
@@ -292,9 +293,9 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
|||||||
QObject::connect(worker->process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
|
QObject::connect(worker->process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
|
||||||
|
|
||||||
// Create a server, find an unused name and start listening
|
// Create a server, find an unused name and start listening
|
||||||
forever {
|
Q_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;
|
||||||
@@ -356,7 +357,7 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
|
|||||||
// Failed to start errors are bad - it usually means the worker isn't installed.
|
// Failed to start errors are bad - it usually means the worker isn't installed.
|
||||||
// Don't restart the process, but tell our owner, who will probably want to do something fatal.
|
// Don't restart the process, but tell our owner, who will probably want to do something fatal.
|
||||||
qLog(Error) << "Worker failed to start";
|
qLog(Error) << "Worker failed to start";
|
||||||
emit WorkerFailedToStart();
|
Q_EMIT WorkerFailedToStart();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
# Workaround a bug in protobuf-generate.cmake (https://github.com/protocolbuffers/protobuf/issues/12450)
|
# Workaround a bug in protobuf-generate.cmake (https://github.com/protocolbuffers/protobuf/issues/12450)
|
||||||
if(NOT protobuf_PROTOC_EXE)
|
if(NOT protobuf_PROTOC_EXE)
|
||||||
@@ -11,27 +11,14 @@ 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()
|
||||||
|
|
||||||
link_directories(
|
|
||||||
${GLIB_LIBRARY_DIRS}
|
|
||||||
${PROTOBUF_LIBRARY_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
|
||||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
|
||||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
|
add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
|
||||||
|
|
||||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
|
||||||
@@ -47,6 +34,11 @@ target_include_directories(libstrawberry-tagreader PRIVATE
|
|||||||
${CMAKE_BINARY_DIR}/src
|
${CMAKE_BINARY_DIR}/src
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_link_directories(libstrawberry-tagreader PRIVATE
|
||||||
|
${GLIB_LIBRARY_DIRS}
|
||||||
|
${PROTOBUF_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
${Protobuf_LIBRARIES}
|
${Protobuf_LIBRARIES}
|
||||||
@@ -56,13 +48,15 @@ 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_directories(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARY_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_directories(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARY_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,10 +223,13 @@ 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);
|
||||||
|
Q_UNUSED(gd3_version)
|
||||||
|
|
||||||
file.seek(file.pos() + 4);
|
file.seek(file.pos() + 4);
|
||||||
QByteArray gd3_length_bytes = file.read(4);
|
QByteArray gd3_length_bytes = file.read(4);
|
||||||
@@ -226,19 +243,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 +288,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,57 +55,89 @@ 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;
|
||||||
|
|
||||||
|
static TagLib::String TagLibStringListToSlashSeparatedString(const TagLib::StringList &taglib_string_list);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileRefFactory *factory_;
|
FileRefFactory *factory_;
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
qt_wrap_cpp(MACDEPLOYCHECK_MOC ${CMAKE_SOURCE_DIR}/ext/libstrawberry-common/core/logging.h)
|
qt_wrap_cpp(MACDEPLOYCHECK_MOC ${CMAKE_SOURCE_DIR}/ext/libstrawberry-common/core/logging.h)
|
||||||
link_directories(${GLIB_LIBRARY_DIRS})
|
|
||||||
add_executable(macdeploycheck macdeploycheck.cpp ${CMAKE_SOURCE_DIR}/ext/libstrawberry-common/core/logging.cpp ${MACDEPLOYCHECK_MOC})
|
add_executable(macdeploycheck macdeploycheck.cpp ${CMAKE_SOURCE_DIR}/ext/libstrawberry-common/core/logging.cpp ${MACDEPLOYCHECK_MOC})
|
||||||
target_include_directories(macdeploycheck PUBLIC SYSTEM
|
target_include_directories(macdeploycheck PUBLIC SYSTEM
|
||||||
${GLIB_INCLUDE_DIRS}
|
${GLIB_INCLUDE_DIRS}
|
||||||
@@ -8,6 +10,7 @@ target_include_directories(macdeploycheck PUBLIC
|
|||||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
||||||
${CMAKE_BINARY_DIR}/src
|
${CMAKE_BINARY_DIR}/src
|
||||||
)
|
)
|
||||||
|
target_link_directories(macdeploycheck PUBLIC ${GLIB_LIBRARY_DIRS})
|
||||||
target_link_libraries(macdeploycheck PUBLIC
|
target_link_libraries(macdeploycheck PUBLIC
|
||||||
"-framework AppKit"
|
"-framework AppKit"
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
|
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
@@ -7,16 +7,6 @@ set(HEADERS tagreaderworker.h)
|
|||||||
|
|
||||||
qt_wrap_cpp(MOC ${HEADERS})
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
|
|
||||||
link_directories(${GLIB_LIBRARY_DIRS})
|
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
|
||||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
|
||||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
|
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
|
||||||
|
|
||||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
|
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
|
||||||
@@ -31,6 +21,8 @@ target_include_directories(strawberry-tagreader PRIVATE
|
|||||||
${CMAKE_BINARY_DIR}/src
|
${CMAKE_BINARY_DIR}/src
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_link_directories(strawberry-tagreader PRIVATE ${GLIB_LIBRARY_DIRS})
|
||||||
|
|
||||||
target_link_libraries(strawberry-tagreader PRIVATE
|
target_link_libraries(strawberry-tagreader PRIVATE
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
Qt${QT_VERSION_MAJOR}::Core
|
Qt${QT_VERSION_MAJOR}::Core
|
||||||
@@ -39,13 +31,15 @@ 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_directories(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARY_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_directories(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARY_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,8 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
@@ -27,20 +29,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 +72,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 : std::as_const(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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
if(HAVE_TRANSLATIONS)
|
if(HAVE_TRANSLATIONS)
|
||||||
include(../cmake/Translations.cmake)
|
include(../cmake/Translations.cmake)
|
||||||
@@ -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,10 @@ set(SOURCES
|
|||||||
core/scopedtransaction.cpp
|
core/scopedtransaction.cpp
|
||||||
core/translations.cpp
|
core/translations.cpp
|
||||||
core/systemtrayicon.cpp
|
core/systemtrayicon.cpp
|
||||||
|
core/localredirectserver.cpp
|
||||||
|
core/mimedata.cpp
|
||||||
|
core/potranslator.cpp
|
||||||
|
core/temporaryfile.cpp
|
||||||
utilities/strutils.cpp
|
utilities/strutils.cpp
|
||||||
utilities/envutils.cpp
|
utilities/envutils.cpp
|
||||||
utilities/colorutils.cpp
|
utilities/colorutils.cpp
|
||||||
@@ -57,7 +61,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 +77,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,23 +98,25 @@ 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/playlistitemmimedata.cpp
|
||||||
playlist/playlistlistcontainer.cpp
|
playlist/playlistlistcontainer.cpp
|
||||||
playlist/playlistlistmodel.cpp
|
playlist/playlistlistmodel.cpp
|
||||||
|
playlist/playlistlistsortfiltermodel.cpp
|
||||||
playlist/playlistlistview.cpp
|
playlist/playlistlistview.cpp
|
||||||
playlist/playlistmanager.cpp
|
playlist/playlistmanager.cpp
|
||||||
playlist/playlistsaveoptionsdialog.cpp
|
playlist/playlistsaveoptionsdialog.cpp
|
||||||
@@ -114,6 +125,7 @@ set(SOURCES
|
|||||||
playlist/playlistundocommands.cpp
|
playlist/playlistundocommands.cpp
|
||||||
playlist/playlistview.cpp
|
playlist/playlistview.cpp
|
||||||
playlist/playlistproxystyle.cpp
|
playlist/playlistproxystyle.cpp
|
||||||
|
playlist/songmimedata.cpp
|
||||||
playlist/songloaderinserter.cpp
|
playlist/songloaderinserter.cpp
|
||||||
playlist/songplaylistitem.cpp
|
playlist/songplaylistitem.cpp
|
||||||
playlist/dynamicplaylistcontrols.cpp
|
playlist/dynamicplaylistcontrols.cpp
|
||||||
@@ -134,17 +146,23 @@ set(SOURCES
|
|||||||
|
|
||||||
smartplaylists/playlistgenerator.cpp
|
smartplaylists/playlistgenerator.cpp
|
||||||
smartplaylists/playlistgeneratorinserter.cpp
|
smartplaylists/playlistgeneratorinserter.cpp
|
||||||
|
smartplaylists/playlistgeneratormimedata.cpp
|
||||||
smartplaylists/playlistquerygenerator.cpp
|
smartplaylists/playlistquerygenerator.cpp
|
||||||
smartplaylists/smartplaylistquerywizardplugin.cpp
|
smartplaylists/smartplaylistquerywizardplugin.cpp
|
||||||
|
smartplaylists/smartplaylistquerywizardpluginsortpage.cpp
|
||||||
|
smartplaylists/smartplaylistquerywizardpluginsearchpage.cpp
|
||||||
smartplaylists/smartplaylistsearch.cpp
|
smartplaylists/smartplaylistsearch.cpp
|
||||||
smartplaylists/smartplaylistsearchpreview.cpp
|
smartplaylists/smartplaylistsearchpreview.cpp
|
||||||
smartplaylists/smartplaylistsearchterm.cpp
|
smartplaylists/smartplaylistsearchterm.cpp
|
||||||
smartplaylists/smartplaylistsearchtermwidget.cpp
|
smartplaylists/smartplaylistsearchtermwidget.cpp
|
||||||
|
smartplaylists/smartplaylistsearchtermwidgetoverlay.cpp
|
||||||
smartplaylists/smartplaylistsmodel.cpp
|
smartplaylists/smartplaylistsmodel.cpp
|
||||||
smartplaylists/smartplaylistsviewcontainer.cpp
|
smartplaylists/smartplaylistsviewcontainer.cpp
|
||||||
smartplaylists/smartplaylistsview.cpp
|
smartplaylists/smartplaylistsview.cpp
|
||||||
smartplaylists/smartplaylistwizard.cpp
|
smartplaylists/smartplaylistwizard.cpp
|
||||||
smartplaylists/smartplaylistwizardplugin.cpp
|
smartplaylists/smartplaylistwizardplugin.cpp
|
||||||
|
smartplaylists/smartplaylistwizardtypepage.cpp
|
||||||
|
smartplaylists/smartplaylistwizardfinishpage.cpp
|
||||||
|
|
||||||
covermanager/albumcovermanager.cpp
|
covermanager/albumcovermanager.cpp
|
||||||
covermanager/albumcovermanagerlist.cpp
|
covermanager/albumcovermanagerlist.cpp
|
||||||
@@ -170,7 +188,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 +206,8 @@ 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
|
||||||
|
lyrics/lyricfindlyricsprovider.cpp
|
||||||
|
|
||||||
providers/musixmatchprovider.cpp
|
providers/musixmatchprovider.cpp
|
||||||
|
|
||||||
@@ -196,6 +215,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 +243,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 +271,19 @@ 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
|
streaming/streamsongmimedata.cpp
|
||||||
|
|
||||||
radios/radioservices.cpp
|
radios/radioservices.cpp
|
||||||
radios/radiobackend.cpp
|
radios/radiobackend.cpp
|
||||||
@@ -273,6 +295,7 @@ set(SOURCES
|
|||||||
radios/radiochannel.cpp
|
radios/radiochannel.cpp
|
||||||
radios/somafmservice.cpp
|
radios/somafmservice.cpp
|
||||||
radios/radioparadiseservice.cpp
|
radios/radioparadiseservice.cpp
|
||||||
|
radios/radiomimedata.cpp
|
||||||
|
|
||||||
scrobbler/audioscrobbler.cpp
|
scrobbler/audioscrobbler.cpp
|
||||||
scrobbler/scrobblersettings.cpp
|
scrobbler/scrobblersettings.cpp
|
||||||
@@ -288,6 +311,8 @@ set(SOURCES
|
|||||||
|
|
||||||
organize/organize.cpp
|
organize/organize.cpp
|
||||||
organize/organizeformat.cpp
|
organize/organizeformat.cpp
|
||||||
|
organize/organizeformatvalidator.cpp
|
||||||
|
organize/organizesyntaxhighlighter.cpp
|
||||||
organize/organizedialog.cpp
|
organize/organizedialog.cpp
|
||||||
organize/organizeerrordialog.cpp
|
organize/organizeerrordialog.cpp
|
||||||
|
|
||||||
@@ -306,6 +331,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 +342,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 +351,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 +371,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
|
||||||
|
|
||||||
@@ -385,13 +415,18 @@ set(HEADERS
|
|||||||
smartplaylists/playlistquerygenerator.h
|
smartplaylists/playlistquerygenerator.h
|
||||||
smartplaylists/playlistgeneratormimedata.h
|
smartplaylists/playlistgeneratormimedata.h
|
||||||
smartplaylists/smartplaylistquerywizardplugin.h
|
smartplaylists/smartplaylistquerywizardplugin.h
|
||||||
|
smartplaylists/smartplaylistquerywizardpluginsortpage.h
|
||||||
|
smartplaylists/smartplaylistquerywizardpluginsearchpage.h
|
||||||
smartplaylists/smartplaylistsearchpreview.h
|
smartplaylists/smartplaylistsearchpreview.h
|
||||||
smartplaylists/smartplaylistsearchtermwidget.h
|
smartplaylists/smartplaylistsearchtermwidget.h
|
||||||
|
smartplaylists/smartplaylistsearchtermwidgetoverlay.h
|
||||||
smartplaylists/smartplaylistsmodel.h
|
smartplaylists/smartplaylistsmodel.h
|
||||||
smartplaylists/smartplaylistsviewcontainer.h
|
smartplaylists/smartplaylistsviewcontainer.h
|
||||||
smartplaylists/smartplaylistsview.h
|
smartplaylists/smartplaylistsview.h
|
||||||
smartplaylists/smartplaylistwizard.h
|
smartplaylists/smartplaylistwizard.h
|
||||||
smartplaylists/smartplaylistwizardplugin.h
|
smartplaylists/smartplaylistwizardplugin.h
|
||||||
|
smartplaylists/smartplaylistwizardtypepage.h
|
||||||
|
smartplaylists/smartplaylistwizardfinishpage.h
|
||||||
|
|
||||||
covermanager/albumcovermanager.h
|
covermanager/albumcovermanager.h
|
||||||
covermanager/albumcovermanagerlist.h
|
covermanager/albumcovermanagerlist.h
|
||||||
@@ -415,7 +450,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 +466,14 @@ 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
|
||||||
|
lyrics/lyricfindlyricsprovider.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 +501,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
|
||||||
@@ -483,25 +522,25 @@ set(HEADERS
|
|||||||
widgets/tracksliderpopup.h
|
widgets/tracksliderpopup.h
|
||||||
widgets/tracksliderslider.h
|
widgets/tracksliderslider.h
|
||||||
widgets/loginstatewidget.h
|
widgets/loginstatewidget.h
|
||||||
widgets/qsearchfield.h
|
widgets/searchfield.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
|
||||||
@@ -524,6 +563,8 @@ set(HEADERS
|
|||||||
scrobbler/lastfmimport.h
|
scrobbler/lastfmimport.h
|
||||||
|
|
||||||
organize/organize.h
|
organize/organize.h
|
||||||
|
organize/organizeformatvalidator.h
|
||||||
|
organize/organizesyntaxhighlighter.h
|
||||||
organize/organizedialog.h
|
organize/organizedialog.h
|
||||||
organize/organizeerrordialog.h
|
organize/organizeerrordialog.h
|
||||||
|
|
||||||
@@ -592,9 +633,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
|
||||||
|
|
||||||
@@ -609,7 +650,7 @@ option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON)
|
|||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
set(NOT_APPLE ON)
|
set(NOT_APPLE ON)
|
||||||
optional_source(NOT_APPLE SOURCES widgets/qsearchfield_qt.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h)
|
optional_source(NOT_APPLE SOURCES widgets/searchfield_qt.cpp widgets/searchfield_qt_private.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h widgets/searchfield_qt_private.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_GLOBALSHORTCUTS)
|
if(HAVE_GLOBALSHORTCUTS)
|
||||||
@@ -748,6 +789,7 @@ optional_source(HAVE_LIBPULSE SOURCES engine/pulsedevicefinder.cpp)
|
|||||||
optional_source(HAVE_GSTREAMER
|
optional_source(HAVE_GSTREAMER
|
||||||
SOURCES
|
SOURCES
|
||||||
transcoder/transcoder.cpp
|
transcoder/transcoder.cpp
|
||||||
|
transcoder/transcoderoptionsinterface.cpp
|
||||||
transcoder/transcodedialog.cpp
|
transcoder/transcodedialog.cpp
|
||||||
transcoder/transcoderoptionsdialog.cpp
|
transcoder/transcoderoptionsdialog.cpp
|
||||||
transcoder/transcoderoptionsflac.cpp
|
transcoder/transcoderoptionsflac.cpp
|
||||||
@@ -826,7 +868,7 @@ optional_source(APPLE
|
|||||||
core/macsystemtrayicon.mm
|
core/macsystemtrayicon.mm
|
||||||
core/macfslistener.mm
|
core/macfslistener.mm
|
||||||
osd/osdmac.mm
|
osd/osdmac.mm
|
||||||
widgets/qsearchfield_mac.mm
|
widgets/searchfield_mac.mm
|
||||||
engine/macosdevicefinder.cpp
|
engine/macosdevicefinder.cpp
|
||||||
globalshortcuts/globalshortcutsbackend-macos.mm
|
globalshortcuts/globalshortcutsbackend-macos.mm
|
||||||
globalshortcuts/globalshortcutgrabber.mm
|
globalshortcuts/globalshortcutgrabber.mm
|
||||||
@@ -850,7 +892,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 +938,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
|
||||||
@@ -969,6 +1030,7 @@ if(HAVE_TRANSLATIONS)
|
|||||||
endif(NOT LINGUAS OR LINGUAS STREQUAL "None")
|
endif(NOT LINGUAS OR LINGUAS STREQUAL "None")
|
||||||
endif(LINGUAS STREQUAL "All")
|
endif(LINGUAS STREQUAL "All")
|
||||||
|
|
||||||
|
if(NOT MSVC)
|
||||||
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_SOURCE_DIR}/translations/translations.pot
|
||||||
@@ -977,92 +1039,11 @@ if(HAVE_TRANSLATIONS)
|
|||||||
${UIC}
|
${UIC}
|
||||||
${CMAKE_SOURCE_DIR}/data/html/oauthsuccess.html
|
${CMAKE_SOURCE_DIR}/data/html/oauthsuccess.html
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
add_po(PO strawberry_ LANGUAGES ${LANGUAGES} DIRECTORY translations)
|
add_po(PO strawberry_ LANGUAGES ${LANGUAGES} DIRECTORY translations)
|
||||||
|
|
||||||
endif(HAVE_TRANSLATIONS)
|
endif(HAVE_TRANSLATIONS)
|
||||||
|
|
||||||
link_directories(
|
|
||||||
${Boost_LIBRARY_DIRS}
|
|
||||||
${GLIB_LIBRARY_DIRS}
|
|
||||||
${GOBJECT_LIBRARY_DIRS}
|
|
||||||
${SQLITE_LIBRARY_DIRS}
|
|
||||||
${PROTOBUF_LIBRARY_DIRS}
|
|
||||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
if(HAVE_ICU)
|
|
||||||
link_directories(${ICU_LIBRARY_DIRS})
|
|
||||||
else()
|
|
||||||
link_directories(${Iconv_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_ALSA)
|
|
||||||
link_directories(${ALSA_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_LIBPULSE)
|
|
||||||
link_directories(${LIBPULSE_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_GSTREAMER)
|
|
||||||
link_directories(
|
|
||||||
${GSTREAMER_LIBRARY_DIRS}
|
|
||||||
${GSTREAMER_BASE_LIBRARY_DIRS}
|
|
||||||
${GSTREAMER_APP_LIBRARY_DIRS}
|
|
||||||
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
|
||||||
${GSTREAMER_TAG_LIBRARY_DIRS}
|
|
||||||
${GSTREAMER_PBUTILS_LIBRARY_DIRS}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_VLC)
|
|
||||||
link_directories(${LIBVLC_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
|
||||||
link_directories(${CHROMAPRINT_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(X11_FOUND)
|
|
||||||
link_directories(${X11_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(XCB_FOUND)
|
|
||||||
link_directories(${XCB_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_GIO)
|
|
||||||
link_directories(${GIO_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_GIO_UNIX)
|
|
||||||
link_directories(${GIO_UNIX_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_AUDIOCD)
|
|
||||||
link_directories(${LIBCDIO_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_LIBGPOD)
|
|
||||||
link_directories(${LIBGPOD_LIBRARY_DIRS} ${GDK_PIXBUF_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_LIBMTP)
|
|
||||||
link_directories(${LIBMTP_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
|
||||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
|
||||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_QTSPARKLE)
|
|
||||||
link_directories(${QTSPARKLE_LIBRARY_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(strawberry_lib STATIC
|
add_library(strawberry_lib STATIC
|
||||||
${SOURCES}
|
${SOURCES}
|
||||||
${MOC}
|
${MOC}
|
||||||
@@ -1079,6 +1060,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)
|
||||||
@@ -1096,11 +1078,22 @@ target_include_directories(strawberry_lib PUBLIC
|
|||||||
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_link_directories(strawberry_lib PUBLIC
|
||||||
|
${Boost_LIBRARY_DIRS}
|
||||||
|
${GLIB_LIBRARY_DIRS}
|
||||||
|
${GOBJECT_LIBRARY_DIRS}
|
||||||
|
${SQLITE_LIBRARY_DIRS}
|
||||||
|
${PROTOBUF_LIBRARY_DIRS}
|
||||||
|
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||||
|
${ICU_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(strawberry_lib PUBLIC
|
target_link_libraries(strawberry_lib PUBLIC
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
${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,24 +1114,15 @@ 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_directories(strawberry_lib PRIVATE ${ALSA_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBPULSE)
|
if(HAVE_LIBPULSE)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBPULSE_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBPULSE_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${LIBPULSE_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBPULSE_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBPULSE_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -1151,6 +1135,14 @@ if(HAVE_GSTREAMER)
|
|||||||
${GSTREAMER_TAG_INCLUDE_DIRS}
|
${GSTREAMER_TAG_INCLUDE_DIRS}
|
||||||
${GSTREAMER_PBUTILS_INCLUDE_DIRS}
|
${GSTREAMER_PBUTILS_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
target_link_directories(strawberry_lib PRIVATE
|
||||||
|
${GSTREAMER_LIBRARY_DIRS}
|
||||||
|
${GSTREAMER_BASE_LIBRARY_DIRS}
|
||||||
|
${GSTREAMER_APP_LIBRARY_DIRS}
|
||||||
|
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
||||||
|
${GSTREAMER_TAG_LIBRARY_DIRS}
|
||||||
|
${GSTREAMER_PBUTILS_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
target_link_libraries(strawberry_lib PRIVATE
|
target_link_libraries(strawberry_lib PRIVATE
|
||||||
${GSTREAMER_LIBRARIES}
|
${GSTREAMER_LIBRARIES}
|
||||||
${GSTREAMER_BASE_LIBRARIES}
|
${GSTREAMER_BASE_LIBRARIES}
|
||||||
@@ -1167,11 +1159,13 @@ endif()
|
|||||||
|
|
||||||
if(HAVE_VLC)
|
if(HAVE_VLC)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBVLC_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBVLC_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${LIBVLC_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBVLC_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBVLC_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${CHROMAPRINT_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${CHROMAPRINT_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${CHROMAPRINT_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${CHROMAPRINT_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${CHROMAPRINT_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -1181,36 +1175,43 @@ endif()
|
|||||||
|
|
||||||
if(X11_FOUND)
|
if(X11_FOUND)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${X11_INCLUDE_DIR})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${X11_INCLUDE_DIR})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${X11_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${X11_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${X11_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(XCB_FOUND)
|
if(XCB_FOUND)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${XCB_INCLUDE_DIR})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${XCB_INCLUDE_DIR})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${XCB_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${XCB_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${XCB_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_GIO)
|
if(HAVE_GIO)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${GIO_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${GIO_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${GIO_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_GIO_UNIX)
|
if(HAVE_GIO_UNIX)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_UNIX_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_UNIX_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${GIO_UNIX_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${GIO_UNIX_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${GIO_UNIX_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_AUDIOCD)
|
if(HAVE_AUDIOCD)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBCDIO_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBCDIO_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${LIBCDIO_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBCDIO_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBCDIO_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBGPOD)
|
if(HAVE_LIBGPOD)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS} ${GDK_PIXBUF_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS} ${GDK_PIXBUF_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${LIBGPOD_LIBRARY_DIRS} ${GDK_PIXBUF_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES} ${GDK_PIXBUF_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES} ${GDK_PIXBUF_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBMTP)
|
if(HAVE_LIBMTP)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBMTP_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBMTP_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${LIBMTP_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBMTP_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBMTP_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -1242,6 +1243,7 @@ endif()
|
|||||||
|
|
||||||
if(HAVE_QTSPARKLE)
|
if(HAVE_QTSPARKLE)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${QTSPARKLE_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${QTSPARKLE_INCLUDE_DIRS})
|
||||||
|
target_link_directories(strawberry_lib PRIVATE ${QTSPARKLE_LIBRARY_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${QTSPARKLE_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${QTSPARKLE_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -123,7 +130,7 @@ void AnalyzerContainer::ShowPopupMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AnalyzerContainer::wheelEvent(QWheelEvent *e) {
|
void AnalyzerContainer::wheelEvent(QWheelEvent *e) {
|
||||||
emit WheelEvent(e->angleDelta().y());
|
Q_EMIT WheelEvent(e->angleDelta().y());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnalyzerContainer::SetEngine(SharedPtr<EngineBase> engine) {
|
void AnalyzerContainer::SetEngine(SharedPtr<EngineBase> engine) {
|
||||||
@@ -142,7 +149,7 @@ void AnalyzerContainer::DisableAnalyzer() {
|
|||||||
|
|
||||||
void AnalyzerContainer::ChangeAnalyzer(const int id) {
|
void AnalyzerContainer::ChangeAnalyzer(const int id) {
|
||||||
|
|
||||||
QObject *instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
|
QObject *instance = analyzer_types_.at(id)->newInstance(Q_ARG(QWidget*, this));
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
qLog(Warning) << "Couldn't initialize a new" << analyzer_types_[id]->className();
|
qLog(Warning) << "Couldn't initialize a new" << analyzer_types_[id]->className();
|
||||||
@@ -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,20 +198,27 @@ 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);
|
QAction *action = actions_.value(i);
|
||||||
|
action->setChecked(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!current_analyzer_) {
|
||||||
|
ChangeAnalyzer(0);
|
||||||
|
QAction *action = actions_.value(0);
|
||||||
|
action->setChecked(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Framerate
|
// Framerate
|
||||||
QList<QAction*> actions = group_framerate_->actions();
|
const QList<QAction*> actions = group_framerate_->actions();
|
||||||
for (int i = 0; i < framerate_list_.count(); ++i) {
|
for (int i = 0; i < framerate_list_.count(); ++i) {
|
||||||
if (current_framerate_ == framerate_list_[i]) {
|
if (current_framerate_ == framerate_list_.value(i)) {
|
||||||
ChangeFramerate(current_framerate_);
|
ChangeFramerate(current_framerate_);
|
||||||
actions[i]->setChecked(true);
|
QAction *action = actions[i];
|
||||||
|
action->setChecked(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +229,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 +238,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,30 +46,24 @@ 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;
|
||||||
|
|
||||||
signals:
|
Q_SIGNALS:
|
||||||
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 Q_SLOTS:
|
||||||
void ChangeAnalyzer(const int id);
|
void ChangeAnalyzer(const int id);
|
||||||
void ChangeFramerate(int new_framerate);
|
void ChangeFramerate(int new_framerate);
|
||||||
void DisableAnalyzer();
|
void DisableAnalyzer();
|
||||||
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
|
||||||
// . . . . # .
|
// . . . . # .
|
||||||
@@ -165,11 +167,12 @@ void BlockAnalyzer::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
|||||||
|
|
||||||
for (int x = 0, y = 0; x < static_cast<int>(scope_.size()); ++x) {
|
for (int x = 0, y = 0; x < static_cast<int>(scope_.size()); ++x) {
|
||||||
// determine y
|
// determine y
|
||||||
for (y = 0; scope_[x] < yscale_[y]; ++y);
|
for (y = 0; scope_[x] < yscale_.at(y); ++y);
|
||||||
|
|
||||||
// This is opposite to what you'd think, higher than y means the bar is lower than y (physically)
|
// This is opposite to what you'd think, higher than y means the bar is lower than y (physically)
|
||||||
if (static_cast<double>(y) > store_[x]) {
|
if (static_cast<double>(y) > store_.at(x)) {
|
||||||
y = static_cast<int>(store_[x] += step_);
|
store_[x] += step_;
|
||||||
|
y = static_cast<int>(store_.value(x));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
store_[x] = y;
|
store_[x] = y;
|
||||||
@@ -177,18 +180,19 @@ void BlockAnalyzer::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
|||||||
|
|
||||||
// If y is lower than fade_pos_, then the bar has exceeded the height of the fadeout
|
// If y is lower than fade_pos_, then the bar has exceeded the height of the fadeout
|
||||||
// if the fadeout is quite faded now, then display the new one
|
// if the fadeout is quite faded now, then display the new one
|
||||||
if (y <= fade_pos_[x] /*|| fade_intensity_[x] < kFadeSize / 3*/) {
|
if (y <= fade_pos_.at(x) /*|| fade_intensity_[x] < kFadeSize / 3*/) {
|
||||||
fade_pos_[x] = y;
|
fade_pos_[x] = y;
|
||||||
fade_intensity_[x] = kFadeSize;
|
fade_intensity_[x] = kFadeSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fade_intensity_[x] > 0) {
|
if (fade_intensity_.at(x) > 0) {
|
||||||
const int offset = --fade_intensity_[x];
|
--fade_intensity_[x];
|
||||||
const int y2 = y_ + (fade_pos_[x] * (kHeight + 1));
|
const int offset = fade_intensity_.value(x);
|
||||||
|
const int y2 = y_ + (fade_pos_.value(x) * (kHeight + 1));
|
||||||
canvas_painter.drawPixmap(x * (kWidth + 1), y2, fade_bars_[offset], 0, 0, kWidth, height() - y2);
|
canvas_painter.drawPixmap(x * (kWidth + 1), y2, fade_bars_[offset], 0, 0, kWidth, height() - y2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fade_intensity_[x] == 0) fade_pos_[x] = rows_;
|
if (fade_intensity_.at(x) == 0) fade_pos_[x] = rows_;
|
||||||
|
|
||||||
// REMEMBER: y is a number from 0 to rows_, 0 means all blocks are glowing, rows_ means none are
|
// REMEMBER: y is a number from 0 to rows_, 0 means all blocks are glowing, rows_ means none are
|
||||||
canvas_painter.drawPixmap(x * (kWidth + 1), y * (kHeight + 1) + y_, *bar(), 0, y * (kHeight + 1), bar()->width(), bar()->height());
|
canvas_painter.drawPixmap(x * (kWidth + 1), y * (kHeight + 1) + y_, *bar(), 0, y * (kHeight + 1), bar()->width(), bar()->height());
|
||||||
@@ -266,12 +270,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 +289,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 +298,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 +314,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,9 +45,9 @@ 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 Q_SLOTS:
|
||||||
void changeK_barHeight(int);
|
void changeK_barHeight(int);
|
||||||
void changeF_peakSpeed(int);
|
void changeF_peakSpeed(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";
|
||||||
|
|
||||||
@@ -63,13 +63,15 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||||||
save_playcounts_to_files_(false),
|
save_playcounts_to_files_(false),
|
||||||
save_ratings_to_files_(false) {
|
save_ratings_to_files_(false) {
|
||||||
|
|
||||||
|
setObjectName(QLatin1String(metaObject()->className()));
|
||||||
|
|
||||||
original_thread_ = thread();
|
original_thread_ = thread();
|
||||||
|
|
||||||
backend_ = make_shared<CollectionBackend>();
|
backend_ = make_shared<CollectionBackend>();
|
||||||
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);
|
||||||
|
|
||||||
@@ -80,7 +82,7 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||||||
SCollection::~SCollection() {
|
SCollection::~SCollection() {
|
||||||
|
|
||||||
if (watcher_) {
|
if (watcher_) {
|
||||||
watcher_->Stop();
|
watcher_->Abort();
|
||||||
watcher_->deleteLater();
|
watcher_->deleteLater();
|
||||||
}
|
}
|
||||||
if (watcher_thread_) {
|
if (watcher_thread_) {
|
||||||
@@ -94,6 +96,7 @@ void SCollection::Init() {
|
|||||||
|
|
||||||
watcher_ = new CollectionWatcher(Song::Source::Collection);
|
watcher_ = new CollectionWatcher(Song::Source::Collection);
|
||||||
watcher_thread_ = new Thread(this);
|
watcher_thread_ = new Thread(this);
|
||||||
|
watcher_thread_->setObjectName(watcher_->objectName());
|
||||||
|
|
||||||
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||||
|
|
||||||
@@ -107,7 +110,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);
|
||||||
@@ -151,7 +154,7 @@ void SCollection::ExitReceived() {
|
|||||||
QObject::disconnect(obj, nullptr, this, nullptr);
|
QObject::disconnect(obj, nullptr, this, nullptr);
|
||||||
qLog(Debug) << obj << "successfully exited.";
|
qLog(Debug) << obj << "successfully exited.";
|
||||||
wait_for_exit_.removeAll(obj);
|
wait_for_exit_.removeAll(obj);
|
||||||
if (wait_for_exit_.isEmpty()) emit ExitFinished();
|
if (wait_for_exit_.isEmpty()) Q_EMIT ExitFinished();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +162,7 @@ void SCollection::IncrementalScan() { watcher_->IncrementalScanAsync(); }
|
|||||||
|
|
||||||
void SCollection::FullScan() { watcher_->FullScanAsync(); }
|
void SCollection::FullScan() { watcher_->FullScanAsync(); }
|
||||||
|
|
||||||
void SCollection::AbortScan() { watcher_->Stop(); }
|
void SCollection::StopScan() { watcher_->Stop(); }
|
||||||
|
|
||||||
void SCollection::Rescan(const SongList &songs) {
|
void SCollection::Rescan(const SongList &songs) {
|
||||||
|
|
||||||
@@ -179,7 +182,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 +209,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 +220,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 +228,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,24 +64,24 @@ class SCollection : public QObject {
|
|||||||
private:
|
private:
|
||||||
void SyncPlaycountAndRatingToFiles();
|
void SyncPlaycountAndRatingToFiles();
|
||||||
|
|
||||||
public slots:
|
public Q_SLOTS:
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
void PauseWatcher();
|
void PauseWatcher();
|
||||||
void ResumeWatcher();
|
void ResumeWatcher();
|
||||||
|
|
||||||
void FullScan();
|
void FullScan();
|
||||||
void AbortScan();
|
void StopScan();
|
||||||
void Rescan(const SongList &songs);
|
void Rescan(const SongList &songs);
|
||||||
|
|
||||||
void IncrementalScan();
|
void IncrementalScan();
|
||||||
|
|
||||||
private slots:
|
private Q_SLOTS:
|
||||||
void ExitReceived();
|
void ExitReceived();
|
||||||
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false);
|
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false);
|
||||||
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
||||||
|
|
||||||
signals:
|
Q_SIGNALS:
|
||||||
void Error(const QString &error);
|
void Error(const QString &error);
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -231,12 +236,15 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void UpdateSongRatingAsync(const int id, const float rating, const bool save_tags = false);
|
void UpdateSongRatingAsync(const int id, const float rating, const bool save_tags = false);
|
||||||
void UpdateSongsRatingAsync(const QList<int> &ids, const float rating, const bool save_tags = false);
|
void UpdateSongsRatingAsync(const QList<int> &ids, const float rating, const bool save_tags = false);
|
||||||
|
|
||||||
public slots:
|
public Q_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);
|
||||||
@@ -267,12 +275,14 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
|
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
|
||||||
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:
|
Q_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;
|
||||||
|
|
||||||
private slots:
|
SharedPtr<CollectionBackend> backend() const { return backend_; }
|
||||||
// To be called by the backend
|
|
||||||
void DirectoryDiscovered(const CollectionDirectory &directories);
|
QMap<int, CollectionDirectory> directories() const { return directories_; }
|
||||||
void DirectoryDeleted(const CollectionDirectory &directories);
|
QStringList paths() const { return paths_; }
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void AddDirectory(const CollectionDirectory &directory);
|
||||||
|
void RemoveDirectory(const CollectionDirectory &directory);
|
||||||
|
|
||||||
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)
|
||||||
|
size_t hash = qHash(filter_string_);
|
||||||
|
#else
|
||||||
|
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,70 +47,51 @@
|
|||||||
#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"
|
||||||
#include "ui_collectionfilterwidget.h"
|
#include "ui_collectionfilterwidget.h"
|
||||||
#include "widgets/qsearchfield.h"
|
#include "widgets/searchfield.h"
|
||||||
#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(
|
QObject::connect(ui_->search_field, &SearchField::returnPressed, this, &CollectionFilterWidget::ReturnPressed);
|
||||||
QString("<html><head/><body><p>") +
|
QObject::connect(timer_filter_delay_, &QTimer::timeout, this, &CollectionFilterWidget::FilterDelayTimeout);
|
||||||
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;\">") +
|
timer_filter_delay_->setInterval(kFilterDelay);
|
||||||
tr("Available fields") +
|
timer_filter_delay_->setSingleShot(true);
|
||||||
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(filter_delay_, &QTimer::timeout, this, &CollectionFilterWidget::FilterDelayTimeout);
|
|
||||||
|
|
||||||
filter_delay_->setInterval(kFilterDelay);
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -145,7 +127,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
|||||||
collection_menu_->addSeparator();
|
collection_menu_->addSeparator();
|
||||||
ui_->options->setMenu(collection_menu_);
|
ui_->options->setMenu(collection_menu_);
|
||||||
|
|
||||||
QObject::connect(ui_->search_field, &QSearchField::textChanged, this, &CollectionFilterWidget::FilterTextChanged);
|
QObject::connect(ui_->search_field, &SearchField::textChanged, this, &CollectionFilterWidget::FilterTextChanged);
|
||||||
QObject::connect(ui_->options, &QToolButton::clicked, ui_->options, &QToolButton::showMenu);
|
QObject::connect(ui_->options, &QToolButton::clicked, ui_->options, &QToolButton::showMenu);
|
||||||
|
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
@@ -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];
|
const int filter_max_age = filter_max_ages_.value(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);
|
||||||
|
|
||||||
@@ -495,12 +479,12 @@ void CollectionFilterWidget::keyReleaseEvent(QKeyEvent *e) {
|
|||||||
|
|
||||||
switch (e->key()) {
|
switch (e->key()) {
|
||||||
case Qt::Key_Up:
|
case Qt::Key_Up:
|
||||||
emit UpPressed();
|
Q_EMIT UpPressed();
|
||||||
e->accept();
|
e->accept();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Qt::Key_Down:
|
case Qt::Key_Down:
|
||||||
emit DownPressed();
|
Q_EMIT DownPressed();
|
||||||
e->accept();
|
e->accept();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -85,21 +86,20 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
bool SearchFieldHasFocus() const;
|
bool SearchFieldHasFocus() const;
|
||||||
void FocusSearchField();
|
void FocusSearchField();
|
||||||
|
|
||||||
public slots:
|
public Q_SLOTS:
|
||||||
void UpdateGroupByActions();
|
void UpdateGroupByActions();
|
||||||
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||||
void FocusOnFilter(QKeyEvent *e);
|
void FocusOnFilter(QKeyEvent *e);
|
||||||
|
|
||||||
signals:
|
Q_SIGNALS:
|
||||||
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;
|
||||||
|
|
||||||
private slots:
|
private Q_SLOTS:
|
||||||
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 GroupByClicked(QAction *action);
|
void GroupByClicked(QAction *action);
|
||||||
void SaveGroupBy();
|
void SaveGroupBy();
|
||||||
@@ -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_;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSearchField" name="search_field" native="true">
|
<widget class="SearchField" name="search_field" native="true">
|
||||||
<property name="placeholderText" stdset="0">
|
<property name="placeholderText" stdset="0">
|
||||||
<string>Enter search terms here</string>
|
<string>Enter search terms here</string>
|
||||||
</property>
|
</property>
|
||||||
@@ -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>
|
||||||
@@ -120,9 +123,9 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>QSearchField</class>
|
<class>SearchField</class>
|
||||||
<extends>QWidget</extends>
|
<extends>QWidget</extends>
|
||||||
<header>widgets/qsearchfield.h</header>
|
<header>widgets/searchfield.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class CollectionItemDelegate : public QStyledItemDelegate {
|
|||||||
explicit CollectionItemDelegate(QObject *parent);
|
explicit CollectionItemDelegate(QObject *parent);
|
||||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
|
||||||
|
|
||||||
public slots:
|
public Q_SLOTS:
|
||||||
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &idx) override;
|
bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &idx) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
signals:
|
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||||
|
|
||||||
|
Q_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 Q_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 Q_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
|
||||||
@@ -21,147 +21,58 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#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::currentSecsSinceEpoch() - 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();
|
const 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 +81,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 +90,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,34 +118,25 @@ 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;
|
||||||
|
|
||||||
// Bind values
|
// Bind values
|
||||||
for (const QVariant &value : bound_values_) {
|
for (const QVariant &value : std::as_const(bound_values_)) {
|
||||||
QSqlQuery::addBindValue(value);
|
QSqlQuery::addBindValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -101,6 +104,8 @@ CollectionView::CollectionView(QWidget *parent)
|
|||||||
is_in_keyboard_search_(false),
|
is_in_keyboard_search_(false),
|
||||||
delete_files_(false) {
|
delete_files_(false) {
|
||||||
|
|
||||||
|
setObjectName(QLatin1String(metaObject()->className()));
|
||||||
|
|
||||||
setItemDelegate(new CollectionItemDelegate(this));
|
setItemDelegate(new CollectionItemDelegate(this));
|
||||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||||
setHeaderHidden(true);
|
setHeaderHidden(true);
|
||||||
@@ -109,7 +114,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 +122,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 +137,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 +147,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 +164,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 +195,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 +216,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 +244,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();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -263,7 +275,7 @@ void CollectionView::TotalSongCountUpdated(const int count) {
|
|||||||
unsetCursor();
|
unsetCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
emit TotalSongCountUpdated_();
|
Q_EMIT TotalSongCountUpdated_();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +292,7 @@ void CollectionView::TotalArtistCountUpdated(const int count) {
|
|||||||
unsetCursor();
|
unsetCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
emit TotalArtistCountUpdated_();
|
Q_EMIT TotalArtistCountUpdated_();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +309,7 @@ void CollectionView::TotalAlbumCountUpdated(const int count) {
|
|||||||
unsetCursor();
|
unsetCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
emit TotalAlbumCountUpdated_();
|
Q_EMIT TotalAlbumCountUpdated_();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,34 +350,56 @@ void CollectionView::mouseReleaseEvent(QMouseEvent *e) {
|
|||||||
QTreeView::mouseReleaseEvent(e);
|
QTreeView::mouseReleaseEvent(e);
|
||||||
|
|
||||||
if (total_song_count_ == 0) {
|
if (total_song_count_ == 0) {
|
||||||
emit ShowConfigDialog();
|
Q_EMIT ShowConfigDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 +425,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 +496,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 +507,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 +524,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);
|
||||||
@@ -505,13 +540,13 @@ void CollectionView::Load() {
|
|||||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||||
mimedata->clear_first_ = true;
|
mimedata->clear_first_ = true;
|
||||||
}
|
}
|
||||||
emit AddToPlaylistSignal(q_mimedata);
|
Q_EMIT AddToPlaylistSignal(q_mimedata);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionView::AddToPlaylist() {
|
void CollectionView::AddToPlaylist() {
|
||||||
|
|
||||||
emit AddToPlaylistSignal(model()->mimeData(selectedIndexes()));
|
Q_EMIT AddToPlaylistSignal(model()->mimeData(selectedIndexes()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +556,7 @@ void CollectionView::AddToPlaylistEnqueue() {
|
|||||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||||
mimedata->enqueue_now_ = true;
|
mimedata->enqueue_now_ = true;
|
||||||
}
|
}
|
||||||
emit AddToPlaylistSignal(q_mimedata);
|
Q_EMIT AddToPlaylistSignal(q_mimedata);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +566,7 @@ void CollectionView::AddToPlaylistEnqueueNext() {
|
|||||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||||
mimedata->enqueue_next_now_ = true;
|
mimedata->enqueue_next_now_ = true;
|
||||||
}
|
}
|
||||||
emit AddToPlaylistSignal(q_mimedata);
|
Q_EMIT AddToPlaylistSignal(q_mimedata);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +576,103 @@ void CollectionView::OpenInNewPlaylist() {
|
|||||||
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
|
||||||
mimedata->open_in_new_playlist_ = true;
|
mimedata->open_in_new_playlist_ = true;
|
||||||
}
|
}
|
||||||
emit AddToPlaylistSignal(q_mimedata);
|
Q_EMIT AddToPlaylistSignal(q_mimedata);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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.constFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,7 +733,7 @@ void CollectionView::EditTracks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CollectionView::EditTagError(const QString &message) {
|
void CollectionView::EditTagError(const QString &message) {
|
||||||
emit Error(message);
|
Q_EMIT Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionView::RescanSongs() {
|
void CollectionView::RescanSongs() {
|
||||||
@@ -631,8 +762,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;
|
||||||
}
|
}
|
||||||
@@ -641,12 +775,12 @@ void CollectionView::FilterReturnPressed() {
|
|||||||
|
|
||||||
if (!currentIndex().isValid()) return;
|
if (!currentIndex().isValid()) return;
|
||||||
|
|
||||||
emit doubleClicked(currentIndex());
|
Q_EMIT doubleClicked(currentIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +805,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;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
int TotalArtists() const;
|
int TotalArtists() const;
|
||||||
int TotalAlbums() const;
|
int TotalAlbums() const;
|
||||||
|
|
||||||
public slots:
|
public Q_SLOTS:
|
||||||
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);
|
||||||
@@ -82,7 +82,7 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
|
|
||||||
void EditTagError(const QString &message);
|
void EditTagError(const QString &message);
|
||||||
|
|
||||||
signals:
|
Q_SIGNALS:
|
||||||
void ShowConfigDialog();
|
void ShowConfigDialog();
|
||||||
|
|
||||||
void TotalSongCountUpdated_();
|
void TotalSongCountUpdated_();
|
||||||
@@ -93,15 +93,17 @@ 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;
|
||||||
|
|
||||||
private slots:
|
private Q_SLOTS:
|
||||||
void Load();
|
void Load();
|
||||||
void AddToPlaylist();
|
void AddToPlaylist();
|
||||||
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
|
||||||
|
|||||||
@@ -42,12 +42,14 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QMutexLocker>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
#include "core/filesystemwatcherinterface.h"
|
#include "core/filesystemwatcherinterface.h"
|
||||||
#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 +72,7 @@
|
|||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
@@ -97,6 +98,8 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
|||||||
cue_parser_(new CueParser(backend_, this)),
|
cue_parser_(new CueParser(backend_, this)),
|
||||||
last_scan_time_(0) {
|
last_scan_time_(0) {
|
||||||
|
|
||||||
|
setObjectName(source_ == Song::Source::Collection ? QLatin1String(metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(source_), QLatin1String(metaObject()->className())));
|
||||||
|
|
||||||
original_thread_ = thread();
|
original_thread_ = thread();
|
||||||
|
|
||||||
rescan_timer_->setInterval(2s);
|
rescan_timer_->setInterval(2s);
|
||||||
@@ -105,7 +108,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);
|
||||||
@@ -134,10 +137,51 @@ void CollectionWatcher::Exit() {
|
|||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() == thread());
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
Stop();
|
Abort();
|
||||||
if (backend_) backend_->Close();
|
if (backend_) backend_->Close();
|
||||||
moveToThread(original_thread_);
|
moveToThread(original_thread_);
|
||||||
emit ExitFinished();
|
Q_EMIT ExitFinished();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionWatcher::Stop() {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_stop_);
|
||||||
|
stop_requested_ = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionWatcher::CancelStop() {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_stop_);
|
||||||
|
stop_requested_ = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollectionWatcher::stop_requested() const {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_stop_);
|
||||||
|
return stop_requested_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionWatcher::Abort() {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_abort_);
|
||||||
|
abort_requested_ = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollectionWatcher::abort_requested() const {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_abort_);
|
||||||
|
return abort_requested_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollectionWatcher::stop_or_abort_requested() const {
|
||||||
|
|
||||||
|
return stop_requested() || abort_requested();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,11 +194,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 +226,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 +236,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,14 +266,14 @@ CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher,
|
|||||||
}
|
}
|
||||||
|
|
||||||
task_id_ = watcher_->task_manager_->StartTask(description);
|
task_id_ = watcher_->task_manager_->StartTask(description);
|
||||||
emit watcher_->ScanStarted(task_id_);
|
Q_EMIT watcher_->ScanStarted(task_id_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionWatcher::ScanTransaction::~ScanTransaction() {
|
CollectionWatcher::ScanTransaction::~ScanTransaction() {
|
||||||
|
|
||||||
// If we're stopping then don't commit the transaction
|
// If we're stopping then don't commit the transaction
|
||||||
if (!watcher_->stop_requested_ && !watcher_->abort_requested_) {
|
if (!watcher_->stop_or_abort_requested()) {
|
||||||
CommitNewOrUpdatedSongs();
|
CommitNewOrUpdatedSongs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,39 +299,39 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
|||||||
|
|
||||||
if (!deleted_songs.isEmpty()) {
|
if (!deleted_songs.isEmpty()) {
|
||||||
if (mark_songs_unavailable_ && watcher_->source() == Song::Source::Collection) {
|
if (mark_songs_unavailable_ && watcher_->source() == Song::Source::Collection) {
|
||||||
emit watcher_->SongsUnavailable(deleted_songs);
|
Q_EMIT watcher_->SongsUnavailable(deleted_songs);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
emit watcher_->SongsDeleted(deleted_songs);
|
Q_EMIT watcher_->SongsDeleted(deleted_songs);
|
||||||
}
|
}
|
||||||
deleted_songs.clear();
|
deleted_songs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new_songs.isEmpty()) {
|
if (!new_songs.isEmpty()) {
|
||||||
emit watcher_->NewOrUpdatedSongs(new_songs);
|
Q_EMIT watcher_->NewOrUpdatedSongs(new_songs);
|
||||||
new_songs.clear();
|
new_songs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!touched_songs.isEmpty()) {
|
if (!touched_songs.isEmpty()) {
|
||||||
emit watcher_->SongsMTimeUpdated(touched_songs);
|
Q_EMIT watcher_->SongsMTimeUpdated(touched_songs);
|
||||||
touched_songs.clear();
|
touched_songs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!readded_songs.isEmpty()) {
|
if (!readded_songs.isEmpty()) {
|
||||||
emit watcher_->SongsReadded(readded_songs);
|
Q_EMIT watcher_->SongsReadded(readded_songs);
|
||||||
readded_songs.clear();
|
readded_songs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new_subdirs.isEmpty()) {
|
if (!new_subdirs.isEmpty()) {
|
||||||
emit watcher_->SubdirsDiscovered(new_subdirs);
|
Q_EMIT watcher_->SubdirsDiscovered(new_subdirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!touched_subdirs.isEmpty()) {
|
if (!touched_subdirs.isEmpty()) {
|
||||||
emit watcher_->SubdirsMTimeUpdated(touched_subdirs);
|
Q_EMIT watcher_->SubdirsMTimeUpdated(touched_subdirs);
|
||||||
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 +340,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);
|
||||||
}
|
}
|
||||||
@@ -305,7 +349,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
|||||||
new_subdirs.clear();
|
new_subdirs.clear();
|
||||||
|
|
||||||
if (incremental_ || ignores_mtime_) {
|
if (incremental_ || ignores_mtime_) {
|
||||||
emit watcher_->UpdateLastSeen(dir_, expire_unavailable_songs_days_);
|
Q_EMIT watcher_->UpdateLastSeen(dir_, expire_unavailable_songs_days_);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -316,7 +360,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 +369,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 +379,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 +394,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 +428,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;
|
||||||
}
|
}
|
||||||
@@ -405,7 +450,7 @@ CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
|||||||
|
|
||||||
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
||||||
|
|
||||||
stop_requested_ = false;
|
CancelStop();
|
||||||
|
|
||||||
watched_dirs_[dir.id] = dir;
|
watched_dirs_[dir.id] = dir;
|
||||||
|
|
||||||
@@ -416,9 +461,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;
|
||||||
@@ -426,18 +477,16 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
|
|||||||
transaction.SetKnownSubdirs(subdirs);
|
transaction.SetKnownSubdirs(subdirs);
|
||||||
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_or_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_or_abort_requested()) {
|
||||||
if (monitor_) AddWatch(dir, subdir.path);
|
last_scan_time_ = QDateTime::currentSecsSinceEpoch();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
Q_EMIT CompilationsNeedUpdating();
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
emit CompilationsNeedUpdating();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +529,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);
|
||||||
@@ -491,7 +540,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
|
||||||
if (stop_requested_ || abort_requested_) return;
|
if (stop_or_abort_requested()) return;
|
||||||
|
|
||||||
QString child(it.next());
|
QString child(it.next());
|
||||||
QFileInfo child_info(child);
|
QFileInfo child_info(child);
|
||||||
@@ -510,7 +559,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 (Song::kRejectedExtensions.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)) {
|
||||||
@@ -526,7 +575,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stop_requested_ || abort_requested_) return;
|
if (stop_or_abort_requested()) return;
|
||||||
|
|
||||||
// Ask the database for a list of files in this directory
|
// Ask the database for a list of files in this directory
|
||||||
SongList songs_in_db = t->FindSongsInSubdirectory(path);
|
SongList songs_in_db = t->FindSongsInSubdirectory(path);
|
||||||
@@ -537,7 +586,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
QStringList files_on_disk_copy = files_on_disk;
|
QStringList files_on_disk_copy = files_on_disk;
|
||||||
for (const QString &file : files_on_disk_copy) {
|
for (const QString &file : files_on_disk_copy) {
|
||||||
|
|
||||||
if (stop_requested_ || abort_requested_) return;
|
if (stop_or_abort_requested()) return;
|
||||||
|
|
||||||
// Associated CUE
|
// Associated CUE
|
||||||
QString new_cue = CueParser::FindCueFilename(file);
|
QString new_cue = CueParser::FindCueFilename(file);
|
||||||
@@ -612,7 +661,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
|
||||||
@@ -627,6 +676,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
|
|
||||||
// Nothing has changed - mark the song available without re-scanning
|
// Nothing has changed - mark the song available without re-scanning
|
||||||
else if (matching_song.unavailable()) {
|
else if (matching_song.unavailable()) {
|
||||||
|
qLog(Debug) << "Unavailable song" << file << "restored.";
|
||||||
t->readded_songs << matching_songs;
|
t->readded_songs << matching_songs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,11 +688,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.
|
||||||
@@ -656,7 +706,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;
|
||||||
@@ -689,7 +739,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;
|
||||||
@@ -711,7 +761,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;
|
||||||
@@ -737,8 +787,8 @@ 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_or_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,6 +844,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,
|
||||||
@@ -814,8 +865,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());
|
||||||
@@ -867,8 +918,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);
|
||||||
@@ -886,55 +937,55 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
|||||||
QStringList changes;
|
QStringList changes;
|
||||||
|
|
||||||
if (matching_song.unavailable()) {
|
if (matching_song.unavailable()) {
|
||||||
qLog(Debug) << "unavailable song" << file << "restored.";
|
qLog(Debug) << "Unavailable song" << file << "restored.";
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
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.";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -989,7 +1040,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);
|
||||||
@@ -1005,7 +1056,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);
|
||||||
@@ -1027,7 +1078,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);
|
||||||
@@ -1076,20 +1127,22 @@ 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_or_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_.value(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_or_abort_requested()) break;
|
||||||
CollectionSubdirectory subdir;
|
CollectionSubdirectory subdir;
|
||||||
subdir.directory_id = dir;
|
subdir.directory_id = dir;
|
||||||
subdir.mtime = 0;
|
subdir.mtime = 0;
|
||||||
@@ -1100,7 +1153,7 @@ void CollectionWatcher::RescanPathsNow() {
|
|||||||
|
|
||||||
rescan_queue_.clear();
|
rescan_queue_.clear();
|
||||||
|
|
||||||
emit CompilationsNeedUpdating();
|
Q_EMIT CompilationsNeedUpdating();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1111,7 +1164,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);
|
||||||
@@ -1133,8 +1186,8 @@ 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_or_abort_requested()) break;
|
||||||
|
|
||||||
QImage image(path);
|
QImage image(path);
|
||||||
if (image.isNull()) continue;
|
if (image.isNull()) continue;
|
||||||
@@ -1196,7 +1249,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();
|
||||||
@@ -1210,14 +1263,14 @@ void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
|
|||||||
|
|
||||||
void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mtimes) {
|
void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mtimes) {
|
||||||
|
|
||||||
stop_requested_ = false;
|
CancelStop();
|
||||||
|
|
||||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
|
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_or_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.";
|
||||||
@@ -1231,16 +1284,16 @@ 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_or_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();
|
Q_EMIT CompilationsNeedUpdating();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1250,7 +1303,7 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
|
|||||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_or_abort_requested()) break;
|
||||||
|
|
||||||
QString child = it.next();
|
QString child = it.next();
|
||||||
QFileInfo path_info(child);
|
QFileInfo path_info(child);
|
||||||
@@ -1284,7 +1337,7 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Collec
|
|||||||
|
|
||||||
quint64 i = 0;
|
quint64 i = 0;
|
||||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_or_abort_requested()) break;
|
||||||
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
||||||
subdir_files_count[subdir.path] = files_count;
|
subdir_files_count[subdir.path] = files_count;
|
||||||
i += files_count;
|
i += files_count;
|
||||||
@@ -1302,17 +1355,17 @@ void CollectionWatcher::RescanSongsAsync(const SongList &songs) {
|
|||||||
|
|
||||||
void CollectionWatcher::RescanSongs(const SongList &songs) {
|
void CollectionWatcher::RescanSongs(const SongList &songs) {
|
||||||
|
|
||||||
stop_requested_ = false;
|
CancelStop();
|
||||||
|
|
||||||
QStringList scanned_paths;
|
QStringList scanned_paths;
|
||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_or_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_or_abort_requested()) break;
|
||||||
if (subdir.path != song_path) continue;
|
if (subdir.path != song_path) continue;
|
||||||
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
|
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
|
||||||
quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
||||||
@@ -1321,6 +1374,6 @@ void CollectionWatcher::RescanSongs(const SongList &songs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit CompilationsNeedUpdating();
|
Q_EMIT CompilationsNeedUpdating();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||