Compare commits

...

254 Commits
0.8.1 ... 0.8.5

Author SHA1 Message Date
Jonas Kvinge
d3faf18251 Fix typo 2020-12-19 17:42:24 +01:00
Jonas Kvinge
ddcc296af4 Release 0.8.5 2020-12-19 17:38:24 +01:00
Jonas Kvinge
ee765bc210 Update strawberry.spec 2020-12-17 22:49:57 +01:00
Jonas Kvinge
630c67f7d0 Remove -U__STRICT_ANSI__ from compile flags 2020-12-17 19:47:46 +01:00
Jonas Kvinge
e78bb1b29c Use directsoundsink by default because of buggy wasapi plugin 2020-12-13 21:56:25 +01:00
Strawbs Bot
db312a7380 Update translations 2020-12-13 01:02:57 +01:00
Jonas Kvinge
48b4cbe1b9 Update ccpp.yml 2020-12-12 19:05:21 +01:00
Jonas Kvinge
edb1d4c747 Append Qt5 to Windows setups 2020-12-12 12:51:23 +01:00
Jonas Kvinge
128935b55e Use LPCSTR in RegisterWindowMessageA 2020-12-12 12:51:01 +01:00
Jonas Kvinge
d5a0d67c7b Use ScopedWCharArray 2020-12-12 12:50:33 +01:00
Jonas Kvinge
a86640fdee Exclude Windows from find D-Bus in CMake 2020-12-12 12:49:41 +01:00
Jonas Kvinge
03291b27d1 Mark unused 2020-12-12 12:49:35 +01:00
Strawbs Bot
acf81f116f Update translations 2020-12-12 00:51:58 +01:00
Jonas Kvinge
6564c405c1 Update Changelog 2020-12-12 00:50:46 +01:00
Jonas Kvinge
1bd586268c Update some copyrights 2020-12-12 00:33:27 +01:00
Jonas Kvinge
497952611d Fix HTML escaping for custom OSD notifications
Fixes #618
2020-12-11 23:59:38 +01:00
Jonas Kvinge
9149d1baa3 Add back mistakenly removed QT_MIN_VERSION in CMakeLists.txt 2020-12-11 21:01:51 +01:00
Jonas Kvinge
66c60ac5c7 Add back CentOS 8 to CI 2020-12-09 21:44:47 +01:00
Strawbs Bot
971fad4560 Update translations 2020-12-09 19:33:33 +01:00
Jonas Kvinge
8ed1ce4103 Move ThreadSafeNetworkDiskCache to own file 2020-12-09 18:41:07 +01:00
Jonas Kvinge
3112c34d11 Change to NetworkAccessManager 2020-12-09 18:39:37 +01:00
Jonas Kvinge
7ebf3cecc6 Only include Rpm.cmake and Deb.cmake on Linux 2020-12-09 18:29:30 +01:00
Jonas Kvinge
660fa99f9c Simplify Rpm.cmake 2020-12-09 18:09:49 +01:00
Jonas Kvinge
a997e53d8b Update RPM spec for Qt 6 2020-12-09 01:29:38 +01:00
Jonas Kvinge
1d1dd583e2 Remove CentOS from CI 2020-12-08 15:59:53 +01:00
Jonas Kvinge
d490b29265 Use regex 2020-12-07 22:43:00 +01:00
Jonas Kvinge
e8ca64f16b Replace empty quotes with empty strings for values in CUE parser 2020-12-07 22:34:55 +01:00
Jonas Kvinge
380aa7d884 Only set disc and year in CUE parser when valid 2020-12-07 21:57:15 +01:00
Jonas Kvinge
0a31a94ee8 Change double-click in cover manager to open fullsize cover
Fixes #612
2020-12-07 17:23:08 +01:00
Jonas Kvinge
e1d50ce28f Make URL in scrobbler settings clickable 2020-12-07 17:21:34 +01:00
Jonas Kvinge
38cc39f23d Workaround udisks2 mountpoint issue with Qt 6 2020-12-06 18:46:30 +01:00
Jonas Kvinge
8110035d71 Remove unref 2020-12-06 18:45:37 +01:00
Jonas Kvinge
9af409d6d6 Make sure URL is valid in giolister 2020-12-06 18:45:17 +01:00
Jonas Kvinge
8646829853 Update .travis.yml 2020-12-06 11:16:15 +01:00
Jonas Kvinge
b9e87813b1 Add back Travis-CI 2020-12-06 06:45:08 +01:00
Strawbs Bot
138e032746 Update translations 2020-12-05 01:03:29 +01:00
Jonas Kvinge
d94c6b5aba Set active playlist when playlist doubleclick behaviour is enqueue
Fixes #610
2020-12-04 22:11:35 +01:00
Jonas Kvinge
7c03a39316 Update Changelog 2020-12-04 19:38:00 +01:00
Jonas Kvinge
c482e264f4 Rename variable 2020-12-04 19:13:21 +01:00
Nicolas Toussaint
3a9a1f0a94 add CLI play-playlist option, to play given playlist name. (#608)
* add CLI play-playlist option, to play given playlist name.

* Apply MR change requests
2020-12-04 19:08:41 +01:00
Jonas Kvinge
5b5f728f49 Update README.md 2020-12-03 22:24:57 +01:00
Jonas Kvinge
179aac6b35 Update FUNDING.yml 2020-12-03 22:16:22 +01:00
Strawbs Bot
25de9126ca Update translations 2020-11-30 01:19:32 +01:00
Jonas Kvinge
87be5662ad Update .circleci/config.yml 2020-11-29 16:05:02 +01:00
Jonas Kvinge
053a59e3f4 Update .github/workflows/ccpp.yml 2020-11-29 16:03:44 +01:00
Jonas Kvinge
250857b3d4 Update .circleci/config.yml 2020-11-29 07:54:42 +01:00
Jonas Kvinge
dc288c584c Search qpa/qplatformnativeinterface.h for both Qt 5 and 6 2020-11-29 07:50:08 +01:00
Jonas Kvinge
fc02543f15 Make X11Extras and WinExtras optional
The initial release of Qt 6 does not have these.

These modules are not important.
X11Extras is only used for X11 global shortcuts, which in most cases is
overriden by KDE or Gnome shortcuts anyway.
It is also used in OSD Pretty to detect transparency, but this can be
done using private GUI headers instead.

WinExtras is only used in OSD Pretty to make it transparent.
2020-11-29 07:41:30 +01:00
Jonas Kvinge
0808db706f Remove libgstrealmedia.dylib from macdeploy 2020-11-29 06:43:24 +01:00
Strawbs Bot
1e649fc565 Update translations 2020-11-29 01:02:57 +01:00
Strawbs Bot
c523e959d7 Update translations 2020-11-27 01:02:59 +01:00
Strawbs Bot
1422f988c1 Update translations 2020-11-26 01:02:53 +01:00
Strawbs Bot
e86252b934 Update translations 2020-11-25 01:02:17 +01:00
Jonas Kvinge
e9c59b5c31 Add gst-launch-1.0.exe to windows setup 2020-11-24 15:45:24 +01:00
Strawbs Bot
f7bddee0ce Update translations 2020-11-24 01:05:32 +01:00
Jonas Kvinge
d56b76c9e3 Save both ID3v1 and ID3v2
Fixes #598
2020-11-23 22:25:47 +01:00
Jonas Kvinge
a41353ed03 Update progress even if QDateTime::fromString() fails 2020-11-23 21:54:58 +01:00
Strawbs Bot
7ea12cf2f9 Update translations 2020-11-23 01:05:20 +01:00
Jonas Kvinge
5bc5170112 Remove travis 2020-11-22 20:01:45 +01:00
Jonas Kvinge
946ed0c0b5 Check for valid datetime when importing last played 2020-11-22 19:01:53 +01:00
Jonas Kvinge
fcd4e5aca2 Save and restore geometry in edit tag dialog 2020-11-22 03:37:15 +01:00
Jonas Kvinge
6ab6e6d3a8 Format code 2020-11-22 03:36:46 +01:00
Strawbs Bot
0893d01b4a Update translations 2020-11-22 01:02:49 +01:00
Jonas Kvinge
bf04c45ebf Remove unused live scanning option 2020-11-21 09:40:48 +01:00
Jonas Kvinge
2786a1a97e Remove required version from cmake files 2020-11-21 07:41:43 +01:00
Strawbs Bot
cd9a4f1aa9 Update translations 2020-11-21 01:16:20 +01:00
Jonas Kvinge
91e5cafe76 Remove disabling repeat and shuffle buttons
It's more confusing than helpful
2020-11-20 21:48:10 +01:00
Jonas Kvinge
47754951f0 Format code 2020-11-20 21:47:01 +01:00
Strawbs Bot
fbef8ef32e Update translations 2020-11-20 01:08:25 +01:00
Jonas Kvinge
abd5bf3158 Update .github/workflows/ccpp.yml 2020-11-19 21:25:40 +01:00
Jonas Kvinge
fd024368a3 Update .github/workflows/ccpp.yml 2020-11-19 21:24:30 +01:00
Jonas Kvinge
cf4856a0c9 Fix signal not always emitted from messagreply 2020-11-19 18:29:03 +01:00
Jonas Kvinge
1b791c82fc Use qint64 for message request ID 2020-11-19 18:26:50 +01:00
Jonas Kvinge
3a27a3e868 Format code 2020-11-19 18:22:57 +01:00
Jonas Kvinge
7e6c16b287 Change progress and max progress to qint64 in taskmanager 2020-11-19 18:14:42 +01:00
Jonas Kvinge
f877639ed7 Fix repeat/shuffle disabled when a dynamic playlist is open
Fixes #593
2020-11-19 18:12:48 +01:00
Strawbs Bot
846309d4dd Add Swedish 2020-11-19 01:19:58 +01:00
Strawbs Bot
f2239ddfdf Update translations 2020-11-19 01:02:47 +01:00
Jonas Kvinge
742d455b0e Build on Big Sur 2020-11-18 22:48:16 +01:00
Strawbs Bot
20b08a78e7 Update translations 2020-11-18 01:17:34 +01:00
Jonas Kvinge
042da74955 Change return type of qHash with Qt 6 to size_t 2020-11-17 01:22:38 +01:00
Jonas Kvinge
4dcae4ce21 Use QChar::unicode() 2020-11-17 01:22:00 +01:00
Jonas Kvinge
fa0f07f553 Use QString::number() 2020-11-17 01:21:38 +01:00
Jonas Kvinge
39792d76cf Fix PlaylistGenerator::type() 2020-11-17 01:20:45 +01:00
Strawbs Bot
d181ee9101 Update translations 2020-11-17 01:02:12 +01:00
Jonas Kvinge
335ba1a5b0 Disable tagreader checksum tests
These don't match anymore because tags are now removed.
2020-11-15 12:17:35 +01:00
Jonas Kvinge
4934a360f1 Turn back git revision 2020-11-15 12:16:44 +01:00
Strawbs Bot
0559a4c6f2 Update translations 2020-11-15 01:03:31 +01:00
Jonas Kvinge
fc07919a75 Release 0.8.4 2020-11-15 00:31:32 +01:00
Jonas Kvinge
d7661edf67 Update Changelog 2020-11-15 00:20:16 +01:00
Jonas Kvinge
1c91693294 Ignore org.kde.kglobalaccel.NoSuchComponent error 2020-11-15 00:20:08 +01:00
Jonas Kvinge
3fc3cbc6d5 Update protobuf dll in nsi 2020-11-14 19:13:40 +01:00
Jonas Kvinge
11b5895e69 Update ccpp.yml 2020-11-14 18:36:59 +01:00
Jonas Kvinge
b4c614edbf Set volume bit 2020-11-14 04:36:38 +01:00
Jonas Kvinge
deddaed04a Remove use of std::bind where possible 2020-11-14 02:13:22 +01:00
Jonas Kvinge
a155e503f4 Never use reference when iterating QJsonArray 2020-11-13 21:10:50 +01:00
Jonas Kvinge
c0663bc19f Use reference 2020-11-13 20:34:29 +01:00
Strawbs Bot
7ffa51b83d Update translations 2020-11-13 01:02:53 +01:00
Jonas Kvinge
6d397b9988 Fix smart playlist by filename 2020-11-12 20:47:13 +01:00
Jonas Kvinge
571a7fa26b Fix single letter collection nodes showing before dividers 2020-11-12 20:30:58 +01:00
Jonas Kvinge
b3b5a38c3a Minor code style fix 2020-11-11 22:55:56 +01:00
Jonas Kvinge
a4b115f89b Update Changelog 2020-11-11 22:51:55 +01:00
Strawbs Bot
3d672bb145 Update translations 2020-11-11 01:03:35 +01:00
Jonas Kvinge
15b656b753 Merge pull request #587 from fbugno/issue515
Fix HiDPI scaling for glow animation and drag over playlist
2020-11-10 22:52:08 +01:00
Felipe Bugno
f5785db163 Code style changes to match the existing code
This changes the style of the private variable and the call
convention of the inherited functions.
2020-11-10 17:55:00 -03:00
Jonas Kvinge
1ff1bf3292 Only call deleteLater when proxystyle is set 2020-11-10 19:06:26 +01:00
Felipe Bugno
b062febea0 Fix HiDPI scaling for glow animation and drag over playlist
This set the proper scaling and pixel ratio of QPixmap widgets
used as cached objects.

Most of cached objects uses a custom QPaint instead of the default
painter object from the parent widget. The problem is that, unlike
the painter from the parent object, set by the main application,
and that has DPI and scaling settings from the device, these custom
QPainters don't know about the underlying device, thus uses a
scale of 1 to render artifacts.

When a cached object "edited" by a custom QPaint along his pipeline
where used on a paint or drawrow routine, his stored image is distorted
and burred in a effort to resize it to the display configuration.
2020-11-09 21:49:22 -03:00
Strawbs Bot
35301dc79e Update translations 2020-11-10 01:02:36 +01:00
Jonas Kvinge
30c336726b Only backup database if schema version is correct 2020-11-09 23:10:43 +01:00
Jonas Kvinge
3821680817 Increase sqlite busy timeout to 30 seconds
Possible fix for #533
2020-11-09 22:49:33 +01:00
Jonas Kvinge
74242ea24f Fix shortcut settings on macOS and Windows 2020-11-09 20:20:31 +01:00
Jonas Kvinge
73c7024e11 Dont return from SongSaveComplete early, needs to free TagReaderReply 2020-11-09 19:17:31 +01:00
Strawbs Bot
bbdec92dc6 Update translations 2020-11-09 01:01:55 +01:00
Jonas Kvinge
b4c289101c Strip summary but encode message 2020-11-08 21:54:16 +01:00
Jonas Kvinge
722d0797f6 Use QString::toHtmlEscaped() 2020-11-08 17:52:29 +01:00
Jonas Kvinge
deb27d5b55 Replace '&' with '&' in D-BUS OSD messages 2020-11-08 14:24:09 +01:00
Jonas Kvinge
9cc4ffdf6e Only strip '&' from D-Bus OSD messages 2020-11-08 13:46:17 +01:00
Strawbs Bot
c76d63d1d9 Update translations 2020-11-08 13:32:51 +01:00
Jonas Kvinge
21bb4f33ad Update Changelog 2020-11-08 04:05:39 +01:00
Jonas Kvinge
2897b881d6 Only override fancy tabwidget style with adwaita 2020-11-08 04:04:37 +01:00
Jonas Kvinge
e9b89d0929 Simplify FancyTabWidget override 2020-11-08 03:23:18 +01:00
Jonas Kvinge
e801254b2e Log used style 2020-11-08 03:22:38 +01:00
Jonas Kvinge
6f49918ee9 Update Changelog 2020-11-08 02:10:20 +01:00
Jonas Kvinge
0ae7c18f1f Add setting to set style 2020-11-08 02:04:24 +01:00
Jonas Kvinge
c9c3fb396a Move ComboBoxLoadFromSettings function to settingspage 2020-11-08 02:02:48 +01:00
Strawbs Bot
91db4f1934 Update translations 2020-11-08 01:02:31 +01:00
Jonas Kvinge
9bbed6e95c Use QClipboard::setText instead of QClipboard::setMimeData
Fixes #581

Fixes clipboard copying on Windows too
2020-11-08 00:51:24 +01:00
Strawbs Bot
748bc27b25 Update translations 2020-11-06 11:50:48 +01:00
Jonas Kvinge
f42708e8bc Remove qt6-qt5compat-devel from ccpp.yml 2020-11-05 22:30:37 +01:00
Jonas Kvinge
160e4570a2 Use C++17 (#579)
* Use C++17

* Replace std::random_shuffle with std::shuffle

* Add random include
2020-11-05 22:28:49 +01:00
Jonas Kvinge
6272965143 Update Changelog 2020-11-05 00:10:16 +01:00
Jonas Kvinge
1e9613bf7f Update ccpp.yml 2020-11-04 23:45:40 +01:00
Jonas Kvinge
a061dac298 Check network proxy mode 2020-11-04 22:22:26 +01:00
Jonas Kvinge
914dee8571 Pass network proxy settings to gstreamer
Fixes #558
2020-11-04 22:16:20 +01:00
yavuzmert
47e2905edf Re-enable progress on system tray icon (#578)
* Re-enable progress on system tray icon

* fix copy-paste typo in mac sources

* Make tray icon progress optional

* Move tray icon progress setting control to MainWindow

* Move trayicon settings to behavioursettings

Co-authored-by: Yavuz Mert <yavuz.mert@darkbluesystems.net>
2020-11-04 21:01:04 +01:00
Jonas Kvinge
ee6675aee0 No longer need Core5Compat 2020-11-04 18:17:33 +01:00
Jonas Kvinge
62e0d9fe64 Remove all uses of QTextCodec 2020-11-04 18:16:23 +01:00
Jonas Kvinge
a174c142c1 Remove unused linked list includes 2020-11-04 18:06:36 +01:00
Jonas Kvinge
95afc5fdec Keep tabs in the middle on macOS 2020-11-04 18:05:58 +01:00
Strawbs Bot
04d69f66c0 Update translations 2020-11-04 01:02:16 +01:00
Jonas Kvinge
0347141edd Update snap dialog 2020-11-03 19:32:32 +01:00
Jonas Kvinge
1d6baae6e0 Set ssl-strict false in songloader too 2020-11-03 18:48:35 +01:00
yavuzmert
fb0f48f08a Make context view top label selectable (#576)
Co-authored-by: Yavuz Mert <yavuz.mert@darkbluesystems.net>
2020-11-03 01:34:09 +01:00
Jonas Kvinge
76e5e03d31 Change copy command in snap dialog 2020-11-02 21:26:20 +01:00
Jonas Kvinge
4cab743634 Center playlist tabbar star icon
Fixes #574
2020-11-02 17:57:12 +01:00
Jonas Kvinge
7c10ec97b7 Remove unused parameter 2020-11-02 17:47:16 +01:00
Jonas Kvinge
8718a16889 Star/unstar playlist with doubleclick 2020-11-02 17:45:29 +01:00
Strawbs Bot
75a0b924c3 Update translations 2020-11-02 01:02:00 +01:00
Jonas Kvinge
83a90e0c05 Make tabbar style hack less intrusive 2020-11-01 21:54:23 +01:00
Strawbs Bot
e5eadd1315 Update translations 2020-10-31 14:34:41 +01:00
Jonas Kvinge
f0142d90d4 Update comment 2020-10-31 14:27:53 +01:00
Jonas Kvinge
cabd6e6e9d Override QStyle::subElementRect in fancy tabbar to fix style problems
Something is causing the contents of the tabbar to be stretched from top to bottom with space between icons and text.
You can see this on the default Fedora (Gnome) installation.
Also fixes the tabbar on macOS where the content was in the middle instead of the top.
2020-10-31 14:05:06 +01:00
Jonas Kvinge
4804a05736 Remove fixed width in about dialog as it crashes on wayland 2020-10-31 02:41:42 +01:00
Jonas Kvinge
c258e5a3af Add KDE global shortcuts
Fixes #572
2020-10-31 02:08:19 +01:00
Jonas Kvinge
e8492940a5 Add -fPIC compiler flag for Redhat/Fedora/CentOS
Fixes #573
2020-10-30 18:12:42 +01:00
Jonas Kvinge
4bccb1ab47 Only save as ID3v2, and remove empty tags
Fixes #571
2020-10-29 18:47:09 +01:00
Strawbs Bot
b6d219e232 Update translations 2020-10-28 17:50:58 +01:00
Jonas Kvinge
23ee17594d Simplify CMake Qt 2020-10-27 20:54:25 +01:00
Jonas Kvinge
d9d39d8e25 Only override MainWindow::nativeEvent for Windows 2020-10-27 20:17:28 +01:00
Jonas Kvinge
224d5d46c1 Set QApplication::setQuitOnLastWindowClosed to false 2020-10-27 20:01:07 +01:00
Jonas Kvinge
09e0059930 Resize organize window when copying to device
Fixes #566
2020-10-27 17:50:16 +01:00
Jonas Kvinge
0ddff2b087 Fix stupid bug 2020-10-27 17:47:40 +01:00
Jonas Kvinge
2e6a29eacc Remove ifdef HAVE_GSTREAMER
Fixes #568
2020-10-27 17:17:12 +01:00
Jonas Kvinge
ad2fb82aa9 Don't edit playlist name on doubleclick in playlists view
Fixes #567
2020-10-27 17:11:17 +01:00
Strawbs Bot
a3f91c11e8 Update translations 2020-10-26 01:02:24 +01:00
Jonas Kvinge
a50c978ce3 Fix cancelling logout when window is maxmimized
Fixes #565
2020-10-25 19:59:27 +01:00
Strawbs Bot
27d6f881cd Update translations 2020-10-25 01:04:50 +02:00
Jonas Kvinge
944cd020af Only strip problematic characters when saving a playlist 2020-10-25 01:01:43 +02:00
Jonas Kvinge
bbe5d64b99 Add info about backing up configuration to snap dialog 2020-10-25 01:01:16 +02:00
Jonas Kvinge
f7b36ac4c7 Replace use of QVariant::type() with Qt 6 2020-10-24 03:32:40 +02:00
Jonas Kvinge
1d555ca17e Turn back git revision 2020-10-24 03:30:21 +02:00
Jonas Kvinge
f91b6c3468 Release 0.8.3 2020-10-24 01:32:25 +02:00
Jonas Kvinge
abe6eeb350 Update Changelog 2020-10-23 19:35:07 +02:00
Jonas Kvinge
64f90a7912 Remove Qt MacExtras, no longer used 2020-10-22 23:11:44 +02:00
Jonas Kvinge
5733966843 Dont reset settingspage changed status when window is shown
This is done by Init in Load() when the settings is opened.
2020-10-22 20:25:31 +02:00
Jonas Kvinge
eb1344fcec Unref caps in HandoffCallback 2020-10-22 17:49:13 +02:00
Strawbs Bot
c5fb29f00e Update translations 2020-10-22 01:02:03 +02:00
Jonas Kvinge
63135b9c54 Engine will never be in playing state on error 2020-10-21 23:27:15 +02:00
Jonas Kvinge
f7c666584e Remove debug line 2020-10-21 21:33:26 +02:00
Jonas Kvinge
6834324de2 Add more unit tests for organizeformat 2020-10-21 21:32:30 +02:00
Jonas Kvinge
3a0d59e66f Only append path if not empty 2020-10-21 21:32:12 +02:00
Jonas Kvinge
ffd2e2188a Fix crash with empty APE tag for MPC files 2020-10-21 20:29:06 +02:00
Jonas Kvinge
0e8d5bdc5d Fix crash with empty APE tag for WavPack and APE files 2020-10-21 20:27:43 +02:00
Jonas Kvinge
14806f6614 Update Changelog 2020-10-21 19:57:00 +02:00
Jonas Kvinge
00ece83b9d Tidal: Set default quality to lossless 2020-10-21 19:56:37 +02:00
Jonas Kvinge
8197ae2a2d Tidal: Guess filetype by filename extension in URL when missing codec. 2020-10-21 01:12:46 +02:00
Strawbs Bot
5fe658bb16 Update translations 2020-10-21 01:02:16 +02:00
Jonas Kvinge
617179f0c6 Always set state to NULL in destructor 2020-10-21 00:32:55 +02:00
Jonas Kvinge
95ac85f642 Move stream discoverer from pipeline to engine
Fixes #491
2020-10-21 00:07:58 +02:00
Jonas Kvinge
ca8877ad47 Revert gst_discoverer_stop 2020-10-20 18:47:40 +02:00
Jonas Kvinge
6d8f31048c Add call to gst_discoverer_stop
Stream discoverer currently only works on Linux
2020-10-20 18:28:09 +02:00
Jonas Kvinge
ac859eb576 Make sure we query empty instead of null 2020-10-20 17:14:38 +02:00
Jonas Kvinge
2dfa171b6a Save effective_albumartist as empty istead of null
Since we use the metadata now instead of the container keys for queries in
the model, there is a regression when the values are null.
2020-10-20 17:12:28 +02:00
Jonas Kvinge
6f72e3e2ea Always append divider key to sort text if the key is a digit 2020-10-20 17:11:14 +02:00
Strawbs Bot
c2b73ae963 Update translations 2020-10-20 01:04:50 +02:00
Jonas Kvinge
6c50077409 Fix concurrentrun test 2020-10-19 22:33:08 +02:00
Jonas Kvinge
06746449a1 Disable TestContainerNodes 2020-10-19 22:32:50 +02:00
Jonas Kvinge
da7b8edf51 Revert back to using PathWithoutFilenameExtension in organizedialog 2020-10-19 21:35:39 +02:00
Jonas Kvinge
6e29b41f23 Update Changelog 2020-10-19 21:07:25 +02:00
Jonas Kvinge
912bb069af Fix updating album cover to collection for edit tag dialog 2020-10-19 21:05:59 +02:00
Jonas Kvinge
6b2d7a67d8 Use Utilities::FiddleFileExtension in organize 2020-10-19 19:56:40 +02:00
Jonas Kvinge
dbb8ec0290 Remove debug in bus callbacks 2020-10-19 19:09:48 +02:00
Strawbs Bot
60b32760f2 Update translations 2020-10-19 01:03:27 +02:00
Jonas Kvinge
73a40bcb49 Update Changelog 2020-10-18 17:08:28 +02:00
Jonas Kvinge
0e258a5a32 Remove unused utilities functions 2020-10-18 14:12:01 +02:00
Jonas Kvinge
7ca65c81d8 Use QFileInfo instead of custom functions 2020-10-18 14:07:48 +02:00
Jonas Kvinge
2ad1a60e59 Use correct file extension in organize preview and resulting filename
Fixes #564
2020-10-18 13:24:33 +02:00
Jonas Kvinge
cf17ff4478 Update Changelog 2020-10-18 00:29:28 +02:00
Jonas Kvinge
fffc3aac68 Update Changelog 2020-10-17 21:11:06 +02:00
Jonas Kvinge
295ac3c458 Add back thumbbar 2020-10-17 21:10:57 +02:00
Jonas Kvinge
b6693a71f9 Rename initialise to initialize 2020-10-17 17:29:09 +02:00
Jonas Kvinge
5b21118a8c Replace gst_tag_list_free with gst_tag_list_unref 2020-10-17 04:54:46 +02:00
Jonas Kvinge
0235b19801 Only update buttons once 2020-10-17 04:21:11 +02:00
Jonas Kvinge
7426399aa2 Enable thumbbar for debug build 2020-10-17 03:41:28 +02:00
Jonas Kvinge
6861b0d668 Possible fix Windows thumbbar 2020-10-17 03:37:39 +02:00
Jonas Kvinge
c30fb0d38c Log errors in MM device finder 2020-10-17 03:19:13 +02:00
Jonas Kvinge
e45521c6c0 Fix updating playing widget song details in small mode 2020-10-16 23:57:18 +02:00
Strawbs Bot
d11fe8d4fc Update translations 2020-10-16 01:01:51 +02:00
Jonas Kvinge
8e83e63e3d Add snap warning dialog 2020-10-15 21:47:52 +02:00
Jonas Kvinge
c8fd0ac4b3 Update snapcraft.yaml 2020-10-15 18:51:21 +02:00
Jonas Kvinge
d78419eb33 Enable WASAPI plugin
Fixes #283
2020-10-15 16:08:59 +02:00
Jonas Kvinge
e44a3d013d Disable windows thumbbar
Fixes stability issues with WASAPI
2020-10-15 16:07:20 +02:00
Jonas Kvinge
aeb0d05017 Add CoInitializeEx() to mmdevice finder and refactor code 2020-10-15 16:06:07 +02:00
Jonas Kvinge
62702e4b3d Add space 2020-10-14 22:53:58 +02:00
Jonas Kvinge
5146cdfa2f Improve windows thumbbar code 2020-10-14 22:53:08 +02:00
Jonas Kvinge
246e7018c3 Remove concurrentrun.h 2020-10-14 22:50:19 +02:00
Jonas Kvinge
24286dbe9d Use QtConcurrent::run directly 2020-10-14 22:49:37 +02:00
Jonas Kvinge
2f442dfbe1 Ignore return value 2020-10-14 22:38:32 +02:00
Jonas Kvinge
b2fb01ee9c Use QtConcurrent 2020-10-14 22:35:54 +02:00
Jonas Kvinge
45e0a9a4ef Remove build-snap from CI 2020-10-14 18:36:29 +02:00
Jonas Kvinge
675b7b4bf4 Fix CI 2020-10-14 18:05:04 +02:00
Jonas Kvinge
be7a35443e Add gdb to nsi 2020-10-14 16:55:17 +02:00
Strawbs Bot
9918615fcd Update translations 2020-10-14 01:02:02 +02:00
Jonas Kvinge
4b72ef77c1 Turn back git revision 2020-10-14 00:26:35 +02:00
Jonas Kvinge
e427c61fbb Release 0.8.2 2020-10-13 18:03:05 +02:00
Jonas Kvinge
c78e8937d5 Update ISSUE_TEMPLATE.md 2020-10-13 01:50:32 +02:00
Jonas Kvinge
5d63f7a93d Update ISSUE_TEMPLATE.md 2020-10-13 01:50:03 +02:00
Jonas Kvinge
46551ada7f Update Changelog 2020-10-13 01:47:22 +02:00
Jonas Kvinge
83f17a37b1 Use format only when available 2020-10-13 01:38:09 +02:00
Strawbs Bot
9e23e0a623 Update translations 2020-10-13 01:05:54 +02:00
Jonas Kvinge
4a53d4f043 Ignore "IDirectSoundBuffer_GetStatus The operation completed successfully"
Fixes #557
2020-10-13 00:49:34 +02:00
Jonas Kvinge
0fd61945c7 Fix saving initial settings 2020-10-12 17:20:18 +02:00
Strawbs Bot
e3624eed30 Update translations 2020-10-12 01:03:47 +02:00
Jonas Kvinge
1923c8be0c Remove unused includes 2020-10-11 01:31:12 +02:00
Jonas Kvinge
a3e37fbfe2 Remove use of HTML in system tray icon tooltip 2020-10-11 01:29:26 +02:00
Jonas Kvinge
318c3bb422 Check if QNetworkRequest::ContentTypeHeader is filetype everwhere 2020-10-11 01:08:42 +02:00
Strawbs Bot
db96e24028 Update translations 2020-10-11 01:02:04 +02:00
Jonas Kvinge
cc2cce66df Make const 2020-10-11 00:07:38 +02:00
Jonas Kvinge
7afeabd288 Subsonic: Read created from album too
Fixes #526
2020-10-11 00:05:06 +02:00
Jonas Kvinge
eb43a812d6 Fix saving subsonic cover when ContentTypeHeader returns filetype 2020-10-10 23:46:29 +02:00
Jonas Kvinge
12cbcdb6f4 Fix SQL query by song id when song id is a string 2020-10-10 23:44:42 +02:00
Jonas Kvinge
a2e35e30dc Use lowercase for divider keys sort text
Fixes #556
2020-10-10 18:44:57 +02:00
Jonas Kvinge
b6ff7e6b47 Fix transition to next song in CUE files
Fixes #552
2020-10-10 01:57:02 +02:00
Jonas Kvinge
6dba40c6bb Turn back git revision 2020-10-10 01:21:13 +02:00
285 changed files with 17276 additions and 9479 deletions

View File

@@ -155,77 +155,6 @@ commands:
hicolor-icon-theme
install_centos_dependencies:
description: Install CentOS dependencies
steps:
- run:
name: Install epel-release
command: dnf install -y epel-release
- run:
name: Install epel-release-latest-8.noarch.rpm
command: dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
- run:
name: Install config-manager
command: dnf install -y 'dnf-command(config-manager)'
- run:
name: PowerTools
command: dnf config-manager --set-enabled PowerTools
- run:
name: DNF Clean All
command: dnf clean all
- run:
name: Update packages
command: dnf update -y
- run:
name: Install CentOS dependencies
command: >
dnf install -y
glibc
gcc-c++
make
libtool
cmake3
rpmdevtools
redhat-lsb-core
git
man
tar
gettext
boost-devel
fuse-devel
dbus-devel
libnotify-devel
gnutls-devel
sqlite-devel
protobuf-devel
protobuf-compiler
alsa-lib-devel
pulseaudio-libs-devel
qt5-devel
qt5-qtbase-devel
qt5-qtx11extras-devel
qt5-qttools-devel
fftw-devel
libchromaprint-devel
libcdio-devel
libgpod-devel
libmtp-devel
libjpeg-devel
cairo-devel
dbus-x11
xorg-x11-server-Xvfb
xorg-x11-xauth
vim-common
desktop-file-utils
libappstream-glib
appstream-data
hicolor-icon-theme
python3-pip
python3-devel
gstreamer1-devel
gstreamer1-plugins-base-devel
install_debian_dependencies:
description: Install Debian dependencies
steps:
@@ -326,7 +255,7 @@ jobs:
build_source:
docker:
- image: opensuse/leap:15.1
- image: opensuse/leap:15.2
steps:
- install_opensuse_dependencies
- checkout
@@ -359,18 +288,6 @@ jobs:
- build_rpm
build_fedora_31:
docker:
- image: fedora:31
environment:
RPM_BUILD_NCPUS: "2"
steps:
- install_fedora_dependencies
- checkout
- cmake
- build_source
- build_rpm
build_fedora_32:
docker:
- image: fedora:32
@@ -383,14 +300,13 @@ jobs:
- build_source
- build_rpm
build_centos_8:
build_fedora_33:
docker:
- image: centos:8
- image: fedora:33
environment:
RPM_BUILD_NCPUS: "2"
steps:
- install_centos_dependencies
- install_fedora_dependencies
- checkout
- cmake
- build_source
@@ -464,17 +380,11 @@ workflows:
only: /.*/
- build_fedora_31:
filters:
tags:
only: /.*/
- build_fedora_32:
filters:
tags:
only: /.*/
- build_centos_8:
- build_fedora_33:
filters:
tags:
only: /.*/

1
.github/FUNDING.yml vendored
View File

@@ -1 +1,2 @@
github: jonaski
patreon: jonaskvinge

View File

@@ -7,7 +7,12 @@ assignees: ''
---
For technical issues, questions and feature requests please use the forum on https://forum.strawberrymusicplayer.org/
For technical issues, questions and feature suggestions/requests please use the forum on https://forum.strawberrymusicplayer.org/
Check the Changelog to see if the issue is already fixed:
https://github.com/strawberrymusicplayer/strawberry/blob/master/Changelog
If it's fixed, try the latest development build from: https://builds.strawberrymusicplayer.org/
**Describe the bug**
A clear and concise description of what the bug is.

View File

@@ -7,7 +7,7 @@ jobs:
name: Create source tarball
runs-on: ubuntu-latest
container:
image: opensuse/leap:15.1
image: opensuse/leap:15.2
steps:
- uses: actions/checkout@v1.2.0
- name: Update packages
@@ -223,8 +223,8 @@ jobs:
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_opensuse_tumbleweed:
name: Build openSUSE Tumbleweed
build_opensuse_tumbleweed_qt5:
name: Build openSUSE Tumbleweed Qt 5
runs-on: ubuntu-latest
container:
image: opensuse/tumbleweed
@@ -287,7 +287,7 @@ jobs:
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_WITH_QT5=ON
- name: Create source tarball
working-directory: build
run: ../dist/scripts/maketarball.sh
@@ -301,8 +301,8 @@ jobs:
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_opensuse_qt6:
name: Build openSUSE Qt 6
build_opensuse_tumbleweed_qt6:
name: Build openSUSE Tumbleweed Qt 6
runs-on: ubuntu-latest
container:
image: opensuse/tumbleweed
@@ -352,7 +352,7 @@ jobs:
qt6-x11extras-devel
qt6-base-common-devel
qt6-sql-sqlite
qt6-qt5compat-devel
qt6-linguist-devel
libcdio-devel
libgpod-devel
libmtp-devel
@@ -368,9 +368,17 @@ jobs:
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_WITH_QT6=ON
- name: Build
- name: Create source tarball
working-directory: build
run: cmake --build . --config $BUILD_TYPE
run: ../dist/scripts/maketarball.sh
- name: Create RPM build sources directories
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
- name: Copy source tarball
working-directory: build
run: cp strawberry-*.tar.xz /usr/src/packages/SOURCES/
- name: Build RPM
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_fedora_32:
@@ -615,15 +623,21 @@ jobs:
steps:
- uses: actions/checkout@v1.2.0
- name: Install dnf-plugins-core
run: dnf install -y dnf-plugins-core
- name: Install epel-release
run: dnf install -y epel-release
- name: Install config-manager
run: dnf install -y 'dnf-command(config-manager)'
- name: Enable PowerTools
run: dnf config-manager --set-enabled PowerTools
- name: DNF Clean All
- name: Enable powertools
run: dnf config-manager --set-enabled powertools
- name: Clean all
run: dnf clean all
- name: DNF Update
- name: Update
run: dnf update -y
- name: Install CentOS dependencies
@@ -649,10 +663,9 @@ jobs:
gnutls-devel
sqlite-devel
protobuf-devel
protobuf-compiler
protobuf-c
alsa-lib-devel
pulseaudio-libs-devel
qt5-devel
qt5-qtbase-devel
qt5-qtx11extras-devel
qt5-qttools-devel
@@ -673,7 +686,89 @@ jobs:
hicolor-icon-theme
gstreamer1-devel
gstreamer1-plugins-base-devel
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: Create source tarball
working-directory: build
run: ../dist/scripts/maketarball.sh
- name: Create RPM build sources directories
working-directory: build
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
- name: Copy source tarball
working-directory: build
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
- name: Build RPM
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_mageia_7:
name: Build Mageia 7
runs-on: ubuntu-latest
container:
image: mageia:7
steps:
- uses: actions/checkout@v1.2.0
- name: Update packages
run: urpmi.update --auto -a
- name: Configure auto update
run: urpmi --auto --auto-update
- name: Install Mageia dependencies
run: >
urpmi --auto --force
urpmi-debuginfo-install
git
tar
rpmdevtools
make
cmake
glibc
binutils
gcc-c++
man
gettext
notification-daemon
dbus-devel
libgnutls-devel
lib64boost-devel
lib64protobuf-devel
protobuf-compiler
lib64sqlite3-devel
lib64alsa2-devel
lib64pulseaudio-devel
lib64notify-devel
lib64qt5core-devel
lib64qt5gui-devel
lib64qt5widgets-devel
lib64qt5network-devel
lib64qt5concurrent-devel
lib64qt5sql-devel
lib64qt5dbus-devel
lib64qt5x11extras-devel
lib64qt5help-devel
libqt5test-devel
lib64gstreamer1.0-devel
lib64gstreamer-plugins-base1.0-devel
lib64cdio-devel
lib64gpod-devel
lib64mtp-devel
lib64raw1394-devel
lib64chromaprint-devel
libfftw-devel
desktop-file-utils
appstream-util
libappstream-glib8
hicolor-icon-theme
qt5ct
lib64mesaegl1
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
@@ -995,15 +1090,11 @@ jobs:
run: dpkg-buildpackage -b -d -uc -us -nc -j2
build-macos:
name: Build macOS
runs-on: macos-latest
build-macos-catalina:
name: Build macOS Catalina
runs-on: macos-10.15
steps:
- uses: actions/checkout@v1.2.0
- name: Update
run: brew update
- name: Upgrade
run: brew upgrade
- name: Install packages
run: >
brew install
@@ -1061,8 +1152,73 @@ jobs:
run: make dmg
- uses: actions/upload-artifact@v2
with:
name: upload-macos
path: build/strawberry-*.dmg
name: upload-macos-catalina
path: build/strawberry-*-catalina.dmg
build-macos-bigsur:
name: Build macOS Big Sur
runs-on: macos-11.0
steps:
- uses: actions/checkout@v1.2.0
- name: Install packages
run: >
brew install
glib
pkgconfig
boost
libffi
protobuf
protobuf-c
qt
gettext
gnutls
fftw
sqlite
chromaprint
gstreamer
gst-plugins-base
gst-plugins-good
gst-plugins-bad
gst-plugins-ugly
gst-libav
libcdio
libmtp
create-dmg
taglib
- name: Delete conflicting taglib system headers
shell: bash
run: rm -rf /usr/local/include/taglib
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
env:
PKG_CONFIG_PATH: /usr/local/lib/pkgconfig
Qt5_DIR: /usr/local/opt/qt5/lib/cmake
Qt5LinguistTools_DIR: /usr/local/opt/qt5/lib/cmake/Qt5LinguistTools
GST_SCANNER_PATH: /usr/local/opt/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner
GST_PLUGIN_PATH: /usr/local/lib/gstreamer-1.0
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DUSE_BUNDLE=ON
- name: Build
working-directory: build
shell: bash
run: cmake --build . --config $BUILD_TYPE
- name: Install
working-directory: build
shell: bash
run: make install
- name: Create DMG
working-directory: build
shell: bash
run: make dmg
- uses: actions/upload-artifact@v2
with:
name: upload-macos-bigsur
path: build/strawberry-*-bigsur.dmg
build-windows:
@@ -1179,7 +1335,7 @@ jobs:
- name: Copy extra binaries
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/{sqlite3.exe,killproc.exe,liborc-0.4-0.dll} .
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/{sqlite3.exe,killproc.exe,gst-launch-1.0.exe} .
- name: Copy dependencies
working-directory: build
@@ -1208,12 +1364,12 @@ jobs:
run: makensis strawberry.nsi
upload-macos:
name: Upload macOS DMG
upload-macos-catalina:
name: Upload macOS Catalina DMG
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
needs:
- build-macos
- build-macos-catalina
steps:
- uses: actions/checkout@v1.2.0
- uses: actions/download-artifact@v2
@@ -1227,25 +1383,29 @@ jobs:
- name: rsync
run: |
set -x
for i in $(find uploads -type f -name '*.dmg'); do
for i in $(find uploads -type f -name 'strawberry-*-catalina.dmg'); do
rsync -e "ssh -p 50220 -o StrictHostKeyChecking=no" -va $i travis@echoes.jkvinge.net:/home/travis/builds/macos/catalina/
done
build_snap:
name: Build Snap
runs-on: ubuntu-18.04
upload-macos-bigsur:
name: Upload macOS Big Sur DMG
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
needs:
- build-macos-bigsur
steps:
- uses: actions/checkout@v1.2.0
- uses: snapcore/action-build@v1
id: snapcraft
- uses: snapcore/action-publish@v1
if: github.ref == 'refs/heads/master'
- uses: actions/download-artifact@v2
with:
store_login: ${{ secrets.SNAP_STORE_LOGIN }}
snap: ${{ steps.snapcraft.outputs.snap }}
release: beta
- uses: actions/upload-artifact@v2
path: uploads
- name: Install SSH keys
uses: shimataro/ssh-key-action@v2
with:
name: release_snap
path: ${{ steps.snapcraft.outputs.snap }}
known_hosts: ${{ secrets.KNOWN_HOSTS2 }}
key: ${{ secrets.SSH_KEY }}
- name: rsync
run: |
set -x
for i in $(find uploads -type f -name 'strawberry-*-bigsur.dmg'); do
rsync -e "ssh -p 50220 -o StrictHostKeyChecking=no" -va $i travis@echoes.jkvinge.net:/home/travis/builds/macos/bigsur/
done

View File

@@ -1,9 +1,8 @@
sudo: required
language: C++
os:
- osx
compiler:
- gcc
os: osx
osx_image: xcode11.3
compiler: clang
before_install:
- if ! [ "$DEPLOY_KEY_ENC" == "" ]; then
@@ -13,8 +12,8 @@ before_install:
- git fetch --unshallow
- git pull
- brew update
- travis_wait 120 brew upgrade || echo "Failed"
- travis_wait 120 brew upgrade || echo "Failed"
- travis_wait 400 brew upgrade || echo "Failed"
- travis_wait 400 brew upgrade || echo "Failed"
- brew install glib pkgconfig libffi protobuf protobuf-c qt gettext gnutls fftw sqlite chromaprint zlib taglib
- brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
- brew install libcdio libmtp
@@ -32,14 +31,12 @@ before_script:
script:
- make -j8
- make install
- make dmg
- make dmg2
after_success:
- ls -lh strawberry*.dmg
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$CC_FOR_BUILD" == "gcc" ]] && [ -f ~/.ssh/id_rsa ]; then
if [[ "$TRAVIS_BRANCH" == "master" ]]; then
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos/highsierra/;
elif [[ "$TRAVIS_BRANCH" == "macos" ]]; then
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos/highsierra/;
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [ -f ~/.ssh/id_rsa ]; then
if [[ "$TRAVIS_BRANCH" == "master" ]] || [[ "$TRAVIS_BRANCH" == "travis" ]]; then
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos/mojave/;
fi
fi

View File

@@ -16,11 +16,6 @@ else()
qt5_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
endif()
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
target_include_directories(singleapplication SYSTEM PRIVATE
${QtCore_INCLUDE_DIRS}
${QtWidgets_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(singleapplication PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
@@ -39,10 +34,6 @@ else()
qt5_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
endif()
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
target_include_directories(singlecoreapplication SYSTEM PRIVATE
${QtCore_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(singlecoreapplication PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}

View File

@@ -1,2 +1 @@
cmake_minimum_required(VERSION 3.0)
set(CMAKE_CXX_STANDARD 11)

View File

@@ -11,13 +11,6 @@ include(cmake/Version.cmake)
include(cmake/Summary.cmake)
include(cmake/OptionalSource.cmake)
include(cmake/ParseArguments.cmake)
include(cmake/Rpm.cmake)
include(cmake/Deb.cmake)
if(APPLE)
include(cmake/Dmg.cmake)
endif()
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(LINUX ON)
@@ -29,13 +22,22 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
set(OPENBSD ON)
endif()
set(CMAKE_CXX_STANDARD 11)
if(LINUX)
include(cmake/Rpm.cmake)
include(cmake/Deb.cmake)
endif()
if(APPLE)
include(cmake/Dmg.cmake)
endif()
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
list(APPEND COMPILE_OPTIONS
$<$<COMPILE_LANGUAGE:C>:--std=c99>
$<$<COMPILE_LANGUAGE:CXX>:--std=c++11>
-U__STRICT_ANSI__
$<$<COMPILE_LANGUAGE:C>:-std=c99>
$<$<COMPILE_LANGUAGE:CXX>:-std=c++17>
-Wall
-Wextra
-Wpedantic
@@ -133,6 +135,11 @@ pkg_check_modules(GDK_PIXBUF gdk-pixbuf-2.0)
find_package(Gettext)
find_package(FFTW3)
if(NOT QT_DEFAULT_MAJOR_VERSION)
set(QT_DEFAULT_MAJOR_VERSION 5)
endif()
set(QT_MAJOR_VERSION ${QT_DEFAULT_MAJOR_VERSION} CACHE STRING "Qt version to use (5 or 6), defaults to ${QT_DEFAULT_MAJOR_VERSION}")
option(BUILD_WITH_QT5 "Use Qt 5" OFF)
option(BUILD_WITH_QT6 "Use Qt 6" OFF)
@@ -140,91 +147,66 @@ if(WITH_QT6)
set(BUILD_WITH_QT6 ON)
endif()
if(NOT BUILD_WITH_QT5 AND NOT BUILD_WITH_QT6)
set(BUILD_WITH_QT5 ON)
if(BUILD_WITH_QT5)
set(QT_MAJOR_VERSION 5)
elseif(BUILD_WITH_QT6)
set(QT_MAJOR_VERSION 6)
else()
if(QT_MAJOR_VERSION EQUAL 5)
set(BUILD_WITH_QT5 ON)
elseif(QT_MAJOR_VERSION EQUAL 6)
set(BUILD_WITH_QT6 ON)
else()
set(BUILD_WITH_QT5 ON)
set(QT_MAJOR_VERSION 5)
endif()
endif()
set(QT_COMPONENTS Core Concurrent Widgets Network Sql)
if(X11_FOUND)
list(APPEND QT_COMPONENTS X11Extras)
endif()
if(DBUS_FOUND)
list(APPEND QT_COMPONENTS DBus)
endif()
if(APPLE)
list(APPEND QT_COMPONENTS MacExtras)
endif()
if(WIN32)
list(APPEND QT_COMPONENTS WinExtras)
unset(OPTIONAL_COMPONENTS)
if(QT_MAJOR_VERSION EQUAL 5)
set(QT_MIN_VERSION 5.8)
endif()
if(BUILD_WITH_QT6)
list(APPEND QT_COMPONENTS Core5Compat)
find_package(Qt6 REQUIRED COMPONENTS ${QT_COMPONENTS})
set(QtCore_LIBRARIES Qt6::Core)
set(QtConcurrent_LIBRARIES Qt6::Concurrent)
set(QtWidgets_LIBRARIES Qt6::Widgets)
set(QtNetwork_LIBRARIES Qt6::Network)
set(QtSql_LIBRARIES Qt6::Sql)
set(QT_LIBRARIES Qt6::Core Qt6::Concurrent Qt6::Widgets Qt6::Network Qt6::Sql Qt6::Core5Compat)
if(Qt6DBus_FOUND)
set(QtDBus_LIBRARIES Qt6::DBus)
list(APPEND QT_LIBRARIES Qt6::DBus)
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt6::qdbusxml2cpp LOCATION)
endif()
if(Qt6X11Extras_FOUND)
set(QtX11Extras_LIBRARIES Qt6::X11Extras)
list(APPEND QT_LIBRARIES Qt6::X11Extras)
endif()
if(Qt6MacExtras_FOUND)
set(QtMacExtras_LIBRARIES Qt6::MacExtras)
list(APPEND QT_LIBRARIES Qt6::MacExtras)
endif()
if(Qt6WinExtras_FOUND)
set(QtWinExtras_LIBRARIES Qt6::WinExtras)
list(APPEND QT_LIBRARIES Qt6::WinExtras)
endif()
find_package(Qt6 QUIET COMPONENTS LinguistTools CONFIG)
if (Qt6LinguistTools_FOUND)
set(QT_LCONVERT_EXECUTABLE Qt6::lconvert)
endif()
elseif(BUILD_WITH_QT5)
set(QT_MIN_VERSION 5.8)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS})
set(QtCore_LIBRARIES ${Qt5Core_LIBRARIES})
set(QtConcurrent_LIBRARIES ${Qt5Concurrent_LIBRARIES})
set(QtWidgets_LIBRARIES ${Qt5Widgets_LIBRARIES})
set(QtNetwork_LIBRARIES ${Qt5Network_LIBRARIES})
set(QtSql_LIBRARIES ${Qt5Sql_LIBRARIES})
set(QT_LIBRARIES ${QtCore_LIBRARIES} ${QtConcurrent_LIBRARIES} ${QtWidgets_LIBRARIES} ${QtNetwork_LIBRARIES} ${QtSql_LIBRARIES})
set(QT_INCLUDE_DIRS ${Qt5Core_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Sql_INCLUDE_DIRS})
if(Qt5DBus_FOUND)
set(QtDBus_LIBRARIES ${Qt5DBus_LIBRARIES})
list(APPEND QT_LIBRARIES ${Qt5DBus_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5DBus_INCLUDE_DIRS})
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt5::qdbusxml2cpp LOCATION)
endif()
if(Qt5X11Extras_FOUND)
set(QtX11Extras_LIBRARIES ${Qt5X11Extras_LIBRARIES})
list(APPEND QT_LIBRARIES ${Qt5X11Extras_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5X11Extras_INCLUDE_DIRS})
endif()
if(Qt5MacExtras_FOUND)
set(QtMacExtras_LIBRARIES ${Qt5MacExtras_LIBRARIES})
list(APPEND QT_LIBRARIES ${Qt5MacExtras_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5MacExtras_INCLUDE_DIRS})
endif()
if(Qt5WinExtras_FOUND)
set(QtWinExtras_LIBRARIES ${Qt5WinExtras_LIBRARIES})
list(APPEND QT_LIBRARIES ${Qt5WinExtras_LIBRARIES})
list(APPEND QT_INCLUDE_DIRS ${Qt5WinExtras_INCLUDE_DIRS})
endif()
find_package(Qt5 ${QT_MIN_VERSION} QUIET COMPONENTS LinguistTools CONFIG)
if (Qt5LinguistTools_FOUND)
set(QT_LCONVERT_EXECUTABLE Qt5::lconvert)
endif()
else()
message(FATAL_ERROR "Set BUILD_WITH_QT5 or BUILD_WITH_QT6")
if(DBUS_FOUND AND NOT WIN32)
list(APPEND QT_COMPONENTS DBus)
endif()
if(X11_FOUND)
list(APPEND OPTIONAL_COMPONENTS X11Extras)
endif()
if(WIN32)
list(APPEND OPTIONAL_COMPONENTS WinExtras)
endif()
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS} OPTIONAL_COMPONENTS ${OPTIONAL_COMPONENTS})
set(QtCore_LIBRARIES Qt${QT_MAJOR_VERSION}::Core)
set(QtConcurrent_LIBRARIES Qt${QT_MAJOR_VERSION}::Concurrent)
set(QtGui_LIBRARIES Qt${QT_MAJOR_VERSION}::Gui)
set(QtWidgets_LIBRARIES Qt${QT_MAJOR_VERSION}::Widgets)
set(QtNetwork_LIBRARIES Qt${QT_MAJOR_VERSION}::Network)
set(QtSql_LIBRARIES Qt${QT_MAJOR_VERSION}::Sql)
set(QT_LIBRARIES Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Concurrent Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Widgets Qt${QT_MAJOR_VERSION}::Network Qt${QT_MAJOR_VERSION}::Sql)
if(Qt${QT_MAJOR_VERSION}DBus_FOUND)
set(QtDBus_LIBRARIES Qt${QT_MAJOR_VERSION}::DBus)
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::DBus)
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt${QT_MAJOR_VERSION}::qdbusxml2cpp LOCATION)
endif()
if(Qt${QT_MAJOR_VERSION}X11Extras_FOUND)
set(QtX11Extras_LIBRARIES Qt${QT_MAJOR_VERSION}::X11Extras)
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::X11Extras)
set(HAVE_X11EXTRAS ON)
endif()
if(Qt${QT_MAJOR_VERSION}WinExtras_FOUND)
set(QtWinExtras_LIBRARIES Qt${QT_MAJOR_VERSION}::WinExtras)
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::WinExtras)
set(HAVE_WINEXTRAS ON)
endif()
find_package(Qt${QT_MAJOR_VERSION} QUIET COMPONENTS LinguistTools CONFIG)
if(Qt${QT_MAJOR_VERSION}LinguistTools_FOUND)
set(QT_LCONVERT_EXECUTABLE Qt${QT_MAJOR_VERSION}::lconvert)
endif()
if(X11_FOUND)
@@ -242,6 +224,15 @@ if(X11_FOUND)
endif()
endif(X11_FOUND)
find_path(QPA_QPLATFORMNATIVEINTERFACE_H qpa/qplatformnativeinterface.h PATHS ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS})
if(QPA_QPLATFORMNATIVEINTERFACE_H)
set(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H ON)
include_directories(${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS})
message(STATUS "Have qpa/qplatformnativeinterface.h header.")
else()
message(STATUS "Missing qpa/qplatformnativeinterface.h header.")
endif()
# TAGLIB
option(USE_SYSTEM_TAGLIB "Use system taglib" OFF)
if(USE_SYSTEM_TAGLIB)
@@ -336,6 +327,10 @@ optional_component(GLOBALSHORTCUTS ON "Global shortcuts"
DEPENDS "D-Bus, X11, Windows or macOS" HAVE_GLOBALSHORTCUTS_SUPPORT
)
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts"
DEPENDS "X11Extras" Qt${QT_MAJOR_VERSION}X11Extras_FOUND
)
optional_component(AUDIOCD ON "Devices: Audio CD support"
DEPENDS "libcdio" LIBCDIO_FOUND
)
@@ -404,9 +399,8 @@ endif(USE_BUNDLE AND NOT USE_BUNDLE_DIR)
# Check that we have sqlite3 with FTS5
if(NOT CMAKE_CROSSCOMPILING)
set(CMAKE_REQUIRED_FLAGS "--std=c++11")
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
set(CMAKE_REQUIRED_LIBRARIES ${QtCore_LIBRARIES} ${QtSql_LIBRARIES})
set(CMAKE_REQUIRED_INCLUDES ${QtCore_INCLUDE_DIRS} ${QtSql_INCLUDE_DIRS})
check_cxx_source_runs("
#include <QSqlDatabase>
#include <QSqlQuery>

View File

@@ -2,6 +2,78 @@ Strawberry Music Player
=======================
ChangeLog
0.8.5:
Bugfixes:
* Fix return type of SmartPlaylistQueryWizardPlugin::type().
* Fix comparison between QChar and integer to use QChar::unicode().
* Fix return type of qHash with Qt 6 to use size_t instead of uint.
* Fix tag edit dialog save process sometimes stuck.
* Fix repeat and shuffle buttons greyed out when a dynamic playlist is open.
* Fix CUE parser handling of values with empty quotes.
* Fix broken year and disc collection groupings with CUE songs.
* Fix HTML escaping showing up in OSD notifications when using custom text.
Enhancements:
* Add Swedish translation.
* Made Qt X11Extras and WinExtras modules optional.
* Save and restore geometry in edit tag dialog.
* Add command line option to play a playlist based on name.
* Change double-click behaviour in cover manager to open fullsize cover.
0.8.4:
Bugfixes:
* Fix preventing session logout when window is maxmimized.
* Fix empty space in organize window when copying songs/playlists to devices.
* Fix crash when opening about dialog in a wayland session.
* Fix stretched fancy/side tabbar style issue with adwaita style (Fedora/Gnome).
* Fix centering star icon on playlist tabbar.
* Fix network proxy settings for streaming.
* Fix copy URL to clipboard to handle non-ASCII characters.
* Fix HiDPI scaling for glow animation and drag over playlist.
* Fix smart playlist search by filename.
* Fix single letter collection nodes showing before dividers.
Enhancements:
* Add support for native global shortcuts on KDE.
* Add track progress in system tray icon as an option.
* Only strip problematic characters in suggested filename when saving a playlist to file.
* Change star/unstar playlist to doubleclick instead of singleclick.
* Don't edit playlist name on doubleclick in playlists view.
* Make context view top label text selectable.
* Add setting to change Qt style.
* Clear ID3v3 tags that are empty, and clear ID3v1 tags when setting ID3v3 tags.
* Remove remaining uses of QTextCodec.
* Remove Core5Compat dependency.
0.8.3:
Bugfixes:
* Fixed updating playing widget song details in small cover mode.
* Fixed file extension when transcoding songs.
* Fixed updating album cover to collection in edit tag dialog when pressing save.
* Fixed songs with empty artist in collection.
* Fixed possible crashes with stream discovery.
* Fixed setting engine state to null.
* Fixed tagreader crash with empty APE tags.
* Fixed a gstreamer memory leak.
Enhancements:
* (Windows) Added WASAPI plugin.
0.8.2:
Bugfixes:
* Fixed broken transition to next song for CUE files with certain audio formats (regression since version 0.6.13).
* Fixed all collection divider keys showing on top with some language collate settings (regression in version 0.8.1).
* Fixed SQL querying songs by song ID when song ID is a string.
* Fixed saving album covers for LMS Subsonic servers.
* Fixed reading song creation dates with LMS Subsonic servers.
* Fixed saving initial settings.
* Removed use of HTML in system tray icon tooltip for all desktop environments instead of just KDE and Cinnamon.
* (Windows) Ignore "IDirectSoundBuffer_GetStatus The operation completed successfully" false error when switching device while playing.
0.8.1:
Bugfixes:

View File

@@ -1,5 +1,6 @@
:strawberry: Strawberry Music Player [![Build Status](https://github.com/strawberrymusicplayer/strawberry/workflows/C/C++%20CI/badge.svg)](https://github.com/strawberrymusicplayer/strawberry/actions)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/jonaskvinge)
[![PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/jonaskvinge)
[![Patreon](https://img.shields.io/badge/patreon-donate-green.svg)](https://patreon.com/jonaskvinge)
=======================
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles. It's written in C++ using the Qt toolkit.
@@ -65,11 +66,11 @@ To build Strawberry from source you need the following installed on your system
* [GLib](https://developer.gnome.org/glib/)
* [Protobuf library and compiler](https://developers.google.com/protocol-buffers/)
* [Qt 5.8 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
* [Qt components X11Extras and DBus for Linux/BSD, MacExtras for macOS and WinExtras for Windows](https://www.qt.io/)
* [Qt components X11Extras and D-Bus for Linux/BSD and WinExtras for Windows](https://www.qt.io/)
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
* [Chromaprint library](https://acoustid.org/chromaprint)
* [ALSA library (linux)](https://www.alsa-project.org/)
* [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
* [D-Bus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
* [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?)
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
* [GnuTLS](https://www.gnutls.org/)
@@ -84,8 +85,6 @@ Optional dependencies:
Either GStreamer or VLC engine is required, but only GStreamer is fully implemented so far.
You should also install the gstreamer plugins base and good, and optionally bad and ugly.
With Qt 6 we also depend on the Core5Compat module for QTextCodec.
### :wrench: Compiling from source
### Get the code:

View File

@@ -9,8 +9,7 @@ if (LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if (DEB_CODENAME)
configure_file(${CMAKE_SOURCE_DIR}/debian/changelog.in ${CMAKE_SOURCE_DIR}/debian/changelog)
if (DEB_CODENAME AND DEB_DATE)
add_custom_target(deb
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us

View File

@@ -6,3 +6,10 @@ add_custom_target(dmg
COMMAND create-dmg --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${MACOS_VERSION_PACKAGE}.dmg strawberry.app
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(dmg2
COMMAND /usr/local/opt/qt5/bin/macdeployqt strawberry.app
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macdeploy.py strawberry.app
COMMAND create-dmg --skip-jenkins --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${MACOS_VERSION_PACKAGE}.dmg strawberry.app
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

View File

@@ -1,7 +1,5 @@
# From http://www.cmake.org/Wiki/CMakeMacroParseArguments
cmake_minimum_required(VERSION 2.6)
MACRO(PARSE_ARGUMENTS prefix arg_names option_names)
SET(DEFAULT_ARGS)
FOREACH(arg_name ${arg_names})

View File

@@ -17,6 +17,7 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if (DIST_NAME)
message(STATUS "Distro Name: ${DIST_NAME}")
if (DIST_RELEASE)
message(STATUS "Distro Release: ${DIST_RELEASE}")
@@ -24,45 +25,40 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
if (DIST_VERSION)
message(STATUS "Distro Version: ${DIST_VERSION}")
endif()
set(RPMBUILD_DIR ~/rpmbuild CACHE STRING "Rpmbuild directory, for the rpm target")
if (${DIST_NAME} STREQUAL "opensuse")
if (DIST_RELEASE)
if (${DIST_RELEASE} STREQUAL "leap")
if (DIST_VERSION)
set(RPM_DISTRO "lp${DIST_VERSION}" CACHE STRING "Suffix of the rpm file")
else()
set(RPM_DISTRO ${DIST_RELEASE} CACHE STRING "Suffix of the rpm file")
endif()
elseif (${DIST_RELEASE} STREQUAL "tumbleweed")
set(RPM_DISTRO ${DIST_RELEASE} CACHE STRING "Suffix of the rpm file")
else ()
set(RPM_DISTRO ${DIST_NAME} CACHE STRING "Suffix of the rpm file")
if (${DIST_NAME} STREQUAL "opensuse" AND DIST_RELEASE)
if (${DIST_RELEASE} STREQUAL "leap")
if (DIST_VERSION)
set(RPM_DISTRO "lp${DIST_VERSION}")
else()
set(RPM_DISTRO ${DIST_RELEASE})
endif()
else()
set(RPM_DISTRO ${DIST_NAME} CACHE STRING "Suffix of the rpm file")
elseif (${DIST_RELEASE} STREQUAL "tumbleweed")
set(RPM_DISTRO ${DIST_RELEASE})
endif()
elseif (${DIST_NAME} STREQUAL "fedora")
if (DIST_VERSION)
set(RPM_DISTRO "fc${DIST_VERSION}" CACHE STRING "Suffix of the rpm file")
else ()
set(RPM_DISTRO ${DIST_NAME} CACHE STRING "Suffix of the rpm file")
endif()
elseif (${DIST_NAME} STREQUAL "mageia")
if (DIST_RELEASE)
set(RPM_DISTRO "mga${DIST_RELEASE}" CACHE STRING "Suffix of the rpm file")
else ()
set(RPM_DISTRO ${DIST_NAME} CACHE STRING "Suffix of the rpm file")
endif()
else()
set(RPM_DISTRO ${DIST_NAME} CACHE STRING "Suffix of the rpm file")
elseif (${DIST_NAME} STREQUAL "fedora" AND DIST_VERSION)
set(RPM_DISTRO "fc${DIST_VERSION}")
elseif (${DIST_NAME} STREQUAL "centos" AND DIST_VERSION)
set(RPM_DISTRO "el${DIST_VERSION}")
elseif (${DIST_NAME} STREQUAL "mageia" AND DIST_RELEASE)
set(RPM_DISTRO "mga${DIST_RELEASE}")
endif()
if(NOT RPM_DISTRO)
set(RPM_DISTRO ${DIST_NAME})
endif()
message(STATUS "RPM Suffix: ${RPM_DISTRO}")
configure_file(${CMAKE_SOURCE_DIR}/dist/unix/strawberry.spec.in ${CMAKE_SOURCE_DIR}/dist/unix/strawberry.spec @ONLY)
add_custom_target(rpm
COMMAND ${CMAKE_SOURCE_DIR}/dist/scripts/maketarball.sh
COMMAND ${CMAKE_COMMAND} -E copy strawberry-${STRAWBERRY_VERSION_PACKAGE}.tar.xz ${RPMBUILD_DIR}/SOURCES/
COMMAND ${RPMBUILD_EXEC} -bs ${CMAKE_SOURCE_DIR}/dist/unix/strawberry.spec
COMMAND ${RPMBUILD_EXEC} -bb ${CMAKE_SOURCE_DIR}/dist/unix/strawberry.spec
)
endif()
endif()

View File

@@ -1,5 +1,3 @@
cmake_minimum_required(VERSION 3.0)
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
if(NOT GETTEXT_XGETTEXT_EXECUTABLE)
message(FATAL_ERROR "Could not find xgettext executable")

View File

@@ -1,6 +1,6 @@
set(STRAWBERRY_VERSION_MAJOR 0)
set(STRAWBERRY_VERSION_MINOR 8)
set(STRAWBERRY_VERSION_PATCH 1)
set(STRAWBERRY_VERSION_PATCH 5)
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF)

View File

@@ -17,9 +17,9 @@
<file>schema/device-schema.sql</file>
<file>style/strawberry.css</file>
<file>style/smartplaylistsearchterm.css</file>
<file>html/playing-tooltip.html</file>
<file>html/oauthsuccess.html</file>
<file>pictures/strawberry.png</file>
<file>pictures/strawberry-grey.png</file>
<file>pictures/strawberry-faded.png</file>
<file>pictures/strawbs.png</file>
<file>pictures/nomusic.png</file>

View File

@@ -1,41 +0,0 @@
<table cellspacing="5" cellpadding="5">
<tr>
<td colspan="%columns">
<center><h4>%appName</h4></center>
</td>
</tr>
<tr>
%image
<td>
<table cellspacing="1" cellpadding="1">
<tr>
<td>
<p align="right">%titleKey</p>
</td>
<td>%titleValue</td>
</tr>
<tr>
<td>
<p align="right">%artistKey</p>
</td>
<td>%artistValue</td>
</tr>
<tr>
<td>
<p align="right">%albumKey</p>
</td>
<td>%albumValue</td>
</tr>
<tr>
<td colspan="2" height="20" />
</tr>
<tr>
<td>
<p align="right">%lengthKey</p>
</td>
<td>%lengthValue</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -72,6 +72,7 @@
<file>icons/128x128/star-grey.png</file>
<file>icons/128x128/star.png</file>
<file>icons/128x128/strawberry.png</file>
<file>icons/128x128/strawberry-grey.png</file>
<file>icons/128x128/tools-wizard.png</file>
<file>icons/128x128/view-choose.png</file>
<file>icons/128x128/view-fullscreen.png</file>
@@ -164,6 +165,7 @@
<file>icons/64x64/star-grey.png</file>
<file>icons/64x64/star.png</file>
<file>icons/64x64/strawberry.png</file>
<file>icons/64x64/strawberry-grey.png</file>
<file>icons/64x64/tools-wizard.png</file>
<file>icons/64x64/view-choose.png</file>
<file>icons/64x64/view-fullscreen.png</file>
@@ -260,6 +262,7 @@
<file>icons/48x48/star-grey.png</file>
<file>icons/48x48/star.png</file>
<file>icons/48x48/strawberry.png</file>
<file>icons/48x48/strawberry-grey.png</file>
<file>icons/48x48/tools-wizard.png</file>
<file>icons/48x48/view-choose.png</file>
<file>icons/48x48/view-fullscreen.png</file>
@@ -356,6 +359,7 @@
<file>icons/32x32/star-grey.png</file>
<file>icons/32x32/star.png</file>
<file>icons/32x32/strawberry.png</file>
<file>icons/32x32/strawberry-grey.png</file>
<file>icons/32x32/tools-wizard.png</file>
<file>icons/32x32/view-choose.png</file>
<file>icons/32x32/view-fullscreen.png</file>
@@ -452,6 +456,7 @@
<file>icons/22x22/star-grey.png</file>
<file>icons/22x22/star.png</file>
<file>icons/22x22/strawberry.png</file>
<file>icons/22x22/strawberry-grey.png</file>
<file>icons/22x22/tools-wizard.png</file>
<file>icons/22x22/view-choose.png</file>
<file>icons/22x22/view-fullscreen.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

7
dist/CMakeLists.txt vendored
View File

@@ -1,4 +1,10 @@
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh.in ${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh @ONLY)
if(RPM_DISTRO AND RPM_DATE)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec @ONLY)
endif(RPM_DISTRO AND RPM_DATE)
if(DEB_CODENAME AND DEB_DATE)
configure_file(${CMAKE_SOURCE_DIR}/debian/changelog.in ${CMAKE_SOURCE_DIR}/debian/changelog)
endif(DEB_CODENAME AND DEB_DATE)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD @ONLY)
if (APPLE)
@@ -7,7 +13,6 @@ endif (APPLE)
if (WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry-wasapi.nsi.in ${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry-wasapi.nsi @ONLY)
endif (WIN32)
if (UNIX AND NOT APPLE)

View File

@@ -88,7 +88,6 @@ GSTREAMER_PLUGINS = [
'libgstosxaudio.dylib',
'libgstplayback.dylib',
'libgstrawparse.dylib',
'libgstrealmedia.dylib',
'libgstreplaygain.dylib',
'libgstsoup.dylib',
'libgstspectrum.dylib',

View File

@@ -1,14 +1,22 @@
#!/bin/sh
macos_version=$(sw_vers -productVersion| awk -F '[.]' '{print $2}')
macos_codenames=(
["13"]="highsierra"
["14"]="mojave"
["15"]="catalina"
)
macos_version=$(sw_vers -productVersion)
macos_version_major=$(echo $macos_version | awk -F '[.]' '{print $1}')
macos_version_minor=$(echo $macos_version | awk -F '[.]' '{print $2}')
if [[ -n "${macos_codenames[$macos_version]}" ]]; then
echo "${macos_codenames[$macos_version]}"
if [ "${macos_version_major}" = "10" ]; then
macos_codenames=(
["13"]="highsierra"
["14"]="mojave"
["15"]="catalina"
)
if [[ -n "${macos_codenames[$macos_version_minor]}" ]]; then
echo "${macos_codenames[$macos_version_minor]}"
else
echo "unknown"
fi
elif [ "${macos_version_major}" = "11" ]; then
echo "bigsur"
else
echo "unknown"
fi

View File

@@ -1,12 +1,15 @@
Name: strawberry
Version: @STRAWBERRY_VERSION_RPM_V@
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
Release: @STRAWBERRY_VERSION_RPM_R@%{?dist}
%else
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
%endif
Summary: A music player and music collection organizer
Group: Applications/Multimedia
Group: Productivity/Multimedia/Sound/Players
License: GPL-3.0+
URL: https://www.strawberrymusicplayer.org/
Source0: %{name}-@STRAWBERRY_VERSION_PACKAGE@.tar.xz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
%if 0%{?suse_version} && 0%{?suse_version} > 1325
BuildRequires: libboost_headers-devel
@@ -26,14 +29,11 @@ BuildRequires: update-desktop-files
%if 0%{?suse_version}
BuildRequires: appstream-glib
%else
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
BuildRequires: libappstream-glib
%else
%else
BuildRequires: appstream-util
%endif
%endif
%if 0%{?suse_version} || 0%{?fedora_version} || 0%{?mageia}
BuildRequires: cmake(Qt5LinguistTools)
%endif
%endif
BuildRequires: pkgconfig
BuildRequires: pkgconfig(glib-2.0)
@@ -49,15 +49,29 @@ BuildRequires: pkgconfig(sqlite3) >= 3.9
BuildRequires: pkgconfig(taglib)
%endif
BuildRequires: pkgconfig(fftw3)
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Gui)
BuildRequires: pkgconfig(Qt5Widgets)
BuildRequires: pkgconfig(Qt5Concurrent)
BuildRequires: pkgconfig(Qt5Network)
BuildRequires: pkgconfig(Qt5Sql)
BuildRequires: pkgconfig(Qt5X11Extras)
BuildRequires: pkgconfig(Qt5DBus)
BuildRequires: pkgconfig(Qt5Test)
%if "@QT_MAJOR_VERSION@" == "5" && ( 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} )
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Core)
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Gui)
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Widgets)
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Concurrent)
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Network)
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Sql)
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@X11Extras)
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@DBus)
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Test)
%else
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Core)
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Gui)
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Widgets)
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Concurrent)
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Network)
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Sql)
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@DBus)
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Test)
%endif
%if 0%{?suse_version} || 0%{?fedora_version} || 0%{?mageia}
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@LinguistTools)
%endif
BuildRequires: pkgconfig(gstreamer-1.0)
BuildRequires: pkgconfig(gstreamer-app-1.0)
BuildRequires: pkgconfig(gstreamer-audio-1.0)
@@ -70,14 +84,17 @@ BuildRequires: pkgconfig(libpulse)
BuildRequires: pkgconfig(libcdio)
BuildRequires: pkgconfig(libgpod-1.0)
BuildRequires: pkgconfig(libmtp)
BuildRequires: pkgconfig(libnotify)
BuildRequires: pkgconfig(libudf)
%if 0%{?suse_version} || 0%{?fedora_version}
BuildRequires: pkgconfig(libvlc)
%endif
%if 0%{?suse_version}
Requires: libQt5Sql5-sqlite
%if "@QT_MAJOR_VERSION@" == "6"
Requires: qt6-sql-sqlite
%endif
%if "@QT_MAJOR_VERSION@" == "5"
Requires: libQt5Sql5-sqlite
%endif
%endif
%description
@@ -107,8 +124,11 @@ Features:
%setup -qn %{name}-@STRAWBERRY_VERSION_PACKAGE@
%build
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release
%if 0%{?centos} || 0%{?mageia}
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
%endif
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release -DQT_MAJOR_VERSION=@QT_MAJOR_VERSION@
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7)
%make_build
%else
%cmake_build

View File

@@ -1,182 +0,0 @@
!if "@ARCH@" == x86
!define arch_x86
!endif
!if "@ARCH@" == i686-w64-mingw32.shared
!define arch_x86
!endif
!if "@ARCH@" == x86_64
!define arch_x64
!endif
!if "@ARCH@" == x86_64-w64-mingw32.shared
!define arch_x64
!endif
!if "@CMAKE_BUILD_TYPE@" == Debug
!define debug
!endif
!if "@BUILD_WITH_QT6@" == "ON"
!define with_qt6
!endif
!ifdef debug
!define PRODUCT_NAME "Strawberry Music Player Debug WASAPI plugin"
!define PRODUCT_NAME_SHORT "StrawberryWASAPI"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME_SHORT}Debug"
!ifdef arch_x86
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Strawberry Music Player Debug"
!endif
!ifdef arch_x64
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES64\Strawberry Music Player Debug"
!endif
!else
!define PRODUCT_NAME "Strawberry Music Player WASAPI plugin"
!define PRODUCT_NAME_SHORT "StrawberryWASAPI"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME_SHORT}"
!ifdef arch_x86
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Strawberry Music Player"
!endif
!ifdef arch_x64
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES64\Strawberry Music Player"
!endif
!endif
!define PRODUCT_VERSION_MAJOR @STRAWBERRY_VERSION_MAJOR@
!define PRODUCT_VERSION_MINOR @STRAWBERRY_VERSION_MINOR@
!define PRODUCT_VERSION_PATCH @STRAWBERRY_VERSION_PATCH@
!define PRODUCT_DISPLAY_VERSION "@STRAWBERRY_VERSION_PACKAGE@"
!define PRODUCT_DISPLAY_VERSION_SHORT "@STRAWBERRY_VERSION_PACKAGE@"
!define PRODUCT_PUBLISHER "Jonas Kvinge"
!define PRODUCT_WEB_SITE "https://www.strawberrymusicplayer.org/"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
SetCompressor /SOLID lzma
!addplugindir nsisplugins
!include "MUI2.nsh"
!include "FileAssociation.nsh"
!include "Capabilities.nsh"
!include LogicLib.nsh
!include x64.nsh
!define MUI_ICON "strawberry.ico"
!define MUI_COMPONENTSPAGE_SMALLDESC
; Installer pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
; Uninstaller pages
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
!insertmacro MUI_LANGUAGE "English" ;first language is the default language
Name "${PRODUCT_NAME}"
!ifdef arch_x86
!ifdef debug
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x86.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x86.exe"
!endif
!else
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x86.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x86.exe"
!endif
!endif
!endif
!ifdef arch_x64
!ifdef debug
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x64.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x64.exe"
!endif
!else
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x64.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x64.exe"
!endif
!endif
!endif
InstallDir "${PRODUCT_INSTALL_DIR}"
; Get the path where Strawberry was installed previously and set it as default path
InstallDirRegKey ${PRODUCT_UNINST_ROOT_KEY} ${PRODUCT_UNINST_KEY} "UninstallString"
ShowInstDetails show
ShowUnInstDetails show
RequestExecutionLevel admin
; Check for previous installation, and call the uninstaller if any
Function CheckPreviousInstall
ReadRegStr $R0 ${PRODUCT_UNINST_ROOT_KEY} ${PRODUCT_UNINST_KEY} "UninstallString"
StrCmp $R0 "" done
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
"${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the \
previous version or `Cancel` to cancel this upgrade." \
IDOK uninst
Abort
; Run the uninstaller
uninst:
ClearErrors
ExecWait '$R0' ; Do not copy the uninstaller to a temp file
done:
FunctionEnd
Function .onInit
!insertmacro MUI_LANGDLL_DISPLAY
Call CheckPreviousInstall
FunctionEnd
Section "Gstreamer wasapi plugin" gstreamer-wasapi-plugin
SetOutPath "$INSTDIR\gstreamer-plugins"
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
SectionEnd
Section "Uninstaller"
; Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall-WASAPI.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\Uninstall-WASAPI.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\strawberry.ico"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_DISPLAY_VERSION}"
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "VersionMajor" "${PRODUCT_VERSION_MAJOR}"
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "VersionMinor" "${PRODUCT_VERSION_MINOR}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
SectionEnd
Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
Delete "$INSTDIR\Uninstall-WASAPI.exe"
; Remove the entry from 'installed programs list'
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
SectionEnd

View File

@@ -98,13 +98,13 @@ Name "${PRODUCT_NAME}"
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x86.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x86.exe"
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-Debug-x86.exe"
!endif
!else
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x86.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x86.exe"
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-x86.exe"
!endif
!endif
!endif
@@ -114,13 +114,13 @@ Name "${PRODUCT_NAME}"
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x64.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x64.exe"
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-Debug-x64.exe"
!endif
!else
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x64.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x64.exe"
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-x64.exe"
!endif
!endif
!endif
@@ -175,6 +175,7 @@ Section "Strawberry" Strawberry
File "strawberry-tagreader.exe"
File "strawberry.ico"
File "sqlite3.exe"
File "gst-launch-1.0.exe"
!ifdef arch_x86
File "libgcc_s_sjlj-1.dll"
@@ -238,7 +239,7 @@ Section "Strawberry" Strawberry
File "libpcre-1.dll"
File "libpcre2-16-0.dll"
File "libpng16-16.dll"
File "libprotobuf-24.dll"
File "libprotobuf-25.dll"
File "libpsl-5.dll"
File "libsoup-2.4-1.dll"
File "libspeex-1.dll"
@@ -266,7 +267,7 @@ Section "Strawberry" Strawberry
File "Qt6Network.dll"
File "Qt6Sql.dll"
File "Qt6Widgets.dll"
File "Qt6WinExtras.dll"
;File "Qt6WinExtras.dll"
File "libqtsparkle-qt6.dll"
!else
File "Qt5Concurrent.dll"
@@ -279,6 +280,16 @@ Section "Strawberry" Strawberry
File "libqtsparkle-qt5.dll"
!endif
!ifdef debug
File "gdb.exe"
File "libdl.dll"
File "libexpat-1.dll"
File "libmman.dll"
File "libmpfr-6.dll"
File "libreadline8.dll"
File "libtermcap.dll"
!endif
File "killproc.exe"
; Register Strawberry with Default Programs
@@ -361,7 +372,7 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgsttypefindfunctions.dll" "gstreamer-plugins\libgsttypefindfunctions.dll"
File "/oname=libgstgio.dll" "gstreamer-plugins\libgstgio.dll"
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
;File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
File "/oname=libgsticydemux.dll" "gstreamer-plugins\libgsticydemux.dll"
File "/oname=libgstid3demux.dll" "gstreamer-plugins\libgstid3demux.dll"
@@ -433,6 +444,7 @@ Section "Uninstall"
Delete "$INSTDIR\strawberry.exe"
Delete "$INSTDIR\strawberry-tagreader.exe"
Delete "$INSTDIR\sqlite3.exe"
Delete "$INSTDIR\gst-launch-1.0.exe"
!ifdef arch_x86
Delete "$INSTDIR\libgcc_s_sjlj-1.dll"
@@ -496,7 +508,7 @@ Section "Uninstall"
Delete "$INSTDIR\libpcre-1.dll"
Delete "$INSTDIR\libpcre2-16-0.dll"
Delete "$INSTDIR\libpng16-16.dll"
Delete "$INSTDIR\libprotobuf-24.dll"
Delete "$INSTDIR\libprotobuf-25.dll"
Delete "$INSTDIR\libpsl-5.dll"
Delete "$INSTDIR\libqtsparkle-qt5.dll"
Delete "$INSTDIR\libqtsparkle-qt6.dll"
@@ -533,6 +545,16 @@ Section "Uninstall"
Delete "$INSTDIR\swscale-5.dll"
Delete "$INSTDIR\zlib1.dll"
!ifdef debug
Delete "$INSTDIR\gdb.exe"
Delete "$INSTDIR\libdl.dll"
Delete "$INSTDIR\libexpat-1.dll"
Delete "$INSTDIR\libmman.dll"
Delete "$INSTDIR\libmpfr-6.dll"
Delete "$INSTDIR\libreadline8.dll"
Delete "$INSTDIR\libtermcap.dll"
!endif
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
Delete "$INSTDIR\platforms\qwindows.dll"
Delete "$INSTDIR\sqldrivers\qsqlite.dll"

View File

@@ -9,7 +9,6 @@ link_directories(
${GSTREAMER_BASE_LIBRARY_DIRS}
${GSTREAMER_AUDIO_LIBRARY_DIRS}
${FFTW3_LIBRARY_DIRS}
${QtCore_LIBRARY_DIRS}
)
add_library(gstmoodbar STATIC ${SOURCES})
@@ -21,7 +20,6 @@ target_include_directories(gstmoodbar SYSTEM PRIVATE
${GSTREAMER_BASE_INCLUDE_DIRS}
${GSTREAMER_AUDIO_INCLUDE_DIRS}
${FFTW3_INCLUDE_DIR}
${QtCore_INCLUDE_DIRS}
)
target_include_directories(gstmoodbar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -115,7 +115,7 @@ static void gst_fastspectrum_init (GstFastSpectrum * spectrum) {
spectrum->interval = DEFAULT_INTERVAL;
spectrum->bands = DEFAULT_BANDS;
spectrum->channel_data_initialised = false;
spectrum->channel_data_initialized = false;
g_mutex_init (&spectrum->lock);
@@ -137,14 +137,14 @@ static void gst_fastspectrum_alloc_channel_data (GstFastSpectrum * spectrum) {
QMutexLocker l(klass->fftw_lock);
spectrum->plan = fftw_plan_dft_r2c_1d(nfft, spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
}
spectrum->channel_data_initialised = true;
spectrum->channel_data_initialized = true;
}
static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
if (spectrum->channel_data_initialised) {
if (spectrum->channel_data_initialized) {
{
QMutexLocker l(klass->fftw_lock);
fftw_destroy_plan(spectrum->plan);
@@ -154,7 +154,7 @@ static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
delete[] spectrum->input_ring_buffer;
delete[] spectrum->spect_magnitude;
spectrum->channel_data_initialised = false;
spectrum->channel_data_initialized = false;
}
}
@@ -422,7 +422,7 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
/* If we don't have a FFT context yet (or it was reset due to parameter
* changes) get one and allocate memory for everything
*/
if (!spectrum->channel_data_initialised) {
if (!spectrum->channel_data_initialized) {
GST_DEBUG_OBJECT (spectrum, "allocating for bands %u", bands);
gst_fastspectrum_alloc_channel_data (spectrum);

View File

@@ -65,7 +65,7 @@ struct GstFastSpectrum {
GstClockTime message_ts; /* starttime for next message */
/* <private> */
bool channel_data_initialised;
bool channel_data_initialized;
double* input_ring_buffer;
double* fft_input;
fftw_complex* fft_output;

View File

@@ -28,16 +28,12 @@ endif()
link_directories(
${GLIB_LIBRARY_DIRS}
${QtCore_LIBRARY_DIRS}
${QtNetwork_LIBRARY_DIRS}
)
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
target_include_directories(libstrawberry-common SYSTEM PRIVATE
${GLIB_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(libstrawberry-common PRIVATE

View File

@@ -1,123 +0,0 @@
/* This file is part of Strawberry.
Copyright 2012, David Sansome <me@davidsansome.com>
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 CONCURRENTRUN_H
#define CONCURRENTRUN_H
#include <functional>
#include <QFuture>
#include <QRunnable>
#include <QThreadPool>
/*
The aim of ThreadFunctor classes and ConcurrentRun::Run() functions is to
complete QtConcurrentRun, which lack support for using a particular
QThreadPool, as it always uses QThreadPool::globalInstance().
This is problematic when we do not want to share the same thread pool over
all the application, but want to keep the convenient QtConcurrent::run()
functor syntax.
With ConcurrentRun::Run(), time critical changes can be performed in their
own pool, which is not empty by other actions (as it happens when using
QtConcurrentRun::run()).
ThreadFunctor classes are used to store a functor and its arguments, and
Run() functions are used for convenience: to directly create a new
ThreadFunctor object and start it.
*/
/*
Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and
non-void result):
*/
template<typename ReturnType>
class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable {
public:
ThreadFunctorBase() {}
QFuture<ReturnType> Start(QThreadPool* thread_pool) {
this->setRunnable(this);
this->reportStarted();
Q_ASSERT(thread_pool);
QFuture<ReturnType> future = this->future();
thread_pool->start(this, 0 /* priority: currently we do not support changing the priority. Might be added later if needed */);
return future;
}
void run() override = 0;
};
template <typename ReturnType, typename... Args>
class ThreadFunctor : public ThreadFunctorBase<ReturnType> {
public:
explicit ThreadFunctor(std::function<ReturnType (Args...)> function, Args... args)
: function_(std::bind(function, args...)) {
}
void run() override {
this->reportResult(function_());
this->reportFinished();
}
private:
std::function<ReturnType()> function_;
};
// Partial specialisation for void return type.
template <typename... Args>
class ThreadFunctor <void, Args...> : public ThreadFunctorBase<void> {
public:
explicit ThreadFunctor(std::function<void (Args...)> function, Args... args)
: function_(std::bind(function, args...)) {
}
void run() override {
function_();
this->reportFinished();
}
private:
std::function<void()> function_;
};
/*
Run functions
*/
namespace ConcurrentRun {
// Empty argument form.
template <typename ReturnType>
QFuture<ReturnType> Run(QThreadPool* threadpool, std::function<ReturnType ()> function) {
return (new ThreadFunctor<ReturnType>(function))->Start(threadpool);
}
// Function object with arguments form.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(QThreadPool* threadpool, std::function<ReturnType (Args...)> function, const Args&... args) {
return (new ThreadFunctor<ReturnType, Args...>(function, args...))->Start(threadpool);
}
// Support passing C function pointers instead of function objects.
template <typename ReturnType, typename... Args>
QFuture<ReturnType> Run(QThreadPool* threadpool, ReturnType (*function) (Args...), const Args&... args) {
return Run(threadpool, std::function<ReturnType (Args...)>(function), args...);
}
}
#endif // CONCURRENTRUN_H

View File

@@ -34,25 +34,25 @@ class Lazy {
Lazy() : init_([]() { return new T; }) {}
T* get() const {
CheckInitialised();
CheckInitialized();
return ptr_.get();
}
typename std::add_lvalue_reference<T>::type operator*() const {
CheckInitialised();
CheckInitialized();
return *ptr_;
}
T* operator->() const { return get(); }
// Returns true if the object is not yet initialised.
// Returns true if the object is not yet initialized.
explicit operator bool() const { return ptr_; }
// Deletes the underlying object and will re-run the initialisation function if the object is requested again.
// Deletes the underlying object and will re-run the initialization function if the object is requested again.
void reset() { ptr_.reset(); }
private:
void CheckInitialised() const {
void CheckInitialized() const {
if (!ptr_) {
ptr_.reset(init_(), [](T*obj) { obj->deleteLater(); });
}

View File

@@ -107,10 +107,10 @@ void _MessageHandlerBase::WriteMessage(const QByteArray &data) {
else if (flush_local_socket_) {
((qobject_cast<QLocalSocket*>(device_))->*(flush_local_socket_))();
}
}
void _MessageHandlerBase::DeviceClosed() {
is_device_closed_ = true;
AbortAll();
}

View File

@@ -43,7 +43,7 @@ class QIODevice;
class _MessageHandlerBase : public QObject {
Q_OBJECT
public:
public:
// device can be nullptr, in which case you must call SetDevice before writing any messages.
_MessageHandlerBase(QIODevice *device, QObject *parent);
@@ -52,16 +52,16 @@ public:
// After this is true, messages cannot be sent to the handler any more.
bool is_device_closed() const { return is_device_closed_; }
protected slots:
protected slots:
void WriteMessage(const QByteArray &data);
void DeviceReadyRead();
virtual void DeviceClosed();
protected:
protected:
virtual bool RawMessageArrived(const QByteArray &data) = 0;
virtual void AbortAll() = 0;
protected:
protected:
typedef bool (QAbstractSocket::*FlushAbstractSocket)();
typedef bool (QLocalSocket::*FlushLocalSocket)();
@@ -80,7 +80,7 @@ protected:
// You should subclass this and implement the MessageArrived(MessageType) method.
template <typename MT>
class AbstractMessageHandler : public _MessageHandlerBase {
public:
public:
AbstractMessageHandler(QIODevice *device, QObject *parent);
~AbstractMessageHandler() override { AbortAll(); }
@@ -102,7 +102,7 @@ public:
// Sets the "id" field of reply to the same as the request, and sends the reply on the socket. Used on the worker side.
void SendReply(const MessageType &request, MessageType *reply);
protected:
protected:
// Called when a message is received from the socket.
virtual void MessageArrived(const MessageType &message) { Q_UNUSED(message); }
@@ -110,8 +110,8 @@ protected:
bool RawMessageArrived(const QByteArray &data) override;
void AbortAll() override;
private:
QMap<int, ReplyType*> pending_replies_;
private:
QMap<qint64, ReplyType*> pending_replies_;
};
template <typename MT>
@@ -146,6 +146,7 @@ void AbstractMessageHandler<MT>::SendReply(const MessageType &request, MessageTy
template<typename MT>
bool AbstractMessageHandler<MT>::RawMessageArrived(const QByteArray &data) {
MessageType message;
if (!message.ParseFromArray(data.constData(), data.size())) {
return false;
@@ -161,14 +162,17 @@ bool AbstractMessageHandler<MT>::RawMessageArrived(const QByteArray &data) {
}
return true;
}
template<typename MT>
void AbstractMessageHandler<MT>::AbortAll() {
for (ReplyType *reply : pending_replies_) {
reply->Abort();
}
pending_replies_.clear();
}
#endif // MESSAGEHANDLER_H

View File

@@ -20,6 +20,7 @@
#include <QtGlobal>
#include <QObject>
#include <QThread>
#include <QSemaphore>
#include <QString>
@@ -33,7 +34,7 @@ class _MessageReplyBase : public QObject {
public:
explicit _MessageReplyBase(QObject *parent = nullptr);
virtual int id() const = 0;
virtual qint64 id() const = 0;
bool is_finished() const { return finished_; }
bool is_successful() const { return success_; }
@@ -57,29 +58,27 @@ class _MessageReplyBase : public QObject {
template <typename MessageType>
class MessageReply : public _MessageReplyBase {
public:
explicit MessageReply(const MessageType& request_message, QObject *parent = nullptr);
explicit MessageReply(const MessageType &request_message, QObject *parent = nullptr);
int id() const override { return request_message_.id(); }
const MessageType& request_message() const { return request_message_; }
const MessageType& message() const { return reply_message_; }
qint64 id() const override { return request_message_.id(); }
const MessageType &request_message() const { return request_message_; }
const MessageType &message() const { return reply_message_; }
void SetReply(const MessageType& message);
void SetReply(const MessageType &message);
private:
private:
MessageType request_message_;
MessageType reply_message_;
};
template<typename MessageType>
MessageReply<MessageType>::MessageReply(const MessageType& request_message, QObject *parent)
: _MessageReplyBase(parent)
{
MessageReply<MessageType>::MessageReply(const MessageType &request_message, QObject *parent) : _MessageReplyBase(parent) {
request_message_.MergeFrom(request_message);
}
template<typename MessageType>
void MessageReply<MessageType>::SetReply(const MessageType& message) {
void MessageReply<MessageType>::SetReply(const MessageType &message) {
Q_ASSERT(!finished_);
@@ -90,6 +89,9 @@ void MessageReply<MessageType>::SetReply(const MessageType& message) {
qLog(Debug) << "Releasing ID" << id() << "(finished)";
semaphore_.release();
// The signal is not always emitted without this.
QThread::usleep(10);
emit Finished(success_);
}

View File

@@ -32,7 +32,7 @@
#include <QQueue>
#include <QString>
#include <QStringList>
#include <QAtomicInt>
#include <QAtomicInteger>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#endif
@@ -48,11 +48,11 @@ class _WorkerPoolBase : public QObject {
public:
explicit _WorkerPoolBase(QObject *parent = nullptr);
signals:
signals:
// Emitted when a worker failed to start. This usually happens when the worker wasn't found, or couldn't be executed.
void WorkerFailedToStart();
protected slots:
protected slots:
virtual void DoStart() {}
virtual void NewConnection() {}
virtual void ProcessError(QProcess::ProcessError) {}
@@ -78,7 +78,7 @@ class WorkerPool : public _WorkerPoolBase {
void SetExecutableName(const QString &executable_name);
// Sets the number of worker process to use. Defaults to 1 <= (processors / 2) <= 2.
void SetWorkerCount(int count);
void SetWorkerCount(const int count);
// Sets the prefix to use for the local server (on unix this is a named pipe in /tmp).
// Defaults to QApplication::applicationName().
@@ -93,14 +93,14 @@ class WorkerPool : public _WorkerPoolBase {
// Can be called from any thread.
ReplyType *SendMessageWithReply(MessageType *message);
protected:
protected:
// These are all reimplemented slots, they are called on the WorkerPool's thread.
void DoStart() override;
void NewConnection() override;
void ProcessError(QProcess::ProcessError error) override;
void SendQueuedMessages() override;
private:
private:
struct Worker {
Worker() : local_server_(nullptr), local_socket_(nullptr), process_(nullptr), handler_(nullptr) {}
@@ -138,7 +138,7 @@ private:
// Returns the next handler, or nullptr if there isn't one. Must be called from my thread.
HandlerType *NextHandler() const;
private:
private:
QString local_server_name_;
QString executable_name_;
QString executable_path_;
@@ -147,7 +147,7 @@ private:
mutable int next_worker_;
QList<Worker> workers_;
QAtomicInt next_id_;
QAtomicInteger<qint64> next_id_;
QMutex message_queue_mutex_;
QQueue<ReplyType*> message_queue_;
@@ -170,6 +170,7 @@ WorkerPool<HandlerType>::WorkerPool(QObject *parent)
template <typename HandlerType>
WorkerPool<HandlerType>::~WorkerPool() {
for (const Worker &worker : workers_) {
if (worker.local_socket_ && worker.process_) {
disconnect(worker.process_, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(ProcessError(QProcess::ProcessError)));
@@ -193,10 +194,11 @@ WorkerPool<HandlerType>::~WorkerPool() {
for (ReplyType *reply : message_queue_) {
reply->Abort();
}
}
template <typename HandlerType>
void WorkerPool<HandlerType>::SetWorkerCount(int count) {
void WorkerPool<HandlerType>::SetWorkerCount(const int count) {
Q_ASSERT(workers_.isEmpty());
worker_count_ = count;
}
@@ -220,6 +222,7 @@ void WorkerPool<HandlerType>::Start() {
template <typename HandlerType>
void WorkerPool<HandlerType>::DoStart() {
Q_ASSERT(workers_.isEmpty());
Q_ASSERT(!executable_name_.isEmpty());
Q_ASSERT(QThread::currentThread() == thread());
@@ -248,10 +251,12 @@ void WorkerPool<HandlerType>::DoStart() {
workers_ << worker;
}
}
template <typename HandlerType>
void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
Q_ASSERT(QThread::currentThread() == thread());
DeleteQObjectPointerLater(&worker->local_server_);
@@ -284,6 +289,7 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
// Start the process
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
worker->process_->start(executable_path_, QStringList() << worker->local_server_->fullServerName());
}
template <typename HandlerType>
@@ -338,20 +344,24 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
StartOneWorker(worker);
break;
}
}
template <typename HandlerType>
typename WorkerPool<HandlerType>::ReplyType*
WorkerPool<HandlerType>::NewReply(MessageType *message) {
const int id = next_id_.fetchAndAddOrdered(1);
const qint64 id = next_id_.fetchAndAddOrdered(1);
message->set_id(id);
return new ReplyType(*message);
}
template <typename HandlerType>
typename WorkerPool<HandlerType>::ReplyType*
WorkerPool<HandlerType>::SendMessageWithReply(MessageType *message) {
ReplyType *reply = NewReply(message);
// Add the pending reply to the queue
@@ -364,10 +374,12 @@ WorkerPool<HandlerType>::SendMessageWithReply(MessageType *message) {
metaObject()->invokeMethod(this, "SendQueuedMessages", Qt::QueuedConnection);
return reply;
}
template <typename HandlerType>
void WorkerPool<HandlerType>::SendQueuedMessages() {
QMutexLocker l(&message_queue_mutex_);
while (!message_queue_.isEmpty()) {
@@ -384,10 +396,12 @@ void WorkerPool<HandlerType>::SendQueuedMessages() {
handler->SendRequest(reply);
}
}
template <typename HandlerType>
HandlerType *WorkerPool<HandlerType>::NextHandler() const {
for (int i = 0; i < workers_.count(); ++i) {
const int worker_index = (next_worker_ + i) % workers_.count();
@@ -398,6 +412,7 @@ HandlerType *WorkerPool<HandlerType>::NextHandler() const {
}
return nullptr;
}
#endif // WORKERPOOL_H

View File

@@ -9,7 +9,6 @@ link_directories(
${GLIB_LIBRARY_DIRS}
${PROTOBUF_LIBRARY_DIRS}
${TAGLIB_LIBRARY_DIRS}
${Qt5Core_LIBRARY_DIRS}
)
add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
@@ -17,8 +16,6 @@ add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
${GLIB_INCLUDE_DIRS}
${PROTOBUF_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(libstrawberry-tagreader PRIVATE
@@ -38,7 +35,3 @@ target_link_libraries(libstrawberry-tagreader PRIVATE
${QtNetwork_LIBRARIES}
libstrawberry-common
)
if(BUILD_WITH_QT6)
target_link_libraries(libstrawberry-tagreader PRIVATE Qt6::Core5Compat)
endif()

View File

@@ -84,7 +84,6 @@
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QTextCodec>
#include <QVector>
#include <QtDebug>
@@ -138,6 +137,15 @@ TagReader::~TagReader() {
delete factory_;
}
bool TagReader::IsMediaFile(const QString &filename) const {
qLog(Debug) << "Checking for valid file" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
return !fileref->isNull() && fileref->tag();
}
pb::tagreader::SongMetadata_FileType TagReader::GuessFileType(TagLib::FileRef *fileref) const {
if (dynamic_cast<TagLib::RIFF::WAV::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_WAV;
@@ -198,10 +206,10 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
TagLib::Tag *tag = fileref->tag();
if (tag) {
Decode(tag->title(), nullptr, song->mutable_title());
Decode(tag->artist(), nullptr, song->mutable_artist()); // TPE1
Decode(tag->album(), nullptr, song->mutable_album());
Decode(tag->genre(), nullptr, song->mutable_genre());
Decode(tag->title(), song->mutable_title());
Decode(tag->artist(), song->mutable_artist()); // TPE1
Decode(tag->album(), song->mutable_album());
Decode(tag->genre(), song->mutable_genre());
song->set_year(tag->year());
song->set_track(tag->track());
song->set_valid(true);
@@ -214,7 +222,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
// apart, so we keep specific behavior for some formats by adding another "else if" block below.
if (TagLib::Ogg::XiphComment *tag_ogg = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
ParseOggTag(tag_ogg->fieldListMap(), nullptr, &disc, &compilation, song);
ParseOggTag(tag_ogg->fieldListMap(), &disc, &compilation, song);
if (!tag_ogg->pictureList().isEmpty()) {
song->set_art_automatic(kEmbeddedCover);
}
@@ -225,28 +233,28 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
song->set_bitdepth(file_flac->audioProperties()->bitsPerSample());
if (file_flac->xiphComment()) {
ParseOggTag(file_flac->xiphComment()->fieldListMap(), nullptr, &disc, &compilation, song);
ParseOggTag(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
if (!file_flac->pictureList().isEmpty()) {
song->set_art_automatic(kEmbeddedCover);
}
}
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
if (tag) Decode(tag->comment(), song->mutable_comment());
}
else if (TagLib::WavPack::File *file_wavpack = dynamic_cast<TagLib::WavPack::File *>(fileref->file())) {
song->set_bitdepth(file_wavpack->audioProperties()->bitsPerSample());
if (file_wavpack->tag()) {
ParseAPETag(file_wavpack->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
if (file_wavpack->APETag()) {
ParseAPETag(file_wavpack->APETag()->itemListMap(), &disc, &compilation, song);
}
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
if (tag) Decode(tag->comment(), song->mutable_comment());
}
else if (TagLib::APE::File *file_ape = dynamic_cast<TagLib::APE::File*>(fileref->file())) {
if (file_ape->tag()) {
ParseAPETag(file_ape->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
if (file_ape->APETag()) {
ParseAPETag(file_ape->APETag()->itemListMap(), &disc, &compilation, song);
}
song->set_bitdepth(file_ape->audioProperties()->bitsPerSample());
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
if (tag) Decode(tag->comment(), song->mutable_comment());
}
else if (TagLib::MPEG::File *file_mpeg = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
@@ -255,22 +263,22 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
const TagLib::ID3v2::FrameListMap &map = file_mpeg->ID3v2Tag()->frameListMap();
if (!map["TPOS"].isEmpty()) disc = TStringToQString(map["TPOS"].front()->toString()).trimmed();
if (!map["TCOM"].isEmpty()) Decode(map["TCOM"].front()->toString(), nullptr, song->mutable_composer());
if (!map["TCOM"].isEmpty()) Decode(map["TCOM"].front()->toString(), song->mutable_composer());
// content group
if (!map["TIT1"].isEmpty()) Decode(map["TIT1"].front()->toString(), nullptr, song->mutable_grouping());
if (!map["TIT1"].isEmpty()) Decode(map["TIT1"].front()->toString(), song->mutable_grouping());
// ID3v2: lead performer/soloist
if (!map["TPE1"].isEmpty()) Decode(map["TPE1"].front()->toString(), nullptr, song->mutable_performer());
if (!map["TPE1"].isEmpty()) Decode(map["TPE1"].front()->toString(), song->mutable_performer());
// original artist/performer
if (!map["TOPE"].isEmpty()) Decode(map["TOPE"].front()->toString(), nullptr, song->mutable_performer());
if (!map["TOPE"].isEmpty()) Decode(map["TOPE"].front()->toString(), song->mutable_performer());
// Skip TPE1 (which is the artist) here because we already fetched it
// non-standard: Apple, Microsoft
if (!map["TPE2"].isEmpty()) Decode(map["TPE2"].front()->toString(), nullptr, song->mutable_albumartist());
if (!map["TPE2"].isEmpty()) Decode(map["TPE2"].front()->toString(), song->mutable_albumartist());
if (!map["TCMP"].isEmpty()) compilation = TStringToQString(map["TCMP"].front()->toString()).trimmed();
@@ -280,20 +288,20 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
}
if (!map["USLT"].isEmpty()) {
Decode(map["USLT"].front()->toString(), nullptr, song->mutable_lyrics());
Decode(map["USLT"].front()->toString(), song->mutable_lyrics());
}
else if (!map["SYLT"].isEmpty()) {
Decode(map["SYLT"].front()->toString(), nullptr, song->mutable_lyrics());
Decode(map["SYLT"].front()->toString(), song->mutable_lyrics());
}
if (!map["APIC"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
// Find a suitable comment tag. For now we ignore iTunNORM comments.
for (uint i = 0; i < map["COMM"].size(); ++i) {
for (uint i = 0 ; i < map["COMM"].size() ; ++i) {
const TagLib::ID3v2::CommentsFrame *frame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map["COMM"][i]);
if (frame && TStringToQString(frame->description()) != "iTunNORM") {
Decode(frame->text(), nullptr, song->mutable_comment());
Decode(frame->text(), song->mutable_comment());
break;
}
}
@@ -312,7 +320,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
if (mp4_tag->item("aART").isValid()) {
TagLib::StringList album_artists = mp4_tag->item("aART").toStringList();
if (!album_artists.isEmpty()) {
Decode(album_artists.front(), nullptr, song->mutable_albumartist());
Decode(album_artists.front(), song->mutable_albumartist());
}
}
@@ -326,20 +334,20 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
}
if (mp4_tag->item("\251wrt").isValid()) {
Decode(mp4_tag->item("\251wrt").toStringList().toString(", "), nullptr, song->mutable_composer());
Decode(mp4_tag->item("\251wrt").toStringList().toString(", "), song->mutable_composer());
}
if (mp4_tag->item("\251grp").isValid()) {
Decode(mp4_tag->item("\251grp").toStringList().toString(" "), nullptr, song->mutable_grouping());
Decode(mp4_tag->item("\251grp").toStringList().toString(" "), song->mutable_grouping());
}
if (mp4_tag->item("\251lyr").isValid()) {
Decode(mp4_tag->item("\251lyr").toStringList().toString(" "), nullptr, song->mutable_lyrics());
Decode(mp4_tag->item("\251lyr").toStringList().toString(" "), song->mutable_lyrics());
}
if (mp4_tag->item(kMP4_OriginalYear_ID).isValid()) {
song->set_originalyear(TStringToQString(mp4_tag->item(kMP4_OriginalYear_ID).toStringList().toString('\n')).left(4).toInt());
}
Decode(mp4_tag->comment(), nullptr, song->mutable_comment());
Decode(mp4_tag->comment(), song->mutable_comment());
}
}
@@ -348,7 +356,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
song->set_bitdepth(file_asf->audioProperties()->bitsPerSample());
if (file_asf->tag()) {
Decode(file_asf->tag()->comment(), nullptr, song->mutable_comment());
Decode(file_asf->tag()->comment(), song->mutable_comment());
}
const TagLib::ASF::AttributeListMap &attributes_map = file_asf->tag()->attributeListMap();
@@ -367,15 +375,15 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
}
}
else if (TagLib::MPC::File* file_mpc = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
if (file_mpc->tag()) {
ParseAPETag(file_mpc->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
else if (TagLib::MPC::File *file_mpc = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
if (file_mpc->APETag()) {
ParseAPETag(file_mpc->APETag()->itemListMap(), &disc, &compilation, song);
}
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
if (tag) Decode(tag->comment(), song->mutable_comment());
}
else if (tag) {
Decode(tag->comment(), nullptr, song->mutable_comment());
Decode(tag->comment(), song->mutable_comment());
}
if (!disc.isEmpty()) {
@@ -414,42 +422,27 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
}
void TagReader::Decode(const TagLib::String &tag, const QTextCodec *codec, std::string *output) {
QString tmp;
if (codec && tag.isLatin1()) { // Never override UTF-8.
const std::string fixed = QString::fromUtf8(tag.toCString(true)).toStdString();
tmp = codec->toUnicode(fixed.c_str()).trimmed();
}
else {
tmp = TStringToQString(tag).trimmed();
}
void TagReader::Decode(const TagLib::String &tag, std::string *output) {
QString tmp = TStringToQString(tag).trimmed();
output->assign(DataCommaSizeFromQString(tmp));
}
void TagReader::Decode(const QString &tag, const QTextCodec *codec, std::string *output) {
void TagReader::Decode(const QString &tag, std::string *output) {
if (!codec) {
output->assign(DataCommaSizeFromQString(tag));
}
else {
const QString decoded(codec->toUnicode(tag.toUtf8()));
output->assign(DataCommaSizeFromQString(decoded));
}
output->assign(DataCommaSizeFromQString(tag));
}
void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
if (!map["COMPOSER"].isEmpty()) Decode(map["COMPOSER"].front(), codec, song->mutable_composer());
if (!map["PERFORMER"].isEmpty()) Decode(map["PERFORMER"].front(), codec, song->mutable_performer());
if (!map["CONTENT GROUP"].isEmpty()) Decode(map["CONTENT GROUP"].front(), codec, song->mutable_grouping());
if (!map["COMPOSER"].isEmpty()) Decode(map["COMPOSER"].front(), song->mutable_composer());
if (!map["PERFORMER"].isEmpty()) Decode(map["PERFORMER"].front(), song->mutable_performer());
if (!map["CONTENT GROUP"].isEmpty()) Decode(map["CONTENT GROUP"].front(), song->mutable_grouping());
if (!map["ALBUMARTIST"].isEmpty()) Decode(map["ALBUMARTIST"].front(), codec, song->mutable_albumartist());
else if (!map["ALBUM ARTIST"].isEmpty()) Decode(map["ALBUM ARTIST"].front(), codec, song->mutable_albumartist());
if (!map["ALBUMARTIST"].isEmpty()) Decode(map["ALBUMARTIST"].front(), song->mutable_albumartist());
else if (!map["ALBUM ARTIST"].isEmpty()) Decode(map["ALBUM ARTIST"].front(), song->mutable_albumartist());
if (!map["ORIGINALDATE"].isEmpty()) song->set_originalyear(TStringToQString(map["ORIGINALDATE"].front()).left(4).toInt());
else if (!map["ORIGINALYEAR"].isEmpty()) song->set_originalyear(TStringToQString(map["ORIGINALYEAR"].front()).toInt());
@@ -461,20 +454,18 @@ void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCod
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat());
if (!map["LYRICS"].isEmpty()) Decode(map["LYRICS"].front(), codec, song->mutable_lyrics());
else if (!map["UNSYNCEDLYRICS"].isEmpty()) Decode(map["UNSYNCEDLYRICS"].front(), codec, song->mutable_lyrics());
if (!map["LYRICS"].isEmpty()) Decode(map["LYRICS"].front(), song->mutable_lyrics());
else if (!map["UNSYNCEDLYRICS"].isEmpty()) Decode(map["UNSYNCEDLYRICS"].front(), song->mutable_lyrics());
}
void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
Q_UNUSED(codec);
void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
TagLib::APE::ItemListMap::ConstIterator it = map.find("ALBUM ARTIST");
if (it != map.end()) {
TagLib::StringList album_artists = it->second.values();
if (!album_artists.isEmpty()) {
Decode(album_artists.front(), nullptr, song->mutable_albumartist());
Decode(album_artists.front(), song->mutable_albumartist());
}
}
@@ -488,19 +479,19 @@ void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCode
}
if (map.contains("PERFORMER")) {
Decode(map["PERFORMER"].values().toString(", "), nullptr, song->mutable_performer());
Decode(map["PERFORMER"].values().toString(", "), song->mutable_performer());
}
if (map.contains("COMPOSER")) {
Decode(map["COMPOSER"].values().toString(", "), nullptr, song->mutable_composer());
Decode(map["COMPOSER"].values().toString(", "), song->mutable_composer());
}
if (map.contains("GROUPING")) {
Decode(map["GROUPING"].values().toString(" "), nullptr, song->mutable_grouping());
Decode(map["GROUPING"].values().toString(" "), song->mutable_grouping());
}
if (map.contains("LYRICS")) {
Decode(map["LYRICS"].toString(), nullptr, song->mutable_lyrics());
Decode(map["LYRICS"].toString(), song->mutable_lyrics());
}
if (map.contains("FMPS_PLAYCOUNT")) {
@@ -517,8 +508,8 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, con
vorbis_comments->addField("COMPOSER", StdStringToTaglibString(song.composer()), true);
vorbis_comments->addField("PERFORMER", StdStringToTaglibString(song.performer()), true);
vorbis_comments->addField("CONTENT GROUP", StdStringToTaglibString(song.grouping()), true);
vorbis_comments->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 -1 ? QString() : QString::number(song.disc())), true);
vorbis_comments->addField("COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"), true);
vorbis_comments->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 ? QString() : QString::number(song.disc())), true);
vorbis_comments->addField("COMPILATION", QStringToTaglibString(song.compilation() ? "1" : QString()), true);
// Try to be coherent, the two forms are used but the first one is preferred
@@ -538,13 +529,15 @@ bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetad
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));;
if (!fileref || fileref->isNull()) return false;
fileref->tag()->setTitle(StdStringToTaglibString(song.title()));
fileref->tag()->setArtist(StdStringToTaglibString(song.artist()));
fileref->tag()->setAlbum(StdStringToTaglibString(song.album()));
fileref->tag()->setGenre(StdStringToTaglibString(song.genre()));
fileref->tag()->setComment(StdStringToTaglibString(song.comment()));
fileref->tag()->setYear(song.year());
fileref->tag()->setTrack(song.track());
fileref->tag()->setTitle(song.title().empty() ? TagLib::String() : StdStringToTaglibString(song.title()));
fileref->tag()->setArtist(song.artist().empty() ? TagLib::String() : StdStringToTaglibString(song.artist()));
fileref->tag()->setAlbum(song.album().empty() ? TagLib::String() : StdStringToTaglibString(song.album()));
fileref->tag()->setGenre(song.genre().empty() ? TagLib::String() : StdStringToTaglibString(song.genre()));
fileref->tag()->setComment(song.comment().empty() ? TagLib::String() : StdStringToTaglibString(song.comment()));
fileref->tag()->setYear(song.year() <= 0 ? 0 : song.year());
fileref->tag()->setTrack(song.track() <= 0 ? 0 : song.track());
bool result = false;
if (TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment *tag = file->xiphComment();
@@ -572,14 +565,14 @@ bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetad
else if (TagLib::MPEG::File *file_mpeg = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
TagLib::ID3v2::Tag *tag = file_mpeg->ID3v2Tag(true);
if (!tag) return false;
SetTextFrame("TPOS", song.disc() <= 0 -1 ? QString() : QString::number(song.disc()), tag);
SetTextFrame("TCOM", song.composer(), tag);
SetTextFrame("TIT1", song.grouping(), tag);
SetTextFrame("TOPE", song.performer(), tag);
SetTextFrame("TPOS", song.disc() <= 0 ? QString() : QString::number(song.disc()), tag);
SetTextFrame("TCOM", song.composer().empty() ? std::string() : song.composer(), tag);
SetTextFrame("TIT1", song.grouping().empty() ? std::string() : song.grouping(), tag);
SetTextFrame("TOPE", song.performer().empty() ? std::string() : song.performer(), tag);
// Skip TPE1 (which is the artist) here because we already set it
SetTextFrame("TPE2", song.albumartist(), tag);
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);
SetUnsyncLyricsFrame(song.lyrics(), tag);
SetTextFrame("TPE2", song.albumartist().empty() ? std::string() : song.albumartist(), tag);
SetTextFrame("TCMP", song.compilation() ? QString::number(1) : QString(), tag);
SetUnsyncLyricsFrame(song.lyrics().empty() ? std::string() : song.lyrics(), tag);
}
else if (TagLib::MP4::File *file_mp4 = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
@@ -598,53 +591,26 @@ bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetad
SetVorbisComments(tag, song);
}
bool ret = fileref->save();
result = fileref->save();
#ifdef Q_OS_LINUX
if (ret) {
if (result) {
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
}
#endif // Q_OS_LINUX
return ret;
return result;
}
void TagReader::SaveAPETag(TagLib::APE::Tag *tag, const pb::tagreader::SongMetadata &song) const {
tag->setItem("album artist", TagLib::APE::Item("album artist", TagLib::StringList(song.albumartist().c_str())));
tag->setItem("disc", TagLib::APE::Item("disc", TagLib::String::number(song.disc() <= 0 - 1 ? 0 : song.disc())));
tag->addValue("disc", QStringToTaglibString(song.disc() <= 0 ? QString() : QString::number(song.disc())), true);
tag->setItem("composer", TagLib::APE::Item("composer", TagLib::StringList(song.composer().c_str())));
tag->setItem("grouping", TagLib::APE::Item("grouping", TagLib::StringList(song.grouping().c_str())));
tag->setItem("performer", TagLib::APE::Item("performer", TagLib::StringList(song.performer().c_str())));
tag->setItem("lyrics", TagLib::APE::Item("lyrics", TagLib::String(song.lyrics())));
tag->setItem("compilation", TagLib::APE::Item("compilation", TagLib::StringList(song.compilation() ? "1" : "0")));
}
void TagReader::SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const {
const QByteArray descr_utf8(description.toUtf8());
const QByteArray value_utf8(value.toUtf8());
qLog(Debug) << "Setting FMPSFrame:" << description << ", " << value;
SetUserTextFrame(std::string(descr_utf8.constData(), descr_utf8.length()), std::string(value_utf8.constData(), value_utf8.length()), tag);
}
void TagReader::SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const {
const TagLib::String t_description = StdStringToTaglibString(description);
// Remove the frame if it already exists
TagLib::ID3v2::UserTextIdentificationFrame *frame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, t_description);
if (frame) {
tag->removeFrame(frame);
}
// Create and add a new frame
frame = new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8);
frame->setDescription(t_description);
frame->setText(StdStringToTaglibString(value));
tag->addFrame(frame);
tag->addValue("compilation", QStringToTaglibString(song.compilation() ? QString::number(1) : QString()), true);
}
@@ -652,6 +618,7 @@ void TagReader::SetTextFrame(const char *id, const QString &value, TagLib::ID3v2
const QByteArray utf8(value.toUtf8());
SetTextFrame(id, std::string(utf8.constData(), utf8.length()), tag);
}
void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const {
@@ -665,6 +632,8 @@ void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::I
tag->removeFrame(tag->frameListMap()[id_vector].front());
}
if (value.empty()) return;
// If no frames stored create empty frame
if (frames_buffer.isEmpty()) {
TagLib::ID3v2::TextIdentificationFrame frame(id_vector, TagLib::String::UTF8);
@@ -672,9 +641,9 @@ void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::I
}
// Update and add the frames
for (int lyrics_index = 0; lyrics_index < frames_buffer.size(); lyrics_index++) {
TagLib::ID3v2::TextIdentificationFrame* frame = new TagLib::ID3v2::TextIdentificationFrame(frames_buffer.at(lyrics_index));
if (lyrics_index == 0) {
for (int i = 0 ; i < frames_buffer.size() ; ++i) {
TagLib::ID3v2::TextIdentificationFrame *frame = new TagLib::ID3v2::TextIdentificationFrame(frames_buffer.at(i));
if (i == 0) {
frame->setText(StdStringToTaglibString(value));
}
// add frame takes ownership and clears the memory
@@ -683,15 +652,6 @@ void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::I
}
bool TagReader::IsMediaFile(const QString &filename) const {
qLog(Debug) << "Checking for valid file" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
return !fileref->isNull() && fileref->tag();
}
QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const {
if (filename.isEmpty()) return QByteArray();
@@ -711,8 +671,7 @@ QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const {
if (flac_file && flac_file->xiphComment()) {
TagLib::List<TagLib::FLAC::Picture*> pics = flac_file->pictureList();
if (!pics.isEmpty()) {
// Use the first picture in the file - this could be made cleverer and
// pick the front cover if it's present.
// Use the first picture in the file - this could be made cleverer and pick the front cover if it's present.
std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin();
TagLib::FLAC::Picture *picture = *it;
@@ -817,7 +776,7 @@ QByteArray TagReader::LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) co
}
void TagReader::SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const {
void TagReader::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const {
TagLib::ByteVector id_vector("USLT");
QVector<TagLib::ByteVector> frames_buffer;
@@ -828,6 +787,8 @@ void TagReader::SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Ta
tag->removeFrame(tag->frameListMap()[id_vector].front());
}
if (value.empty()) return;
// If no frames stored create empty frame
if (frames_buffer.isEmpty()) {
TagLib::ID3v2::UnsynchronizedLyricsFrame frame(TagLib::String::UTF8);
@@ -836,9 +797,9 @@ void TagReader::SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Ta
}
// Update and add the frames
for (int lyrics_index = 0; lyrics_index < frames_buffer.size(); lyrics_index++) {
TagLib::ID3v2::UnsynchronizedLyricsFrame* frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(frames_buffer.at(lyrics_index));
if (lyrics_index == 0) {
for (int i = 0 ; i < frames_buffer.size() ; ++i) {
TagLib::ID3v2::UnsynchronizedLyricsFrame *frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(frames_buffer.at(i));
if (i == 0) {
frame->setText(StdStringToTaglibString(value));
}
// add frame takes ownership and clears the memory

View File

@@ -35,8 +35,6 @@
#include "tagreadermessages.pb.h"
class QTextCodec;
#ifndef USE_SYSTEM_TAGLIB
using namespace Strawberry_TagLib;
#endif
@@ -52,27 +50,24 @@ class TagReader {
explicit TagReader();
~TagReader();
bool IsMediaFile(const QString &filename) const;
pb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
void ReadFile(const QString &filename, pb::tagreader::SongMetadata *song) const;
bool SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const;
bool IsMediaFile(const QString &filename) const;
QByteArray LoadEmbeddedArt(const QString &filename) const;
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
static void Decode(const TagLib::String& tag, const QTextCodec *codec, std::string *output);
static void Decode(const QString &tag, const QTextCodec *codec, std::string *output);
static void Decode(const TagLib::String &tag, std::string *output);
static void Decode(const QString &tag, std::string *output);
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
void ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const;
void SaveAPETag(TagLib::APE::Tag *tag, const pb::tagreader::SongMetadata &song) 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 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 SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const;

View File

@@ -101,7 +101,7 @@ message LoadEmbeddedArtResponse {
}
message Message {
optional int32 id = 1;
optional int64 id = 1;
optional ReadFileRequest read_file_request = 2;
optional ReadFileResponse read_file_response = 3;

View File

@@ -15,8 +15,6 @@ endif()
link_directories(
${GLIB_LIBRARY_DIRS}
${TAGLIB_LIBRARY_DIRS}
${QtCore_LIBRARY_DIRS}
${QtNetwork_LIBRARY_DIRS}
)
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
@@ -24,8 +22,6 @@ add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
${GLIB_INCLUDE_DIRS}
${PROTOBUF_INCLUDE_DIRS}
${QtCore_INCLUDE_DIRS}
${QtNetwork_INCLUDE_DIRS}
)
target_include_directories(strawberry-tagreader PRIVATE

View File

@@ -27,11 +27,9 @@
#include "tagreaderworker.h"
TagReaderWorker::TagReaderWorker(QIODevice *socket, QObject *parent)
: AbstractMessageHandler<pb::tagreader::Message>(socket, parent)
{
}
: AbstractMessageHandler<pb::tagreader::Message>(socket, parent) {}
void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
void TagReaderWorker::MessageArrived(const pb::tagreader::Message &message) {
pb::tagreader::Message reply;
@@ -51,6 +49,7 @@ void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
}
SendReply(message, &reply);
}

View File

@@ -29,14 +29,14 @@
class QIODevice;
class TagReaderWorker : public AbstractMessageHandler<pb::tagreader::Message> {
public:
public:
explicit TagReaderWorker(QIODevice *socket, QObject *parent = nullptr);
protected:
protected:
void MessageArrived(const pb::tagreader::Message &message) override;
void DeviceClosed() override;
private:
private:
TagReader tag_reader_;
};

View File

@@ -1,10 +1,9 @@
name: strawberry
version: '0.8.1+git'
adopt-info: strawberry
summary: music player and collection organizer
description: |
Strawberry is a music player and collection organizer.
It is a fork of Clementine released in 2018 aimed at music collectors,
audio enthusiasts and audiophiles
It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles
grade: stable
confinement: strict
@@ -43,7 +42,7 @@ parts:
alsa-lib:
plugin: autotools
source: https://www.alsa-project.org/files/pub/lib/alsa-lib-1.2.1.2.tar.bz2
source: https://www.alsa-project.org/files/pub/lib/alsa-lib-1.2.3.tar.bz2
configflags:
- --prefix=/usr
- --sysconfdir=/etc
@@ -71,6 +70,9 @@ parts:
after:
- alsa-lib
- desktop-qt5
override-pull: |
snapcraftctl pull
snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1-2)"
override-build: |
cmake ../src -DCMAKE_INSTALL_PREFIX=/usr
make -j $(getconf _NPROCESSORS_ONLN)
@@ -84,6 +86,7 @@ parts:
- gcc
- g++
- protobuf-compiler
- gettext
- libglib2.0-dev
- libgnutls28-dev
- libdbus-1-dev
@@ -96,6 +99,7 @@ parts:
- qtbase5-dev
- qtbase5-dev-tools
- qtbase5-private-dev
- qttools5-dev
- libqt5x11extras5-dev
- libgstreamer1.0-dev
- libgstreamer-plugins-base1.0-dev
@@ -154,6 +158,10 @@ parts:
- gstreamer1.0-plugins-bad
- gstreamer1.0-plugins-ugly
- gstreamer1.0-libav
- qt5-gtk-platformtheme
- plasma-integration
- kde-style-breeze
- qtwayland5
apps:
strawberry:
@@ -174,7 +182,7 @@ apps:
- x11
- wayland
- alsa
- pulseaudio
- audio-playback
- removable-media
- optical-drive
- raw-usb

View File

@@ -19,6 +19,7 @@ set(SOURCES
core/multisortfilterproxy.cpp
core/musicstorage.cpp
core/network.cpp
core/threadsafenetworkdiskcache.cpp
core/networktimeouts.cpp
core/networkproxyfactory.cpp
core/qtfslistener.cpp
@@ -184,6 +185,7 @@ set(SOURCES
dialogs/userpassdialog.cpp
dialogs/deleteconfirmationdialog.cpp
dialogs/lastfmimportdialog.cpp
dialogs/snapdialog.cpp
widgets/autoexpandingtreeview.cpp
widgets/busyindicator.cpp
@@ -256,6 +258,7 @@ set(HEADERS
core/filesystemwatcherinterface.h
core/mergedproxymodel.h
core/network.h
core/threadsafenetworkdiskcache.h
core/networktimeouts.h
core/qtfslistener.h
core/songloader.h
@@ -399,6 +402,7 @@ set(HEADERS
dialogs/userpassdialog.h
dialogs/deleteconfirmationdialog.h
dialogs/lastfmimportdialog.h
dialogs/snapdialog.h
widgets/autoexpandingtreeview.h
widgets/busyindicator.h
@@ -513,6 +517,7 @@ set(UI
dialogs/addstreamdialog.ui
dialogs/userpassdialog.ui
dialogs/lastfmimportdialog.ui
dialogs/snapdialog.ui
widgets/trackslider.ui
widgets/fileview.ui
@@ -545,19 +550,19 @@ if(HAVE_GLOBALSHORTCUTS)
HEADERS globalshortcuts/globalshortcuts.h globalshortcuts/globalshortcutbackend.h globalshortcuts/globalshortcutgrabber.h settings/shortcutssettingspage.h
UI globalshortcuts/globalshortcutgrabber.ui settings/shortcutssettingspage.ui
)
if (X11_FOUND OR WIN32)
if(HAVE_X11EXTRAS OR WIN32)
set(X11_OR_WIN ON)
endif()
optional_source(X11_OR_WIN
SOURCES globalshortcuts/globalshortcutbackend-system.cpp globalshortcuts/globalshortcut.cpp
HEADERS globalshortcuts/globalshortcutbackend-system.h globalshortcuts/globalshortcut.h
)
optional_source(X11_FOUND
optional_source(HAVE_X11EXTRAS
SOURCES globalshortcuts/globalshortcut-x11.cpp
)
optional_source(HAVE_DBUS
SOURCES globalshortcuts/globalshortcutbackend-gsd.cpp
HEADERS globalshortcuts/globalshortcutbackend-gsd.h
SOURCES globalshortcuts/globalshortcutbackend-gsd.cpp globalshortcuts/globalshortcutbackend-kde.cpp
HEADERS globalshortcuts/globalshortcutbackend-gsd.h globalshortcuts/globalshortcutbackend-kde.h
)
optional_source(WIN32
SOURCES globalshortcuts/globalshortcut-win.cpp
@@ -631,6 +636,14 @@ if(UNIX AND HAVE_DBUS)
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
dbus/gnomesettingsdaemon)
# org.kde.KGlobalAccel interface
qt6_add_dbus_interface(SOURCES
dbus/org.kde.KGlobalAccel.xml
dbus/kglobalaccel)
qt6_add_dbus_interface(SOURCES
dbus/org.kde.KGlobalAccel.Component.xml
dbus/kglobalaccelcomponent)
else()
# MPRIS 2.0 DBUS interfaces
@@ -659,6 +672,14 @@ if(UNIX AND HAVE_DBUS)
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
dbus/gnomesettingsdaemon)
# org.kde.KGlobalAccel interface
qt5_add_dbus_interface(SOURCES
dbus/org.kde.KGlobalAccel.xml
dbus/kglobalaccel)
qt5_add_dbus_interface(SOURCES
dbus/org.kde.KGlobalAccel.Component.xml
dbus/kglobalaccelcomponent)
endif()
# org.freedesktop.Avahi.Server interface
@@ -1030,7 +1051,6 @@ link_directories(
${GNUTLS_LIBRARY_DIRS}
${SQLITE_LIBRARY_DIRS}
${TAGLIB_LIBRARY_DIRS}
${QT_LIBRARY_DIRS}
${SINGLEAPPLICATION_LIBRARY_DIRS}
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
${QTSPARKLE_LIBRARY_DIRS}
@@ -1105,7 +1125,6 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
${GOBJECT_INCLUDE_DIRS}
${GNUTLS_INCLUDE_DIRS}
${SQLITE_INCLUDE_DIRS}
${QT_INCLUDE_DIRS}
)
target_include_directories(strawberry_lib PUBLIC

View File

@@ -40,7 +40,7 @@
// INSTRUCTIONS Base2D
// 1. do anything that depends on height() in init(), Base2D will call it before you are shown
// 2. otherwise you can use the constructor to initialise things
// 2. otherwise you can use the constructor to initialize things
// 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it
// 4. if you want to manipulate the scope, reimplement transform()
// 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included

View File

@@ -136,7 +136,7 @@ void AnalyzerContainer::ChangeAnalyzer(int id) {
QObject *instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
if (!instance) {
qLog(Warning) << "Couldn't initialise a new" << analyzer_types_[id]->className();
qLog(Warning) << "Couldn't initialize a new" << analyzer_types_[id]->className();
return;
}

View File

@@ -92,7 +92,7 @@ class RainbowAnalyzer : public Analyzer::Base {
}
private:
// "constants" that get initialised in the constructor
// "constants" that get initialized in the constructor
float band_scale_[kRainbowBands];
QPen colors_[kRainbowBands];

View File

@@ -110,7 +110,7 @@ void SCollection::Init() {
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
connect(app_->lastfm_import(), SIGNAL(UpdateLastPlayed(QString, QString, QString, int)), backend_, SLOT(UpdateLastPlayed(QString, QString, QString, int)));
connect(app_->lastfm_import(), SIGNAL(UpdateLastPlayed(QString, QString, QString, qint64)), backend_, SLOT(UpdateLastPlayed(QString, QString, QString, qint64)));
connect(app_->lastfm_import(), SIGNAL(UpdatePlayCount(QString, QString, int)), backend_, SLOT(UpdatePlayCount(QString, QString, int)));
// This will start the watcher checking for updates

View File

@@ -853,7 +853,11 @@ Song CollectionBackend::GetSongBySongId(const QString &song_id, QSqlDatabase &db
SongList CollectionBackend::GetSongsBySongId(const QStringList &song_ids, QSqlDatabase &db) {
QString in = song_ids.join(",");
QStringList song_ids2;
for (const QString &song_id : song_ids) {
song_ids2 << "'" + song_id + "'";
}
QString in = song_ids2.join(",");
QSqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE SONG_ID IN (%2)").arg(songs_table_, in));
@@ -1364,7 +1368,7 @@ SongList CollectionBackend::GetSongsBy(const QString &artist, const QString &alb
}
void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed) {
void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed) {
SongList songs = GetSongsBy(artist, album, title);
if (songs.isEmpty()) {

View File

@@ -213,7 +213,7 @@ class CollectionBackend : public CollectionBackendInterface {
void SongPathChanged(const Song &song, const QFileInfo &new_file);
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed);
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
void UpdateSongRating(const int id, const float rating);

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2019, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2020, 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
@@ -37,6 +37,7 @@
#include <QList>
#include <QSet>
#include <QMap>
#include <QMetaType>
#include <QVariant>
#include <QByteArray>
#include <QString>
@@ -398,13 +399,14 @@ QString CollectionModel::DividerKey(const GroupBy type, CollectionItem *item) co
case GroupBy_Genre:
case GroupBy_Format:
case GroupBy_FileType: {
QChar c = item->sort_text[0].toUpper();
QChar c = item->sort_text[0];
if (c.isDigit()) return "0";
if (c == ' ') return QString();
if (c.decompositionTag() != QChar::NoDecomposition)
return QChar(c.decomposition()[0]);
return c;
if (c.decompositionTag() != QChar::NoDecomposition) {
return QChar(c.decomposition()[0]);
}
return c;
}
case GroupBy_Year:
case GroupBy_OriginalYear:
@@ -816,7 +818,7 @@ CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) {
int child_level = parent == root_ ? 0 : parent->container_level + 1;
GroupBy child_type = child_level >= 3 ? GroupBy_None : group_by_[child_level];
// Initialise the query. child_type says what type of thing we want (artists, songs, etc.)
// Initialize the query. child_type says what type of thing we want (artists, songs, etc.)
CollectionQuery q(query_options_);
InitQuery(child_type, &q);
@@ -891,7 +893,11 @@ void CollectionModel::LazyPopulate(CollectionItem *parent, const bool signal) {
}
void CollectionModel::ResetAsync() {
QFuture<CollectionModel::QueryResult> future = QtConcurrent::run(std::bind(&CollectionModel::RunQuery, this, root_));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFuture<CollectionModel::QueryResult> future = QtConcurrent::run(&CollectionModel::RunQuery, this, root_);
#else
QFuture<CollectionModel::QueryResult> future = QtConcurrent::run(this, &CollectionModel::RunQuery, root_);
#endif
NewClosure(future, this, SLOT(ResetAsyncQueryFinished(QFuture<CollectionModel::QueryResult>)), future);
}
@@ -1132,7 +1138,7 @@ CollectionItem *CollectionModel::InitItem(const GroupBy type, const bool signal,
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
// Initialise the item depending on what type it's meant to be
// Initialize the item depending on what type it's meant to be
CollectionItem *item = new CollectionItem(item_type, parent);
item->compilation_artist_node_ = nullptr;
item->container_level = container_level;
@@ -1565,8 +1571,9 @@ void CollectionModel::FinishItem(const GroupBy type, const bool signal, const bo
// Create the divider entry if we're supposed to
if (create_divider && show_dividers_) {
QString divider_key = DividerKey(type, item);
if (item->sort_text.isEmpty() || item->sort_text[0].toUpper() != divider_key)
item->sort_text.prepend(divider_key);
if (!divider_key.isEmpty()) {
item->sort_text.prepend(divider_key + " ");
}
if (!divider_key.isEmpty() && !divider_nodes_.contains(divider_key)) {
if (signal)
@@ -1575,7 +1582,7 @@ void CollectionModel::FinishItem(const GroupBy type, const bool signal, const bo
CollectionItem *divider = new CollectionItem(CollectionItem::Type_Divider, root_);
divider->key = divider_key;
divider->display_text = DividerDisplayText(type, divider_key);
divider->sort_text = divider_key + "0000";
divider->sort_text = divider_key + " ";
divider->lazy_loaded = true;
divider_nodes_[divider_key] = divider;
@@ -1729,8 +1736,14 @@ bool CollectionModel::CompareItems(const CollectionItem *a, const CollectionItem
QVariant left(data(a, CollectionModel::Role_SortText));
QVariant right(data(b, CollectionModel::Role_SortText));
if (left.type() == QVariant::Int) return left.toInt() < right.toInt();
return left.toString() < right.toString();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (left.metaType().id() == QMetaType::Int)
#else
if (left.type() == QVariant::Int)
#endif
return left.toInt() < right.toInt();
else
return left.toString() < right.toString();
}

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2019, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2020, 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

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2020, 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

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2020, 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

View File

@@ -21,6 +21,7 @@
#include "config.h"
#include <QtGlobal>
#include <QMetaType>
#include <QDateTime>
#include <QVariant>
#include <QString>
@@ -133,9 +134,23 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
}
else {
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (value.metaType().id() == QMetaType::Int) {
#else
if (value.type() == QVariant::Int) {
#endif
where_clauses_ << QString("%1 %2 %3").arg(column, op, value.toString());
}
else if (
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
value.metaType().id() == QMetaType::QString
#else
value.type() == QVariant::String
#endif
&& value.toString().isNull()) {
where_clauses_ << QString("%1 %2 ?").arg(column, op);
bound_values_ << QString("");
}
else {
where_clauses_ << QString("%1 %2 ?").arg(column, op);
bound_values_ << value;

View File

@@ -76,7 +76,6 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
scan_on_startup_(true),
monitor_(true),
mark_songs_unavailable_(false),
live_scanning_(false),
stop_requested_(false),
rescan_in_progress_(false),
rescan_timer_(new QTimer(this)),
@@ -480,8 +479,6 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
t->AddToProgress(1);
if (live_scanning_) t->CommitNewOrUpdatedSongs();
// Recurse into the new subdirs that we found
t->AddToProgressMax(my_new_subdirs.count());
for (const Subdirectory &my_new_subdir : my_new_subdirs) {

View File

@@ -195,7 +195,6 @@ class CollectionWatcher : public QObject {
bool scan_on_startup_;
bool monitor_;
bool mark_songs_unavailable_;
bool live_scanning_;
bool stop_requested_;
bool rescan_in_progress_; // True if RescanTracksNow() has been called and is working.

View File

@@ -64,4 +64,8 @@
#cmakedefine INSTALL_TRANSLATIONS
#define TRANSLATIONS_DIR "${CMAKE_INSTALL_PREFIX}/share/strawberry/translations"
#cmakedefine HAVE_X11EXTRAS
#cmakedefine HAVE_WINEXTRAS
#cmakedefine HAVE_QPA_QPLATFORMNATIVEINTERFACE_H
#endif // CONFIG_H_IN

View File

@@ -28,6 +28,7 @@
#include <QtGlobal>
#include <QMutex>
#include <QMimeData>
#include <QMetaType>
#include <QVariant>
#include <QList>
#include <QSet>
@@ -444,8 +445,13 @@ bool ContextAlbumsModel::CompareItems(const CollectionItem *a, const CollectionI
QVariant left(data(a, ContextAlbumsModel::Role_SortText));
QVariant right(data(b, ContextAlbumsModel::Role_SortText));
if (left.type() == QVariant::Int) return left.toInt() < right.toInt();
return left.toString() < right.toString();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (left.metaType().id() == QMetaType::Int)
#else
if (left.type() == QVariant::Int)
#endif
return left.toInt() < right.toInt();
else return left.toString() < right.toString();
}

View File

@@ -397,7 +397,7 @@ void ContextAlbumsView::EditTracks() {
void ContextAlbumsView::CopyToDevice() {
#ifndef Q_OS_WIN
if (!organize_dialog_)
organize_dialog_.reset(new OrganizeDialog(app_->task_manager()));
organize_dialog_.reset(new OrganizeDialog(app_->task_manager(), nullptr, this));
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
organize_dialog_->SetCopy(true);

View File

@@ -151,6 +151,7 @@ ContextView::ContextView(QWidget *parent) :
label_top_->setWordWrap(true);
label_top_->setMinimumHeight(50);
label_top_->setContentsMargins(0, 0, 32, 0);
label_top_->setTextInteractionFlags(Qt::TextSelectableByMouse);
layout_scrollarea_->setObjectName("context-layout-scrollarea");
layout_scrollarea_->setContentsMargins(15, 15, 15, 15);
@@ -485,7 +486,7 @@ void ContextView::UpdateFonts() {
void ContextView::SetSong() {
label_top_->setStyleSheet(QString("font: %2pt \"%1\"; font-weight: regular;").arg(font_headline_).arg(font_size_headline_));
label_top_->setText(QString("<b>%1</b><br/>%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br/>"), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br/>")));
label_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
label_stop_summary_->clear();
@@ -643,7 +644,7 @@ void ContextView::SetSong() {
void ContextView::UpdateSong(const Song &song) {
label_top_->setText(QString("<b>%1</b><br/>%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br/>"), Utilities::ReplaceMessage(summary_fmt_, song, "<br/>")));
label_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true)));
if (action_show_data_->isChecked()) {
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());

View File

@@ -44,36 +44,37 @@ const char *CommandlineOptions::kHelpText =
"%1: strawberry [%2] [%3]\n"
"\n"
"%4:\n"
" -p, --play %5\n"
" -t, --play-pause %6\n"
" -u, --pause %7\n"
" -s, --stop %8\n"
" -q, --stop-after-current %9\n"
" -r, --previous %10\n"
" -f, --next %11\n"
" -v, --volume <value> %12\n"
" --volume-up %13\n"
" --volume-down %14\n"
" --volume-increase-by %15\n"
" --volume-decrease-by %16\n"
" --seek-to <seconds> %17\n"
" --seek-by <seconds> %18\n"
" --restart-or-previous %19\n"
" -p, --play %5\n"
" -t, --play-pause %6\n"
" -u, --pause %7\n"
" -s, --stop %8\n"
" -q, --stop-after-current %9\n"
" -r, --previous %10\n"
" -f, --next %11\n"
" -v, --volume <value> %12\n"
" --volume-up %13\n"
" --volume-down %14\n"
" --volume-increase-by %15\n"
" --volume-decrease-by %16\n"
" --seek-to <seconds> %17\n"
" --seek-by <seconds> %18\n"
" --restart-or-previous %19\n"
"\n"
"%20:\n"
" -c, --create <name> %21\n"
" -a, --append %22\n"
" -l, --load %23\n"
" -k, --play-track <n> %24\n"
" -c, --create <name> %21\n"
" -a, --append %22\n"
" -l, --load %23\n"
" -k, --play-track <n> %24\n"
" -i, --play-playlist <name> %25\n"
"\n"
"%25:\n"
" -o, --show-osd %26\n"
" -y, --toggle-pretty-osd %27\n"
" -g, --language <lang> %28\n"
" --quiet %29\n"
" --verbose %30\n"
" --log-levels <levels> %31\n"
" --version %32\n";
"%26:\n"
" -o, --show-osd %27\n"
" -y, --toggle-pretty-osd %28\n"
" -g, --language <lang> %29\n"
" --quiet %30\n"
" --verbose %31\n"
" --log-levels <levels> %32\n"
" --version %33\n";
const char *CommandlineOptions::kVersionText = "Strawberry %1";
@@ -138,6 +139,7 @@ bool CommandlineOptions::Parse() {
{"append", no_argument, nullptr, 'a'},
{"load", no_argument, nullptr, 'l'},
{"play-track", required_argument, nullptr, 'k'},
{"play-playlist", required_argument, nullptr, 'i'},
{"show-osd", no_argument, nullptr, 'o'},
{"toggle-pretty-osd", no_argument, nullptr, 'y'},
{"language", required_argument, nullptr, 'g'},
@@ -150,7 +152,7 @@ bool CommandlineOptions::Parse() {
// Parse the arguments
bool ok = false;
forever {
int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:oyg:", kOptions, nullptr);
int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:i:oyg:", kOptions, nullptr);
// End of the options
if (c == -1) break;
@@ -179,7 +181,8 @@ bool CommandlineOptions::Parse() {
tr("Create a new playlist with files"),
tr("Append files/URLs to the playlist"),
tr("Loads files/URLs, replacing current playlist"),
tr("Play the <n>th track in the playlist"))
tr("Play the <n>th track in the playlist"),
tr("Play given playlist"))
.arg(tr("Other options"), tr("Display the on-screen-display"),
tr("Toggle visibility for the pretty on-screen-display"),
tr("Change the language"),
@@ -213,6 +216,10 @@ bool CommandlineOptions::Parse() {
case 'f':
player_action_ = Player_Next;
break;
case 'i':
player_action_ = Player_PlayPlaylist;
playlist_name_ = QString(optarg);
break;
case 'c':
url_list_action_ = UrlList_CreateNew;
playlist_name_ = QString(optarg);
@@ -362,7 +369,8 @@ QDataStream& operator<<(QDataStream &s, const CommandlineOptions &a) {
<< a.show_osd_
<< a.urls_
<< a.log_levels_
<< a.toggle_pretty_osd_;
<< a.toggle_pretty_osd_
<< a.playlist_name_;
return s;
@@ -382,7 +390,8 @@ QDataStream& operator>>(QDataStream &s, CommandlineOptions &a) {
>> a.show_osd_
>> a.urls_
>> a.log_levels_
>> a.toggle_pretty_osd_;
>> a.toggle_pretty_osd_
>> a.playlist_name_;
a.player_action_ = CommandlineOptions::PlayerAction(player_action);
a.url_list_action_ = CommandlineOptions::UrlListAction(url_list_action);

View File

@@ -57,6 +57,7 @@ class CommandlineOptions {
Player_Next = 6,
Player_RestartOrPrevious = 7,
Player_StopAfterCurrent = 8,
Player_PlayPlaylist = 9,
};
bool Parse();

View File

@@ -132,6 +132,7 @@ QSqlDatabase Database::Connect() {
if (db.isOpen()) {
return db;
}
db.setConnectOptions("QSQLITE_BUSY_TIMEOUT=30000");
//qLog(Debug) << "Opened database with connection id" << connection_id;
if (!injected_database_name_.isNull())
@@ -202,7 +203,7 @@ QSqlDatabase Database::Connect() {
UpdateMainSchema(&db);
}
// We might have to initialise the schema in some attached databases now, if they were deleted and don't match up with the main schema version.
// We might have to initialize the schema in some attached databases now, if they were deleted and don't match up with the main schema version.
for (const QString &key : attached_databases_.keys()) {
if (attached_databases_[key].is_temporary_ && attached_databases_[key].schema_.isEmpty())
continue;
@@ -518,11 +519,13 @@ void Database::DoBackup() {
QSqlDatabase db(this->Connect());
if (!db.isOpen()) return;
// Before we overwrite anything, make sure the database is not corrupt
QMutexLocker l(&mutex_);
const bool ok = IntegrityCheck(db);
if (ok) {
const bool ok = IntegrityCheck(db);
if (ok && SchemaVersion(&db) == kSchemaVersion) {
BackupFile(db.databaseName());
}

View File

@@ -58,6 +58,7 @@ protected:
private:
QPixmap normal_icon_;
QPixmap grey_icon_;
std::unique_ptr<MacSystemTrayIconPrivate> p_;
Q_DISABLE_COPY(MacSystemTrayIcon);
};

View File

@@ -165,7 +165,8 @@ class MacSystemTrayIconPrivate {
MacSystemTrayIcon::MacSystemTrayIcon(QObject* parent)
: SystemTrayIcon(parent),
normal_icon_(QPixmap(":/pictures/strawberry.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)) {
normal_icon_(QPixmap(":/pictures/strawberry.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)),
grey_icon_(QPixmap(":/pictures/strawberry-grey.png").scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)) {
QApplication::setWindowIcon(normal_icon_);
}

View File

@@ -104,6 +104,7 @@
#include "dialogs/addstreamdialog.h"
#include "dialogs/deleteconfirmationdialog.h"
#include "dialogs/lastfmimportdialog.h"
#include "dialogs/snapdialog.h"
#include "organize/organizedialog.h"
#include "widgets/fancytabwidget.h"
#include "widgets/playingwidget.h"
@@ -320,7 +321,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
connect(app, SIGNAL(ErrorAdded(QString)), SLOT(ShowErrorDialog(QString)));
connect(app, SIGNAL(SettingsDialogRequested(SettingsDialog::Page)), SLOT(OpenSettingsDialogAtPage(SettingsDialog::Page)));
// Initialise the UI
// Initialize the UI
ui_->setupUi(this);
album_cover_choice_controller_->Init(app);
@@ -329,7 +330,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
context_view_->Init(app_, collection_view_->view(), album_cover_choice_controller_);
ui_->widget_playing->Init(app_, album_cover_choice_controller_);
// Initialise the search widget
// Initialize the search widget
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
// Add tabs to the fancy tab widget
@@ -362,8 +363,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
track_slider_timer_->setInterval(kTrackSliderUpdateTimeMs);
connect(track_slider_timer_, SIGNAL(timeout()), SLOT(UpdateTrackSliderPosition()));
// Start initialising the player
qLog(Debug) << "Initialising player";
// Start initializing the player
qLog(Debug) << "Initializing player";
app_->player()->SetAnalyzer(ui_->analyzer);
app_->player()->SetEqualizer(equalizer_.get());
app_->player()->Init();
@@ -986,6 +987,18 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
}
#endif
#ifdef Q_OS_LINUX
if (!Utilities::GetEnv("SNAP").isEmpty() && !Utilities::GetEnv("SNAP_NAME").isEmpty()) {
s.beginGroup(kSettingsGroup);
if (!s.value("ignore_snap", false).toBool()) {
SnapDialog *snap_dialog = new SnapDialog();
snap_dialog->setAttribute(Qt::WA_DeleteOnClose);
snap_dialog->show();
}
s.endGroup();
}
#endif
qLog(Debug) << "Started" << QThread::currentThread();
initialized_ = true;
@@ -1010,6 +1023,7 @@ void MainWindow::ReloadSettings() {
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
keep_running_ = s.value("keeprunning", false).toBool();
playing_widget_ = s.value("playing_widget", true).toBool();
bool trayicon_progress = s.value("trayicon_progress", false).toBool();
if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched();
doubleclick_addmode_ = BehaviourSettingsPage::AddBehaviour(s.value("doubleclick_addmode", BehaviourSettingsPage::AddBehaviour_Append).toInt());
doubleclick_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("doubleclick_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
@@ -1021,6 +1035,8 @@ void MainWindow::ReloadSettings() {
int iconsize = s.value(AppearanceSettingsPage::kIconSizePlayControlButtons, 32).toInt();
s.endGroup();
if (tray_icon_) tray_icon_->SetTrayiconProgress(trayicon_progress);
ui_->back_button->setIconSize(QSize(iconsize, iconsize));
ui_->pause_play_button->setIconSize(QSize(iconsize, iconsize));
ui_->stop_button->setIconSize(QSize(iconsize, iconsize));
@@ -1448,21 +1464,22 @@ void MainWindow::PlaylistDoubleClick(const QModelIndex &idx) {
if (!idx.isValid()) return;
int row = idx.row();
QModelIndex source_idx = idx;
if (idx.model() == app_->playlist_manager()->current()->proxy()) {
// The index was in the proxy model (might've been filtered), so we need to get the actual row in the source model.
row = app_->playlist_manager()->current()->proxy()->mapToSource(idx).row();
source_idx = app_->playlist_manager()->current()->proxy()->mapToSource(idx);
}
switch (doubleclick_playlist_addmode_) {
case BehaviourSettingsPage::PlaylistAddBehaviour_Play:
app_->playlist_manager()->SetActiveToCurrent();
app_->player()->PlayAt(row, Engine::Manual, Playlist::AutoScroll_Never, true, true);
app_->player()->PlayAt(source_idx.row(), Engine::Manual, Playlist::AutoScroll_Never, true, true);
break;
case BehaviourSettingsPage::PlaylistAddBehaviour_Enqueue:
app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << idx);
app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << source_idx);
if (app_->player()->GetState() != Engine::Playing) {
app_->playlist_manager()->SetActiveToCurrent();
app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), Engine::Manual, Playlist::AutoScroll_Never, true);
}
break;
@@ -1517,19 +1534,25 @@ void MainWindow::showEvent(QShowEvent *e) {
void MainWindow::closeEvent(QCloseEvent *e) {
#ifdef Q_OS_MACOS
Exit();
#else
if (!hidden_ && keep_running_ && e->spontaneous() && QSystemTrayIcon::isSystemTrayAvailable()) {
e->ignore();
SetHiddenInTray(true);
}
else {
Exit();
}
#endif
QMainWindow::closeEvent(e);
}
void MainWindow::SetHiddenInTray(const bool hidden) {
hidden_ = hidden;
settings_.setValue("hidden", hidden_);
// Some window managers don't remember maximized state between calls to hide() and show(), so we have to remember it ourself.
if (hidden) {
@@ -2252,6 +2275,14 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
case CommandlineOptions::Player_Next:
app_->player()->Next();
break;
case CommandlineOptions::Player_PlayPlaylist:
if (options.playlist_name().isEmpty()) {
qLog(Error) << "ERROR: playlist name missing";
}
else {
app_->player()->PlayPlaylist(options.playlist_name());
}
break;
case CommandlineOptions::Player_RestartOrPrevious:
app_->player()->RestartOrPrevious();
break;
@@ -2518,9 +2549,9 @@ void MainWindow::PlaylistCopyUrl() {
}
if (urls.count() > 0) {
QMimeData *mime_data = new QMimeData;
mime_data->setUrls(urls);
QApplication::clipboard()->setMimeData(mime_data);
QMimeData mime_data;
mime_data.setUrls(urls);
QApplication::clipboard()->setText(mime_data.text());
}
}
@@ -2730,25 +2761,21 @@ void MainWindow::Raise() {
}
#ifdef Q_OS_WIN
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) {
#else
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) {
#endif
Q_UNUSED(eventType);
Q_UNUSED(result);
#ifdef Q_OS_WIN
MSG *msg = static_cast<MSG*>(message);
thumbbar_->HandleWinEvent(msg);
#else
Q_UNUSED(message);
#endif
return false;
if (exit_count_ == 0 && message) {
MSG *msg = static_cast<MSG*>(message);
thumbbar_->HandleWinEvent(msg);
}
return QMainWindow::nativeEvent(eventType, message, result);
}
#endif // Q_OS_WIN
void MainWindow::AutoCompleteTags() {
@@ -2945,7 +2972,7 @@ void MainWindow::SetToggleScrobblingIcon(const bool value) {
if (value) {
if (app_->playlist_manager()->active() && app_->playlist_manager()->active()->scrobbled())
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22));
else
else
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22)); // TODO: Create a faint version of the icon
}
else {

View File

@@ -117,10 +117,12 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void showEvent(QShowEvent *e) override;
void closeEvent(QCloseEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
#ifdef Q_OS_WIN
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
#else
bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
#endif
#endif
// PlatformInterface

View File

@@ -69,6 +69,20 @@ int MultiSortFilterProxy::Compare(const QVariant &left, const QVariant &right) c
// Copied from the QSortFilterProxyModel::lessThan implementation, but returns -1, 0 or 1 instead of true or false.
switch (left.userType()) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
case QMetaType::UnknownType: return (right.metaType().id() != QMetaType::UnknownType) ? -1 : 0;
case QMetaType::Int: return DoCompare(left.toInt(), right.toInt());
case QMetaType::UInt: return DoCompare(left.toUInt(), right.toUInt());
case QMetaType::LongLong: return DoCompare(left.toLongLong(), right.toLongLong());
case QMetaType::ULongLong: return DoCompare(left.toULongLong(), right.toULongLong());
case QMetaType::Float: return DoCompare(left.toFloat(), right.toFloat());
case QMetaType::Double: return DoCompare(left.toDouble(), right.toDouble());
case QMetaType::Char: return DoCompare(left.toChar(), right.toChar());
case QMetaType::QDate: return DoCompare(left.toDate(), right.toDate());
case QMetaType::QTime: return DoCompare(left.toTime(), right.toTime());
case QMetaType::QDateTime: return DoCompare(left.toDateTime(), right.toDateTime());
case QMetaType::QString:
#else
case QVariant::Invalid: return (right.type() != QVariant::Invalid) ? -1 : 0;
case QVariant::Int: return DoCompare(left.toInt(), right.toInt());
case QVariant::UInt: return DoCompare(left.toUInt(), right.toUInt());
@@ -81,6 +95,7 @@ int MultiSortFilterProxy::Compare(const QVariant &left, const QVariant &right) c
case QVariant::Time: return DoCompare(left.toTime(), right.toTime());
case QVariant::DateTime: return DoCompare(left.toDateTime(), right.toDateTime());
case QVariant::String:
#endif
default:
if (isSortLocaleAware())
return left.toString().localeAwareCompare(right.toString());

View File

@@ -21,89 +21,18 @@
#include "config.h"
#include <type_traits>
#include <QtGlobal>
#include <QObject>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QIODevice>
#include <QMutex>
#include <QVariant>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkDiskCache>
#include <QNetworkCacheMetaData>
#include <QAbstractNetworkCache>
#include "network.h"
QMutex ThreadSafeNetworkDiskCache::sMutex;
ThreadSafeNetworkDiskCache *ThreadSafeNetworkDiskCache::sInstance = nullptr;
QNetworkDiskCache *ThreadSafeNetworkDiskCache::sCache = nullptr;
ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent) : QAbstractNetworkCache(parent) {
QMutexLocker l(&sMutex);
if (!sCache) {
sInstance = this;
sCache = new QNetworkDiskCache;
#ifdef Q_OS_WIN32
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/strawberry/networkcache");
#else
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/networkcache");
#endif
}
}
ThreadSafeNetworkDiskCache::~ThreadSafeNetworkDiskCache() {
if (this == sInstance) delete sCache;
}
qint64 ThreadSafeNetworkDiskCache::cacheSize() const {
QMutexLocker l(&sMutex);
return sCache->cacheSize();
}
QIODevice *ThreadSafeNetworkDiskCache::data(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->data(url);
}
void ThreadSafeNetworkDiskCache::insert(QIODevice *device) {
QMutexLocker l(&sMutex);
sCache->insert(device);
}
QNetworkCacheMetaData ThreadSafeNetworkDiskCache::metaData(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->metaData(url);
}
QIODevice *ThreadSafeNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) {
QMutexLocker l(&sMutex);
return sCache->prepare(metaData);
}
bool ThreadSafeNetworkDiskCache::remove(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->remove(url);
}
void ThreadSafeNetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData) {
QMutexLocker l(&sMutex);
sCache->updateMetaData(metaData);
}
void ThreadSafeNetworkDiskCache::clear() {
QMutexLocker l(&sMutex);
sCache->clear();
}
#include "threadsafenetworkdiskcache.h"
NetworkAccessManager::NetworkAccessManager(QObject *parent)
: QNetworkAccessManager(parent) {
@@ -144,4 +73,5 @@ QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkR
}
return QNetworkAccessManager::createRequest(op, new_request, outgoingData);
}

View File

@@ -27,15 +27,10 @@
#include <QtGlobal>
#include <QObject>
#include <QNetworkAccessManager>
#include <QAbstractNetworkCache>
#include <QMutex>
#include <QUrl>
#include <QNetworkRequest>
#include <QNetworkCacheMetaData>
class QIODevice;
class QNetworkReply;
class QNetworkDiskCache;
class NetworkAccessManager : public QNetworkAccessManager {
Q_OBJECT
@@ -47,25 +42,4 @@ class NetworkAccessManager : public QNetworkAccessManager {
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) override;
};
class ThreadSafeNetworkDiskCache : public QAbstractNetworkCache {
public:
explicit ThreadSafeNetworkDiskCache(QObject *parent);
~ThreadSafeNetworkDiskCache() override;
qint64 cacheSize() const override;
QIODevice *data(const QUrl &url) override;
void insert(QIODevice *device) override;
QNetworkCacheMetaData metaData(const QUrl &url) override;
QIODevice *prepare(const QNetworkCacheMetaData &metaData) override;
bool remove(const QUrl &url) override;
void updateMetaData(const QNetworkCacheMetaData &metaData) override;
void clear() override;
private:
static QMutex sMutex;
static ThreadSafeNetworkDiskCache *sInstance;
static QNetworkDiskCache *sCache;
};
#endif // NETWORK_H

View File

@@ -166,7 +166,7 @@ void Player::Init() {
CreateEngine(enginetype);
}
if (!engine_->Init()) { qFatal("Error initialising audio engine"); }
if (!engine_->Init()) { qFatal("Error initializing audio engine"); }
analyzer_->SetEngine(engine_.get());
@@ -398,6 +398,36 @@ void Player::NextItem(const Engine::TrackChangeFlags change, const Playlist::Aut
}
void Player::PlayPlaylist(const QString &playlist_name) {
PlayPlaylistInternal(Engine::Manual, Playlist::AutoScroll_Always, playlist_name);
}
void Player::PlayPlaylistInternal(Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const QString &playlist_name) {
Playlist *playlist = nullptr;
for (Playlist *p : app_->playlist_manager()->GetAllPlaylists()) {
if (playlist_name == app_->playlist_manager()->GetPlaylistName(p->id())) {
playlist = p;
break;
}
}
if (playlist == nullptr) {
qLog(Warning) << "Playlist '" << playlist_name << "' not found.";
return;
}
app_->playlist_manager()->SetActivePlaylist(playlist->id());
app_->playlist_manager()->SetCurrentPlaylist(playlist->id());
if (playlist->rowCount() == 0) return;
int i = app_->playlist_manager()->active()->current_row();
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
if (i == -1) i = 0;
PlayAt(i, change, autoscroll, true);
}
bool Player::HandleStopAfter(const Playlist::AutoScroll autoscroll) {
if (app_->playlist_manager()->active()->stop_after_current()) {

View File

@@ -81,8 +81,8 @@ class PlayerInterface : public QObject {
// Skips this track. Might load more of the current radio station.
virtual void Next() = 0;
virtual void Previous() = 0;
virtual void PlayPlaylist(const QString &playlist_name) = 0;
virtual void SetVolume(const int value) = 0;
virtual void VolumeUp() = 0;
virtual void VolumeDown() = 0;
@@ -163,6 +163,7 @@ class Player : public PlayerInterface {
void RestartOrPrevious() override;
void Next() override;
void Previous() override;
void PlayPlaylist(const QString &playlist_name) override;
void SetVolume(const int value) override;
void VolumeUp() override { SetVolume(GetVolume() + 5); }
void VolumeDown() override { SetVolume(GetVolume() - 5); }
@@ -195,6 +196,7 @@ class Player : public PlayerInterface {
void PreviousItem(const Engine::TrackChangeFlags change);
void NextInternal(const Engine::TrackChangeFlags, const Playlist::AutoScroll autoscroll);
void PlayPlaylistInternal(Engine::TrackChangeFlags, const Playlist::AutoScroll autoscroll, const QString &playlist_name);
void FatalError();
void ValidSongRequested(const QUrl&);

View File

@@ -24,8 +24,6 @@
#include <QCoreApplication>
#include <QSystemTrayIcon>
#include <QAction>
#include <QIODevice>
#include <QFile>
#include <QMenu>
#include <QIcon>
#include <QString>
@@ -56,21 +54,14 @@ QtSystemTrayIcon::QtSystemTrayIcon(QObject *parent)
action_mute_(nullptr) {
app_name_[0] = app_name_[0].toUpper();
QIcon theme_icon_grey = IconLoader::Load("strawberry-grey");
if (!theme_icon_grey.isNull()) {
grey_icon_ = theme_icon_grey.pixmap(48, QIcon::Disabled);
}
tray_->setIcon(normal_icon_);
tray_->installEventFilter(this);
ClearNowPlaying();
#ifndef Q_OS_WIN
de_ = Utilities::DesktopEnvironment().toLower();
QFile pattern_file(":/html/playing-tooltip.html");
if (pattern_file.open(QIODevice::ReadOnly)) {
pattern_ = QString::fromLatin1(pattern_file.readAll());
pattern_file.close();
}
#endif
connect(tray_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(Clicked(QSystemTrayIcon::ActivationReason)));
}
@@ -231,50 +222,9 @@ void QtSystemTrayIcon::SetVisible(bool visible) {
tray_->setVisible(visible);
}
void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QUrl &cover_url) {
void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QUrl&) {
#ifdef Q_OS_WIN
Q_UNUSED(song);
Q_UNUSED(cover_url);
// Windows doesn't support HTML in tooltips, so just show something basic
tray_->setToolTip(song.PrettyTitleWithArtist());
#else
if (de_ == "kde" || de_ == "cinnamon" || de_ == "x-cinnamon") {
tray_->setToolTip(song.PrettyTitleWithArtist());
return;
}
int columns = cover_url.isEmpty() ? 1 : 2;
QString tooltip(pattern_);
tooltip.replace("%columns" , QString::number(columns));
tooltip.replace("%appName" , app_name_);
tooltip.replace("%titleKey" , tr("Title") % ":");
tooltip.replace("%titleValue" , song.PrettyTitle().toHtmlEscaped());
tooltip.replace("%artistKey" , tr("Artist") % ":");
tooltip.replace("%artistValue", song.artist().toHtmlEscaped());
tooltip.replace("%albumKey" , tr("Album") % ":");
tooltip.replace("%albumValue" , song.album().toHtmlEscaped());
tooltip.replace("%lengthKey" , tr("Length") % ":");
tooltip.replace("%lengthValue", song.PrettyLength().toHtmlEscaped());
if (columns == 2) {
QString final_path = cover_url.isLocalFile() ? cover_url.path() : cover_url.toString();
tooltip.replace("%image", " <td> <img src=\"" % final_path % "\" /> </td>");
}
else {
tooltip.replace("<td>%image</td>", "");
tooltip.replace("%image", "");
}
// TODO: we should also repaint this
tray_->setToolTip(tooltip);
#endif
}

View File

@@ -52,7 +52,7 @@ class QtSystemTrayIcon : public SystemTrayIcon {
void ShowPopup(const QString &summary, const QString &message, int timeout) override;
void SetNowPlaying(const Song &song, const QUrl &cover_url) override;
void SetNowPlaying(const Song &song, const QUrl&) override;
void ClearNowPlaying() override;
bool MuteEnabled() const override { return action_mute_->isVisible(); }
@@ -88,11 +88,6 @@ class QtSystemTrayIcon : public SystemTrayIcon {
QAction *action_mute_;
QAction *action_love_;
#ifndef Q_OS_WIN
QString de_;
QString pattern_;
#endif
};
#endif // QTSYSTEMTRAYICON_H

View File

@@ -50,7 +50,6 @@
#include <QUrl>
#include <QImage>
#include <QIcon>
#include <QTextCodec>
#include <QSqlQuery>
#include <QStandardPaths>
#include <QtDebug>
@@ -745,14 +744,6 @@ void Song::set_genre_id3(int id) {
set_genre(TStringToQString(TagLib::ID3v1::genre(id)));
}
QString Song::Decode(const QString &tag, const QTextCodec *codec) {
if (!codec) {
return tag;
}
return codec->toUnicode(tag.toUtf8());
}
void Song::InitFromProtobuf(const pb::tagreader::SongMetadata &pb) {
if (d->source_ == Source_Unknown) d->source_ = Source_LocalFile;
@@ -1372,7 +1363,7 @@ void Song::BindToQuery(QSqlQuery *query) const {
query->bindValue(":art_automatic", d->art_automatic_.toString(QUrl::FullyEncoded));
query->bindValue(":art_manual", d->art_manual_.toString(QUrl::FullyEncoded));
query->bindValue(":effective_albumartist", this->effective_albumartist());
query->bindValue(":effective_albumartist", strval(this->effective_albumartist()));
query->bindValue(":effective_originalyear", intval(this->effective_originalyear()));
query->bindValue(":cue_path", d->cue_path_);
@@ -1436,6 +1427,14 @@ QString Song::PrettyYear() const {
}
QString Song::PrettyOriginalYear() const {
if (effective_originalyear() == -1) return QString();
return QString::number(effective_originalyear());
}
QString Song::TitleWithCompilationArtist() const {
QString title(d->title_);
@@ -1509,7 +1508,11 @@ bool Song::operator!=(const Song &other) const {
return source() != other.source() || url() != other.url() || beginning_nanosec() != other.beginning_nanosec();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
size_t qHash(const Song &song) {
#else
uint qHash(const Song &song) {
#endif
// Should compare the same fields as operator==
return qHash(song.url().toString()) ^ qHash(song.beginning_nanosec());
}

View File

@@ -38,7 +38,6 @@
#include <QImage>
#include <QIcon>
class QTextCodec;
class QSqlQuery;
namespace Engine {
@@ -177,8 +176,6 @@ class Song {
// Useful when you want updated tags from disk but you want to keep user stats.
void MergeUserSetData(const Song &other);
static QString Decode(const QString &tag, const QTextCodec *codec = nullptr);
// Save
void BindToQuery(QSqlQuery *query) const;
void BindToFtsQuery(QSqlQuery *query) const;
@@ -285,6 +282,7 @@ class Song {
QString PrettyTitleWithArtist() const;
QString PrettyLength() const;
QString PrettyYear() const;
QString PrettyOriginalYear() const;
QString TitleWithCompilationArtist() const;
@@ -381,7 +379,11 @@ Q_DECLARE_METATYPE(Song)
typedef QList<Song> SongList;
Q_DECLARE_METATYPE(QList<Song>)
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
size_t qHash(const Song &song);
#else
uint qHash(const Song &song);
#endif
// Hash function using field checked in IsSimilar function
uint HashSimilar(const Song &song);

View File

@@ -439,6 +439,7 @@ SongLoader::Result SongLoader::LoadRemote() {
errors_ << tr("Couldn't create gstreamer source element for %1").arg(url_.toString());
return Error;
}
g_object_set(source, "ssl-strict", FALSE, nullptr);
// Create the other elements and link them up
GstElement *typefind = gst_element_factory_make("typefind", nullptr);

View File

@@ -47,31 +47,31 @@ SystemTrayIcon::SystemTrayIcon(QObject *parent)
QPixmap SystemTrayIcon::CreateIcon(const QPixmap &icon, const QPixmap &grey_icon) {
Q_UNUSED(grey_icon);
QRect rect(icon.rect());
// The angle of the line that's used to cover the icon.
// Centered on rect.topRight()
double angle = double(100 - song_progress()) / 100.0 * M_PI_2 + M_PI;
double length = sqrt(pow(rect.width(), 2.0) + pow(rect.height(), 2.0));
QPolygon mask;
mask << rect.topRight();
mask << rect.topRight() + QPoint(length * sin(angle), -length * cos(angle));
if (song_progress() > 50) mask << rect.bottomLeft();
mask << rect.topLeft();
mask << rect.topRight();
QPixmap ret(icon);
QPainter p(&ret);
// Draw the grey bit
//p.setClipRegion(mask);
//p.drawPixmap(0, 0, grey_icon);
//p.setClipping(false);
if (trayicon_progress_) {
// The angle of the line that's used to cover the icon.
// Centered on rect.topLeft()
double angle = double(100 - song_progress()) / 100.0 * M_PI_2;
double length = sqrt(pow(rect.width(), 2.0) + pow(rect.height(), 2.0));
QPolygon mask;
mask << rect.topLeft();
mask << rect.topLeft() + QPoint(length * sin(angle), length * cos(angle));
if (song_progress() > 50) mask << rect.bottomRight();
mask << rect.topRight();
mask << rect.topLeft();
// Draw the grey bit
p.setClipRegion(mask);
p.drawPixmap(0, 0, grey_icon);
p.setClipping(false);
}
// Draw the playing or paused icon in the top-right
if (!current_state_icon().isNull()) {

View File

@@ -57,6 +57,7 @@ class SystemTrayIcon : public QObject {
public slots:
void SetProgress(int percentage);
void SetTrayiconProgress(bool enabled) { trayicon_progress_ = enabled; }
virtual void SetPaused();
virtual void SetPlaying(bool enable_play_pause = false);
virtual void SetStopped();
@@ -85,6 +86,7 @@ class SystemTrayIcon : public QObject {
QPixmap playing_icon_;
QPixmap paused_icon_;
QPixmap current_state_icon_;
bool trayicon_progress_;
};
#endif // SYSTEMTRAYICON_H

View File

@@ -61,7 +61,7 @@ QList<TaskManager::Task> TaskManager::GetTasks() {
}
void TaskManager::SetTaskBlocksCollectionScans(int id) {
void TaskManager::SetTaskBlocksCollectionScans(const int id) {
{
QMutexLocker l(&mutex_);
@@ -76,7 +76,7 @@ void TaskManager::SetTaskBlocksCollectionScans(int id) {
}
void TaskManager::SetTaskProgress(int id, int progress, int max) {
void TaskManager::SetTaskProgress(const int id, const qint64 progress, const qint64 max) {
{
QMutexLocker l(&mutex_);
@@ -90,7 +90,7 @@ void TaskManager::SetTaskProgress(int id, int progress, int max) {
emit TasksChanged();
}
void TaskManager::IncreaseTaskProgress(int id, int progress, int max) {
void TaskManager::IncreaseTaskProgress(const int id, const qint64 progress, const qint64 max) {
{
QMutexLocker l(&mutex_);
@@ -105,7 +105,7 @@ void TaskManager::IncreaseTaskProgress(int id, int progress, int max) {
}
void TaskManager::SetTaskFinished(int id) {
void TaskManager::SetTaskFinished(const int id) {
bool resume_collection_watchers = false;

View File

@@ -37,10 +37,11 @@ class TaskManager : public QObject {
explicit TaskManager(QObject *parent = nullptr);
struct Task {
Task() : id(0), progress(0), progress_max(0), blocks_collection_scans(false) {}
int id;
QString name;
int progress;
int progress_max;
qint64 progress;
qint64 progress_max;
bool blocks_collection_scans;
};
@@ -61,13 +62,13 @@ class TaskManager : public QObject {
QList<Task> GetTasks();
int StartTask(const QString &name);
void SetTaskBlocksCollectionScans(int id);
void SetTaskProgress(int id, int progress, int max = 0);
void IncreaseTaskProgress(int id, int progress, int max = 0);
void SetTaskFinished(int id);
int GetTaskProgress(int id);
void SetTaskBlocksCollectionScans(const int id);
void SetTaskProgress(const int id, const qint64 progress, const qint64 max = 0);
void IncreaseTaskProgress(const int id, const qint64 progress, const qint64 max = 0);
void SetTaskFinished(const int id);
int GetTaskProgress(const int id);
signals:
signals:
void TasksChanged();
void PauseCollectionWatchers();

View File

@@ -0,0 +1,108 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2019, 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 <QtGlobal>
#include <QObject>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QIODevice>
#include <QMutex>
#include <QNetworkDiskCache>
#include <QNetworkCacheMetaData>
#include <QAbstractNetworkCache>
#include <QUrl>
#include "core/logging.h"
#include "threadsafenetworkdiskcache.h"
QMutex ThreadSafeNetworkDiskCache::sMutex;
int ThreadSafeNetworkDiskCache::sInstances = 0;
QNetworkDiskCache *ThreadSafeNetworkDiskCache::sCache = nullptr;
ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent) : QAbstractNetworkCache(parent) {
QMutexLocker l(&sMutex);
++sInstances;
if (!sCache) {
sCache = new QNetworkDiskCache;
#ifdef Q_OS_WIN32
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/strawberry/networkcache");
#else
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/networkcache");
#endif
}
}
ThreadSafeNetworkDiskCache::~ThreadSafeNetworkDiskCache() {
QMutexLocker l(&sMutex);
--sInstances;
if (sCache && sInstances == 0) {
sCache->deleteLater();
sCache = nullptr;
}
}
qint64 ThreadSafeNetworkDiskCache::cacheSize() const {
QMutexLocker l(&sMutex);
return sCache->cacheSize();
}
QIODevice *ThreadSafeNetworkDiskCache::data(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->data(url);
}
void ThreadSafeNetworkDiskCache::insert(QIODevice *device) {
QMutexLocker l(&sMutex);
sCache->insert(device);
}
QNetworkCacheMetaData ThreadSafeNetworkDiskCache::metaData(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->metaData(url);
}
QIODevice *ThreadSafeNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) {
QMutexLocker l(&sMutex);
return sCache->prepare(metaData);
}
bool ThreadSafeNetworkDiskCache::remove(const QUrl &url) {
QMutexLocker l(&sMutex);
return sCache->remove(url);
}
void ThreadSafeNetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData) {
QMutexLocker l(&sMutex);
sCache->updateMetaData(metaData);
}
void ThreadSafeNetworkDiskCache::clear() {
QMutexLocker l(&sMutex);
sCache->clear();
}

View File

@@ -0,0 +1,61 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2019, 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 THREADSAFENETWORKDISKCACHE_H
#define THREADSAFENETWORKDISKCACHE_H
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QAbstractNetworkCache>
#include <QMutex>
#include <QUrl>
#include <QNetworkCacheMetaData>
class QIODevice;
class QNetworkDiskCache;
class ThreadSafeNetworkDiskCache : public QAbstractNetworkCache {
Q_OBJECT
public:
explicit ThreadSafeNetworkDiskCache(QObject *parent);
~ThreadSafeNetworkDiskCache() override;
qint64 cacheSize() const override;
QIODevice *data(const QUrl &url) override;
void insert(QIODevice *device) override;
QNetworkCacheMetaData metaData(const QUrl &url) override;
QIODevice *prepare(const QNetworkCacheMetaData &metaData) override;
bool remove(const QUrl &url) override;
void updateMetaData(const QNetworkCacheMetaData &metaData) override;
public slots:
void clear() override;
private:
static QMutex sMutex;
static int sInstances;
static QNetworkDiskCache *sCache;
};
#endif // THREADSAFENETWORKDISKCACHE_H

View File

@@ -64,6 +64,7 @@
#include <QtEvents>
#include <QMessageBox>
#include <QNetworkInterface>
#include <QImageReader>
#include <QtDebug>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
@@ -108,6 +109,9 @@
namespace Utilities {
QStringList kSupportedImageMimeTypes;
QStringList kSupportedImageFormats;
static QString tr(const char *str) {
return QCoreApplication::translate("", str);
}
@@ -211,7 +215,12 @@ quint64 FileSystemCapacity(const QString &path) {
return quint64(fs_info.f_blocks) * quint64(fs_info.f_bsize);
#elif defined(Q_OS_WIN32)
_ULARGE_INTEGER ret;
if (GetDiskFreeSpaceEx(QDir::toNativeSeparators(path).toLocal8Bit().constData(), nullptr,&ret, nullptr) != 0)
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
ScopedWCharArray wchar(QDir::toNativeSeparators(path));
if (GetDiskFreeSpaceEx(wchar.get(), nullptr, &ret, nullptr) != 0)
# else
if (GetDiskFreeSpaceEx(QDir::toNativeSeparators(path).toLocal8Bit().constData(), nullptr, &ret, nullptr) != 0)
# endif
return ret.QuadPart;
#endif
@@ -227,7 +236,12 @@ quint64 FileSystemFreeSpace(const QString &path) {
return quint64(fs_info.f_bavail) * quint64(fs_info.f_bsize);
#elif defined(Q_OS_WIN32)
_ULARGE_INTEGER ret;
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
ScopedWCharArray wchar(QDir::toNativeSeparators(path));
if (GetDiskFreeSpaceEx(wchar.get(), &ret, nullptr, nullptr) != 0)
# else
if (GetDiskFreeSpaceEx(QDir::toNativeSeparators(path).toLocal8Bit().constData(), &ret, nullptr, nullptr) != 0)
# endif
return ret.QuadPart;
#endif
@@ -758,29 +772,6 @@ bool IsLaptop() {
}
bool UrlOnSameDriveAsStrawberry(const QUrl &url) {
if (!url.isValid() || !url.isLocalFile() || url.toLocalFile().isEmpty()) return false;
#ifdef Q_OS_WIN
QUrl appUrl = QUrl::fromLocalFile(QCoreApplication::applicationDirPath());
if (url.toLocalFile().left(1) == appUrl.toLocalFile().left(1))
return true;
else
return false;
#else
// Non windows systems have always a / in the path
return true;
#endif
}
QUrl GetRelativePathToStrawberryBin(const QUrl &url) {
if (!url.isValid()) return QUrl();
QDir appPath(QCoreApplication::applicationDirPath());
return QUrl::fromLocalFile(appPath.relativeFilePath(url.toLocalFile()));
}
QString PathWithoutFilenameExtension(const QString &filename) {
if (filename.section('/', -1, -1).contains('.')) return filename.section('.', 0, -2);
return filename;
@@ -943,7 +934,7 @@ QString MacAddress() {
}
QString ReplaceMessage(const QString &message, const Song &song, const QString &newline) {
QString ReplaceMessage(const QString &message, const Song &song, const QString &newline, const bool html_escaped) {
QRegularExpression variable_replacer("[%][a-z]+[%]");
QString copy(message);
@@ -954,7 +945,7 @@ QString ReplaceMessage(const QString &message, const Song &song, const QString &
for (match = variable_replacer.match(message, pos) ; match.hasMatch() ; match = variable_replacer.match(message, pos)) {
pos = match.capturedStart();
QStringList captured = match.capturedTexts();
copy.replace(captured[0], ReplaceVariable(captured[0], song, newline));
copy.replace(captured[0], ReplaceVariable(captured[0], song, newline, html_escaped));
pos += match.capturedLength();
}
@@ -962,75 +953,117 @@ QString ReplaceMessage(const QString &message, const Song &song, const QString &
if (index_of >= 0) copy = copy.remove(index_of, 3);
return copy;
}
QString ReplaceVariable(const QString &variable, const Song &song, const QString &newline) {
QString ReplaceVariable(const QString &variable, const Song &song, const QString &newline, const bool html_escaped) {
QString return_value;
if (variable == "%artist%") {
return song.artist().toHtmlEscaped();
QString value = variable;
if (variable == "%title%") {
value = song.PrettyTitle();
}
else if (variable == "%album%") {
return song.album().toHtmlEscaped();
value = song.album();
}
else if (variable == "%title%") {
return song.PrettyTitle().toHtmlEscaped();
else if (variable == "%artist%") {
value = song.artist();
}
else if (variable == "%albumartist%") {
return song.effective_albumartist().toHtmlEscaped();
}
else if (variable == "%year%") {
return song.PrettyYear().toHtmlEscaped();
}
else if (variable == "%composer%") {
return song.composer().toHtmlEscaped();
}
else if (variable == "%performer%") {
return song.performer().toHtmlEscaped();
}
else if (variable == "%grouping%") {
return song.grouping().toHtmlEscaped();
}
else if (variable == "%length%") {
return song.PrettyLength().toHtmlEscaped();
}
else if (variable == "%disc%") {
return return_value.setNum(song.disc()).toHtmlEscaped();
value = song.effective_albumartist();
}
else if (variable == "%track%") {
return return_value.setNum(song.track()).toHtmlEscaped();
value.setNum(song.track());
}
else if (variable == "%disc%") {
value.setNum(song.disc());
}
else if (variable == "%year%") {
value = song.PrettyYear();
}
else if (variable == "%originalyear%") {
value = song.PrettyOriginalYear();
}
else if (variable == "%genre%") {
return song.genre().toHtmlEscaped();
value = song.genre();
}
else if (variable == "%playcount%") {
return return_value.setNum(song.playcount()).toHtmlEscaped();
else if (variable == "%composer%") {
value = song.composer();
}
else if (variable == "%skipcount%") {
return return_value.setNum(song.skipcount()).toHtmlEscaped();
else if (variable == "%performer%") {
value = song.performer();
}
else if (variable == "%grouping%") {
value = song.grouping();
}
else if (variable == "%length%") {
value = song.PrettyLength();
}
else if (variable == "%filename%") {
return song.basefilename().toHtmlEscaped();
value = song.basefilename();
}
else if (variable == "%url%") {
value = song.url().toString();
}
else if (variable == "%playcount%") {
value.setNum(song.playcount());
}
else if (variable == "%skipcount%") {
value.setNum(song.skipcount());
}
else if (variable == "%rating%") {
value = song.PrettyRating();
}
else if (variable == "%newline%") {
return QString(newline);
return QString(newline); // No HTML escaping, return immediately.
}
//if the variable is not recognized, just return it
return variable;
if (html_escaped) {
value = value.toHtmlEscaped();
}
return value;
}
bool IsColorDark(const QColor &color) {
return ((30 * color.red() + 59 * color.green() + 11 * color.blue()) / 100) <= 130;
}
QStringList SupportedImageMimeTypes() {
if (kSupportedImageMimeTypes.isEmpty()) {
for (const QByteArray &mimetype : QImageReader::supportedMimeTypes()) {
kSupportedImageMimeTypes << mimetype;
}
}
return kSupportedImageMimeTypes;
}
QStringList SupportedImageFormats() {
if (kSupportedImageFormats.isEmpty()) {
for (const QByteArray &filetype : QImageReader::supportedImageFormats()) {
kSupportedImageFormats << filetype;
}
}
return kSupportedImageFormats;
}
QList<QByteArray> ImageFormatsForMimeType(const QByteArray &mimetype) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
return QImageReader::imageFormatsForMimeType(mimetype);
#else
if (mimetype == "image/bmp") return QList<QByteArray>() << "BMP";
else if (mimetype == "image/gif") return QList<QByteArray>() << "GIF";
else if (mimetype == "image/jpeg") return QList<QByteArray>() << "JPG";
else if (mimetype == "image/png") return QList<QByteArray>() << "PNG";
else return QList<QByteArray>();
#endif
}

View File

@@ -108,12 +108,6 @@ const char *EnumToString(const QMetaObject &meta, const char *name, int value);
QStringList Prepend(const QString &text, const QStringList &list);
QStringList Updateify(const QStringList &list);
// Check if two urls are on the same drive (mainly for windows)
bool UrlOnSameDriveAsStrawberry(const QUrl &url);
// Get relative path to Strawberry binary
QUrl GetRelativePathToStrawberryBin(const QUrl &url);
// Get the path without the filename extension
QString PathWithoutFilenameExtension(const QString &filename);
QString FiddleFileExtension(const QString &filename, const QString &new_extension);
@@ -153,11 +147,13 @@ QString UnicodeToAscii(const QString &unicode);
QString MacAddress();
QString ReplaceMessage(const QString &message, const Song &song, const QString &newline);
QString ReplaceVariable(const QString &variable, const Song &song, const QString &newline);
QString ReplaceMessage(const QString &message, const Song &song, const QString &newline, const bool html_escaped = false);
QString ReplaceVariable(const QString &variable, const Song &song, const QString &newline, const bool html_escaped = false);
bool IsColorDark(const QColor &color);
QStringList SupportedImageMimeTypes();
QStringList SupportedImageFormats();
QList<QByteArray> ImageFormatsForMimeType(const QByteArray &mimetype);
} // namespace

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2020, 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
@@ -25,14 +26,17 @@
#include <QList>
#include <QPixmap>
#include <QAction>
#include <QTimer>
#include <QtDebug>
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#include <windows.h>
#include <commctrl.h>
#include <shobjidl.h>
extern HICON qt_pixmapToWinHICON(const QPixmap &p);
#include "core/logging.h"
@@ -44,10 +48,16 @@ const int Windows7ThumbBar::kMaxButtonCount = 7;
Windows7ThumbBar::Windows7ThumbBar(QWidget *widget)
: QObject(widget),
widget_(widget),
button_created_message_id_(0),
taskbar_list_(nullptr) {}
timer_(new QTimer(this)),
button_created_message_id_(0) {
void Windows7ThumbBar::SetActions(const QList<QAction*>& actions) {
timer_->setSingleShot(true);
timer_->setInterval(300);
connect(timer_, SIGNAL(timeout()), SLOT(ActionChanged()));
}
void Windows7ThumbBar::SetActions(const QList<QAction*> &actions) {
qLog(Debug) << "Setting actions";
Q_ASSERT(actions.count() <= kMaxButtonCount);
@@ -55,14 +65,39 @@ void Windows7ThumbBar::SetActions(const QList<QAction*>& actions) {
actions_ = actions;
for (QAction *action : actions) {
if (action) {
connect(action, SIGNAL(changed()), SLOT(ActionChanged()));
connect(action, SIGNAL(changed()), SLOT(ActionChangedTriggered()));
}
}
qLog(Debug) << "Done";
}
static void SetupButton(const QAction *action, THUMBBUTTON *button) {
ITaskbarList3 *Windows7ThumbBar::CreateTaskbarList() {
ITaskbarList3 *taskbar_list = nullptr;
// Copied from win7 SDK shobjidl.h
static const GUID CLSID_ITaskbarList = { 0x56FDF344,0xFD6D,0x11d0,{0x95,0x8A,0x00,0x60,0x97,0xC9,0xA0,0x90}};
// Create the taskbar list
HRESULT hr = CoCreateInstance(CLSID_ITaskbarList, nullptr, CLSCTX_ALL, IID_ITaskbarList3, (void**)&taskbar_list);
if (hr != S_OK) {
qLog(Warning) << "Error creating the ITaskbarList3 interface" << Qt::hex << DWORD (hr);
return nullptr;
}
hr = taskbar_list->HrInit();
if (hr != S_OK) {
qLog(Warning) << "Error initializing taskbar list" << Qt::hex << DWORD (hr);
taskbar_list->Release();
return nullptr;
}
return taskbar_list;
}
void Windows7ThumbBar::SetupButton(const QAction *action, THUMBBUTTON *button) {
if (action) {
button->hIcon = qt_pixmapToWinHICON(action->icon().pixmap(Windows7ThumbBar::kIconSize));
@@ -77,7 +112,7 @@ static void SetupButton(const QAction *action, THUMBBUTTON *button) {
button->dwMask = THUMBBUTTONMASK(THB_ICON | THB_TOOLTIP | THB_FLAGS);
}
else {
button->hIcon = 0;
button->hIcon = nullptr;
button->szTip[0] = L'\0';
button->dwFlags = THBF_NOBACKGROUND;
button->dwMask = THUMBBUTTONMASK(THB_FLAGS);
@@ -89,56 +124,38 @@ void Windows7ThumbBar::HandleWinEvent(MSG *msg) {
if (button_created_message_id_ == 0) {
// Compute the value for the TaskbarButtonCreated message
button_created_message_id_ = RegisterWindowMessage("TaskbarButtonCreated");
button_created_message_id_ = RegisterWindowMessageA(LPCSTR("TaskbarButtonCreated"));
qLog(Debug) << "TaskbarButtonCreated message ID registered" << button_created_message_id_;
}
if (msg->message == button_created_message_id_) {
HRESULT hr;
qLog(Debug) << "Button created";
// Unref the old taskbar list if we had one
if (taskbar_list_) {
qLog(Debug) << "Releasing old taskbar list";
reinterpret_cast<ITaskbarList3*>(taskbar_list_)->Release();
taskbar_list_ = nullptr;
}
// Copied from win7 SDK shobjidl.h
static const GUID CLSID_ITaskbarList ={ 0x56FDF344,0xFD6D,0x11d0,{0x95,0x8A,0x00,0x60,0x97,0xC9,0xA0,0x90}};
// Create the taskbar list
hr = CoCreateInstance(CLSID_ITaskbarList, nullptr, CLSCTX_ALL, IID_ITaskbarList3, (void**)&taskbar_list_);
if (hr != S_OK) {
qLog(Warning) << "Error creating the ITaskbarList3 interface" << Qt::hex << DWORD (hr);
return;
}
ITaskbarList3 *taskbar_list = reinterpret_cast<ITaskbarList3*>(taskbar_list_);
hr = taskbar_list->HrInit();
if (hr != S_OK) {
qLog(Warning) << "Error initialising taskbar list" << Qt::hex << DWORD (hr);
taskbar_list->Release();
taskbar_list_ = nullptr;
return;
}
ITaskbarList3 *taskbar_list = CreateTaskbarList();
if (!taskbar_list) return;
// Add the buttons
qLog(Debug) << "Initialising" << actions_.count() << "buttons";
THUMBBUTTON buttons[kMaxButtonCount];
for (int i = 0; i < actions_.count(); ++i) {
for (int i = 0 ; i < actions_.count() ; ++i) {
const QAction *action = actions_[i];
THUMBBUTTON *button = &buttons[i];
button->iId = i;
SetupButton(action, button);
}
qLog(Debug) << "Adding buttons";
hr = taskbar_list->ThumbBarAddButtons((HWND)widget_->winId(), actions_.count(), buttons);
if (hr != S_OK)
qLog(Debug) << "Adding" << actions_.count() << "buttons";
HRESULT hr = taskbar_list->ThumbBarAddButtons(reinterpret_cast<HWND>(widget_->winId()), actions_.count(), buttons);
if (hr != S_OK) {
qLog(Debug) << "Failed to add buttons" << Qt::hex << DWORD (hr);
for (int i = 0; i < actions_.count(); i++) {
if (buttons[i].hIcon)
DestroyIcon (buttons[i].hIcon);
}
for (int i = 0 ; i < actions_.count() ; ++i) {
if (buttons[i].hIcon) {
DestroyIcon (buttons[i].hIcon);
}
}
taskbar_list->Release();
}
else if (msg->message == WM_COMMAND) {
const int button_id = LOWORD(msg->wParam);
@@ -153,21 +170,38 @@ void Windows7ThumbBar::HandleWinEvent(MSG *msg) {
}
void Windows7ThumbBar::ActionChangedTriggered() {
if (!timer_->isActive()) timer_->start();
}
void Windows7ThumbBar::ActionChanged() {
if (!taskbar_list_) return;
ITaskbarList3 *taskbar_list = reinterpret_cast<ITaskbarList3*>(taskbar_list_);
ITaskbarList3 *taskbar_list = CreateTaskbarList();
if (!taskbar_list) return;
qLog(Debug) << "Updating" << actions_.count() << "buttons";
THUMBBUTTON buttons[kMaxButtonCount];
for (int i = 0; i < actions_.count(); ++i) {
const QAction *action = actions_[i];
for (int i = 0 ; i < actions_.count() ; ++i) {
QAction *action = actions_[i];
THUMBBUTTON *button = &buttons[i];
button->iId = i;
SetupButton(action, button);
if (buttons->hIcon) DestroyIcon(buttons->hIcon);
}
taskbar_list->ThumbBarUpdateButtons((HWND)widget_->winId(), actions_.count(), buttons);
HRESULT hr = taskbar_list->ThumbBarUpdateButtons(reinterpret_cast<HWND>(widget_->winId()), actions_.count(), buttons);
if (hr != S_OK) {
qLog(Debug) << "Failed to update buttons" << Qt::hex << DWORD (hr);
}
for (int i = 0 ; i < actions_.count() ; ++i) {
if (buttons[i].hIcon) {
DestroyIcon(buttons[i].hIcon);
}
}
taskbar_list->Release();
}

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2020, 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
@@ -21,24 +22,22 @@
#ifndef WINDOWS7THUMBBAR_H
#define WINDOWS7THUMBBAR_H
#include "config.h"
#include <windows.h>
#include <shobjidl.h>
#include <QObject>
#include <QWidget>
#include <QList>
#include <QString>
#include <QAction>
#ifndef Q_OS_WIN32
typedef void MSG;
#endif // Q_OS_WIN32
class QTimer;
class QAction;
class Windows7ThumbBar : public QObject {
Q_OBJECT
public:
// Creates a list of buttons in the taskbar icon for this window. Does nothing and is safe to use on other operating systems too.
explicit Windows7ThumbBar(QWidget *widget = 0);
public:
// Creates a list of buttons in the taskbar icon for this window.
explicit Windows7ThumbBar(QWidget *widget = nullptr);
static const int kIconSize;
static const int kMaxButtonCount;
@@ -49,17 +48,20 @@ public:
// Call this from the parent's winEvent() function.
void HandleWinEvent(MSG *msg);
private slots:
private:
ITaskbarList3 *CreateTaskbarList();
void SetupButton(const QAction *action, THUMBBUTTON *button);
private slots:
void ActionChangedTriggered();
void ActionChanged();
private:
private:
QWidget *widget_;
QTimer *timer_;
QList<QAction*> actions_;
unsigned int button_created_message_id_;
// Really an ITaskbarList3* but I don't want to have to include windows.h here
void *taskbar_list_;
};
#endif // WINDOWS7THUMBBAR_H

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2020, 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
@@ -22,7 +23,6 @@
#include <QtGlobal>
#include <QObject>
#include <QNetworkAccessManager>
#include <QTimer>
#include <QString>
#include <QImage>
@@ -34,7 +34,7 @@
const int AlbumCoverFetcher::kMaxConcurrentRequests = 5;
AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *parent, QNetworkAccessManager *network)
AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *parent, NetworkAccessManager *network)
: QObject(parent),
cover_providers_(cover_providers),
network_(network ? network : new NetworkAccessManager(this)),

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2020, 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
@@ -35,7 +36,7 @@
#include <QImage>
class QTimer;
class QNetworkAccessManager;
class NetworkAccessManager;
class CoverProviders;
class AlbumCoverFetcherSearch;
struct CoverSearchStatistics;
@@ -103,7 +104,7 @@ class AlbumCoverFetcher : public QObject {
Q_OBJECT
public:
explicit AlbumCoverFetcher(CoverProviders *cover_providers, QObject *parent = nullptr, QNetworkAccessManager *network = nullptr);
explicit AlbumCoverFetcher(CoverProviders *cover_providers, QObject *parent = nullptr, NetworkAccessManager *network = nullptr);
~AlbumCoverFetcher() override;
static const int kMaxConcurrentRequests;
@@ -126,7 +127,7 @@ class AlbumCoverFetcher : public QObject {
void AddRequest(const CoverSearchRequest &req);
CoverProviders *cover_providers_;
QNetworkAccessManager *network_;
NetworkAccessManager *network_;
quint64 next_id_;
QQueue<CoverSearchRequest> queued_requests_;

View File

@@ -34,12 +34,13 @@
#include <QUrl>
#include <QImage>
#include <QImageReader>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QtDebug>
#include "core/logging.h"
#include "core/utilities.h"
#include "core/network.h"
#include "core/networktimeouts.h"
#include "albumcoverfetcher.h"
#include "albumcoverfetchersearch.h"
@@ -51,8 +52,7 @@ const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 6000;
const int AlbumCoverFetcherSearch::kTargetSize = 500;
const float AlbumCoverFetcherSearch::kGoodScore = 4.0;
AlbumCoverFetcherSearch::AlbumCoverFetcherSearch(
const CoverSearchRequest &request, QNetworkAccessManager *network, QObject *parent)
AlbumCoverFetcherSearch::AlbumCoverFetcherSearch(const CoverSearchRequest &request, NetworkAccessManager *network, QObject *parent)
: QObject(parent),
request_(request),
image_load_timeout_(new NetworkTimeouts(kImageLoadTimeoutMs, this)),
@@ -325,7 +325,7 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(QNetworkReply *reply) {
}
else {
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
if (QImageReader::supportedMimeTypes().contains(mimetype.toUtf8())) {
if (Utilities::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) || Utilities::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
QImage image;
if (image.loadFromData(reply->readAll())) {
if (result.image_size != QSize(0,0) && result.image_size != image.size()) {

View File

@@ -36,10 +36,10 @@
#include "albumcoverfetcher.h"
#include "coversearchstatistics.h"
class QNetworkAccessManager;
class QNetworkReply;
class CoverProvider;
class CoverProviders;
class NetworkAccessManager;
class NetworkTimeouts;
// This class encapsulates a single search for covers initiated by an AlbumCoverFetcher.
@@ -49,7 +49,7 @@ class AlbumCoverFetcherSearch : public QObject {
Q_OBJECT
public:
explicit AlbumCoverFetcherSearch(const CoverSearchRequest &request, QNetworkAccessManager *network, QObject *parent);
explicit AlbumCoverFetcherSearch(const CoverSearchRequest &request, NetworkAccessManager *network, QObject *parent);
~AlbumCoverFetcherSearch() override;
void Start(CoverProviders *cover_providers);
@@ -107,7 +107,7 @@ class AlbumCoverFetcherSearch : public QObject {
typedef QPair<CoverSearchResult, QImage> CandidateImage;
QMultiMap<float, CandidateImage> candidate_images_;
QNetworkAccessManager *network_;
NetworkAccessManager *network_;
bool cancel_requested_;

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2020, 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

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