Compare commits

...

234 Commits

Author SHA1 Message Date
Jonas Kvinge
4b5da8c6d4 Release 1.0.11 2022-12-30 18:52:59 +01:00
Jonas Kvinge
77d7ffd10a Update Changelog 2022-12-30 18:52:34 +01:00
Jonas Kvinge
d2f720011a macdeployqt: Disable libgstgme 2022-12-29 22:26:21 +01:00
Jonas Kvinge
6329a7711d Fix includes 2022-12-29 21:39:03 +01:00
Jonas Kvinge
fd14d044e3 CI: Bump macOS version to 11 2022-12-29 20:32:54 +01:00
Strawbs Bot
6368d507e1 Update translations 2022-12-29 01:06:59 +01:00
Jonas Kvinge
205b7f2401 Split utilities functions into separate files 2022-12-28 22:53:59 +01:00
Strawbs Bot
f7d10f9530 Update translations 2022-12-28 01:19:59 +01:00
Jonas Kvinge
66154bb51e Song: Add xesam:userRating in Song::ToXesam 2022-12-27 21:18:40 +01:00
Jonas Kvinge
fac4ad5313 Scrobbler: Allow album artists to be "Various Artists"
Fixes #1082
2022-12-27 21:15:20 +01:00
Jonas Kvinge
fee63891ac FilesystemMusicStorage: QFile::moveToTrash recursively moves files to trash 2022-12-27 20:59:09 +01:00
Strawbs Bot
efa979ee03 Update translations 2022-12-27 01:01:39 +01:00
Jonas Kvinge
c756346357 GstEnginePipeline: Add audio converter for sink 2022-12-11 01:14:17 +01:00
Strawbs Bot
cc95db25cc Update translations 2022-12-11 01:05:26 +01:00
Jonas Kvinge
44970c3321 GstEnginePipeline: Use converter for event probe 2022-12-11 00:21:16 +01:00
Jonas Kvinge
43c7934af7 Mpris2: Use art manual or automatic directly when available
Fixes #1079
2022-12-10 23:22:46 +01:00
Jonas Kvinge
3e7b51163c Don't use system icons for tabbar
Fixes #1084
2022-12-10 18:49:01 +01:00
Jonas Kvinge
4d5f8a53f0 CI: Remove unneeded "shell: bash" 2022-12-05 18:14:40 +01:00
Jonas Kvinge
0b73ca8318 CI: Add Remove Fedora 35 and add 38 2022-12-05 18:07:27 +01:00
Jonas Kvinge
754cfc3bfd Mpris2: Rename function parameter variable 2022-12-04 21:42:36 +01:00
Jonas Kvinge
2ed1a5c06a Add keywords to desktop file 2022-12-04 21:36:45 +01:00
Jonas Kvinge
b2073df3c3 GstEnginePipeline: Detect if autoaudiosink has volume
Fixes #1037
2022-12-04 08:37:33 +01:00
Jonas Kvinge
6267edaa81 Update Changelog 2022-12-04 03:18:04 +01:00
Jonas Kvinge
292f2de3e6 GstEnginePipeline: Add more info when ignoring error 2022-12-04 03:18:04 +01:00
Strawbs Bot
7803dc8be3 Update translations 2022-12-04 02:44:03 +01:00
Jonas Kvinge
2a1260b79e PlaylistParser: Resolve symbolic links when creating relative paths
Fixes #1071
2022-12-04 00:18:55 +01:00
Jonas Kvinge
f9ec438b7f Don't remove disc from album title when creating cover hash
Fixes #1069
2022-12-03 23:23:18 +01:00
Jonas Kvinge
de544c4856 CI: Fix Fedora 37 build 2022-12-03 13:06:21 +01:00
Jonas Kvinge
55e04dd520 Player: Fix volume adjustment 2022-12-03 05:29:39 +01:00
Jonas Kvinge
b2d06f900b GstEnginePipeline: Correct parameter name for SetFaderVolume function 2022-12-03 04:47:41 +01:00
Jonas Kvinge
b92ec71810 Use system volume when possible
Fixes #1037
2022-12-03 04:33:22 +01:00
Jonas Kvinge
3a4199240e Change constexpr to const 2022-11-27 03:27:40 +01:00
Jonas Kvinge
157de12bdf Improve Tidal and Qobuz request 2022-11-27 01:28:29 +01:00
Jonas Kvinge
7c0c9fccdb Organize: Don't allow organizing files without unique tags
Fixes #1077
2022-11-26 23:37:41 +01:00
Jonas Kvinge
2cb29d06b3 EditTagDialog: Disable fields for unsupported tags 2022-11-23 22:06:45 +01:00
Jonas Kvinge
1fdeb50d93 TagReaderTagLib: Don't use TPE1 as performer for ID3 tags
Fixes #1076
2022-11-23 00:03:23 +01:00
Jonas Kvinge
f59632ae59 TagReaderTest: Add tests for reading and writing compilation tag 2022-11-22 21:27:26 +01:00
Jonas Kvinge
67b503da44 TagReaderTagLib: Fix compilation tag read/write for MP4
Partial fix for #1076
2022-11-22 21:09:18 +01:00
Jonas Kvinge
a351de6904 TagReaderTagLib: Fix saving FMPS_Playcount
Fixes #1074
2022-11-22 19:34:11 +01:00
Jonas Kvinge
f94742abd5 EditTagDialog: Fix tab order
Fixes #1075
2022-11-22 19:06:40 +01:00
Jonas Kvinge
891182637e Update README.md 2022-11-21 23:40:30 +01:00
bhrgunatha
086681c915 Add spacefm support to core/utilities.cpp OpenInFileManager 2022-11-19 12:36:21 +01:00
Strawbs Bot
3970d2d02b Update translations 2022-11-17 01:01:49 +01:00
Jonas Kvinge
2b3fddd015 main: Set PULSE_PROP_media.role 2022-11-17 00:59:22 +01:00
Jonas Kvinge
30fa0480a3 main: Set PULSE_PROP_application.icon_name
Fixes #1066
2022-11-17 00:57:10 +01:00
Jonas Kvinge
320a81ce69 main: Use capitalized name in g_set_application_name()
Partial fix for #1066
2022-11-16 23:53:53 +01:00
Jonas Kvinge
950411ef56 ResizableTextEdit: Force updating geometry
Possible fix for
2022-11-16 21:10:30 +01:00
Strawbs Bot
cb9cf084c0 Update translations 2022-11-12 01:01:38 +01:00
Strawbs Bot
bb18af09ae Update translations 2022-11-11 01:12:18 +01:00
Jonas Kvinge
049bf0c7f9 TrackSlider: Remove extra parentheses 2022-11-10 19:19:33 +01:00
Jonas Kvinge
ebc73ef775 CI: Move macOS remove problematic files step 2022-11-10 01:03:50 +01:00
Strawbs Bot
1bd6f59355 Update translations 2022-11-10 01:01:50 +01:00
Jonas Kvinge
c16396a690 GstEnginePipeline: Log pipeline initialized 2022-11-09 22:42:43 +01:00
Strawbs Bot
32a9fe3e9c Update translations 2022-10-30 01:01:54 +02:00
Jonas Kvinge
f48d133bc6 TidalRequest: Fix returning empty results 2022-10-30 00:17:38 +02:00
Jonas Kvinge
0678526d7d CI: Build on FreeBSD with Qt 6 2022-10-29 22:16:15 +02:00
Jonas Kvinge
26d3e8371f CollectionWatcher: Only run periodic scan when monitoring is on 2022-10-29 19:12:04 +02:00
Jonas Kvinge
6768f614c7 GstEngine: Call SetState right before setFuture 2022-10-29 18:45:09 +02:00
Jonas Kvinge
171dc84df1 GstEngine: Use QUrl::clear() 2022-10-29 18:43:49 +02:00
Jonas Kvinge
5ca6513c04 TidalRequest: Only return error when no songs are received
Fixes #1061
2022-10-28 21:46:14 +02:00
Strawbs Bot
41aeb0ac80 Update translations 2022-10-27 01:01:24 +02:00
Strawbs Bot
f871c4adef Update translations 2022-10-25 01:01:27 +02:00
Jonas Kvinge
ad4d0e89b1 Turn off git revision 2022-10-22 10:14:20 +02:00
Jonas Kvinge
0d0c49caa1 Release 1.0.10 2022-10-21 23:39:59 +02:00
Jonas Kvinge
7cbc7bdcfb Update Changelog 2022-10-21 23:38:29 +02:00
Jonas Kvinge
6b46c8ed4a CI: Fix macOS build 2022-10-21 22:14:38 +02:00
Jonas Kvinge
e33ffd1c8a MusixmatchLyricsProvider: Fix error message 2022-10-21 20:54:16 +02:00
Strawbs Bot
8dce9cc938 Update translations 2022-10-21 01:02:47 +02:00
Jonas Kvinge
f06218f8e3 Qobuz: Add option for base64 app secret 2022-10-20 20:47:06 +02:00
Strawbs Bot
5711eef2be Update translations 2022-10-20 01:20:20 +02:00
Jonas Kvinge
db6aac603c nsi: Bump icu 2022-10-19 19:23:54 +02:00
Jonas Kvinge
f10e928106 Organize: Only load embedded cover if the destination is a device 2022-10-19 18:37:49 +02:00
Jonas Kvinge
99d963b99c MusicStorage: Add source 2022-10-19 18:36:33 +02:00
Jonas Kvinge
5d35c22238 OrganizeDialog: Use QUrl::isLocalFile 2022-10-19 18:34:30 +02:00
Jonas Kvinge
54fc11a235 CollectionBackend: Rename source accessor 2022-10-19 17:11:56 +02:00
Jonas Kvinge
ec99df3144 Create common class for Musixmatch 2022-10-18 22:49:15 +02:00
Jonas Kvinge
d722035883 VLCEngine: Only emit stopped when still playing
VLC sends the stopped signal too late, the player has already moved on based on the end reached signal. Emitting Stopped afterwards will interrupt the player in the middle of the song change.

Fixes #1054
2022-10-18 20:12:21 +02:00
Jonas Kvinge
52139fbaa0 Player: Reverse if 2022-10-18 20:09:38 +02:00
Jonas Kvinge
88854eb558 nsi: Bump libunistring 2022-10-17 21:41:14 +02:00
Jonas Kvinge
c82bba01ee MusixmatchLyricsProvider: Remove unused declaration 2022-10-17 20:34:51 +02:00
Jonas Kvinge
eaa33a03d7 MusixmatchLyricsProvider: Use API for lyrics search when possible 2022-10-17 20:29:44 +02:00
Jonas Kvinge
89e8518f31 GeniusLyricsProvider: Make private 2022-10-17 20:28:51 +02:00
Jonas Kvinge
c7bf2e1da8 LyricsProvider: Add missing newline 2022-10-16 22:52:06 +02:00
Jonas Kvinge
bf904a6afa LyricsProvider: Parse multiple sections of the same tag 2022-10-16 01:27:54 +02:00
Jonas Kvinge
43c14ae71b Replace pragma once with header guards for consistency
Only 2 headers have this.
2022-10-15 19:54:09 +02:00
Jonas Kvinge
4abb8ef3c9 Update Changelog 2022-10-15 14:39:49 +02:00
Jonas Kvinge
e46b92dd7d GeniusLyricsProvider: Use new function for parsing HTML 2022-10-15 14:34:09 +02:00
Jonas Kvinge
1a25faa5b9 LyricsProvider: Add function for parsing lyrics from HTML 2022-10-15 14:33:35 +02:00
Strawbs Bot
3f4bf5f512 Update translations 2022-10-14 01:18:29 +02:00
Jonas Kvinge
ae7dbf15ed nsi: Remove avresample-4.dll 2022-10-14 00:10:30 +02:00
Jonas Kvinge
b22320c48f Replace typedef with using 2022-10-13 22:39:31 +02:00
Jonas Kvinge
61204e8d35 CMakeLists: cmake_minimum_required before project 2022-10-13 21:41:46 +02:00
Jonas Kvinge
b0704c654c utilities_test: Fix QByteArray conversion 2022-10-13 21:37:11 +02:00
Strawbs Bot
f40f8a8873 Update translations 2022-10-13 01:01:31 +02:00
Jonas Kvinge
560a7db506 Update .clang-format 2022-10-12 21:28:14 +02:00
Strawbs Bot
15baa8f70e Update translations 2022-10-12 01:01:48 +02:00
Jonas Kvinge
97a7637294 FileView: Use QFileIconProvider instead of QAbstractFileIconProvider
Fixes compile with Qt 5
2022-10-12 00:36:16 +02:00
Jonas Kvinge
3454656207 PlaylistContainer: Fix search field crash on macOS
Fixes #1053
2022-10-12 00:11:12 +02:00
Iridias
7dff6f26bc FileView: Create new file icon provider if icons are missing 2022-10-11 23:52:00 +02:00
Jonas Kvinge
9e835a23fd Rename QSearchField 2022-10-11 23:34:22 +02:00
Jonas Kvinge
c0259fb6ce CI: Remove unused macOS brew packages 2022-10-11 00:27:26 +02:00
Jonas Kvinge
acf106220b CI: Upgrade packages for macOS 2022-10-10 22:31:35 +02:00
Strawbs Bot
5d4dc9c907 Update translations 2022-10-10 01:02:17 +02:00
Jonas Kvinge
2b9a56af7f Update Changelog 2022-10-09 22:18:20 +02:00
Jonas Kvinge
f1e3ac65ac TidalRequest: Simplify no results
Fixes #1047
2022-10-09 22:07:59 +02:00
Jonas Kvinge
143f72cf6b PlaylistView: Fix scaling mid bar for currently playing track
Fixes #1051
2022-10-09 21:41:16 +02:00
Jonas Kvinge
5c8e49296c CI: Add Fedora 37 2022-10-05 00:08:09 +02:00
Jonas Kvinge
b2558b703c nsi: Add jpeg62.dll for MSVC 2022-10-02 22:33:36 +02:00
Jonas Kvinge
bf9d87106e CI: Build Jammy with Qt 6 2022-10-02 19:38:22 +02:00
Jonas Kvinge
5b7f507d9b Windows7ThumbBar: Use QString length instead of count 2022-10-01 14:03:28 +02:00
Jonas Kvinge
59a7835ace CI: Fix if lines 2022-10-01 01:40:32 +02:00
Jonas Kvinge
2495838fe3 CI: Use matrix 2022-09-30 23:58:25 +02:00
Jonas Kvinge
bab687bedf Add USE_RPATH option 2022-09-30 01:00:29 +02:00
Jonas Kvinge
6467c3c8ee Update CMakeLists.txt 2022-09-30 01:00:15 +02:00
Jonas Kvinge
66c2b7aaa6 Update Version.cmake 2022-09-30 00:59:33 +02:00
Jonas Kvinge
ab72c52661 CI: Remove custom libsoup step for macOS 2022-09-29 22:33:47 +02:00
Jonas Kvinge
f0bf1b8a54 Rename CI 2022-09-29 22:32:04 +02:00
Strawbs Bot
119251719f Update translations 2022-09-24 01:01:45 +02:00
Jonas Kvinge
0348400132 ContextView: Remove use of fixed font
Fixes #1040
2022-09-23 18:10:01 +02:00
Jonas Kvinge
813473805b CI: Get latest MSVC dependencies 2022-09-17 01:45:42 +02:00
Strawbs Bot
fe6368561b Update translations 2022-09-17 01:01:19 +02:00
Strawbs Bot
7ff06f424d Update translations 2022-09-15 01:01:21 +02:00
Jonas Kvinge
31958592c7 main: Always create initial style settings 2022-09-13 22:44:24 +02:00
Jonas Kvinge
adc21f4f75 Remove subdir for generated dbus files 2022-09-13 17:53:57 +02:00
Strawbs Bot
9cecd89d6f Update translations 2022-09-13 01:26:32 +02:00
Jonas Kvinge
d866b9b4d4 Update definitions i CMakeLists 2022-09-12 23:20:07 +02:00
Jonas Kvinge
93f12baf51 Fix narrowing conversions in connects 2022-09-12 23:18:54 +02:00
Jonas Kvinge
78d6fd634b Fix casts from QByteArray 2022-09-12 22:39:08 +02:00
Strawbs Bot
b5fc19f08a Update translations 2022-09-12 01:01:20 +02:00
Jonas Kvinge
2dae6a6546 nsi: Update flac dll version 2022-09-10 23:52:27 +02:00
Jonas Kvinge
b6bba46391 CI: Update vmactions/freebsd-vm 2022-09-09 23:02:28 +02:00
Jonas Kvinge
b791d97116 CI: Use variable for workspace 2022-09-09 22:40:37 +02:00
Strawbs Bot
6aa8255f34 Update translations 2022-09-08 01:01:22 +02:00
Jonas Kvinge
98e2140761 Turn on git revision 2022-09-04 00:48:35 +02:00
Jonas Kvinge
a59e064778 Release 1.0.9 2022-09-03 20:40:14 +02:00
Jonas Kvinge
655c4c66a7 Update Changelog 2022-09-03 20:34:25 +02:00
Jonas Kvinge
407c128f65 GstStartup: Set LIBSOUP3_LIBRARY_PATH for macOS 2022-09-03 00:19:25 +02:00
Jonas Kvinge
59a261a5be main: Add Frameworks to library paths for macOS 2022-09-03 00:19:25 +02:00
Jonas Kvinge
ba4e1afefe CI: Compile entire gstreamer from source for macOS 2022-09-02 23:55:25 +02:00
Jonas Kvinge
98b53b81d8 Update Changelog 2022-09-01 22:06:00 +02:00
Jonas Kvinge
8270cc0aa2 macdeploycheck: Fail on parse error 2022-09-01 21:58:11 +02:00
Jonas Kvinge
74207b1a87 Update ISSUE_TEMPLATE.md 2022-09-01 21:35:33 +02:00
Jonas Kvinge
77983445ce debian: Fix icu dependency 2022-09-01 19:18:40 +02:00
Ondrej Mosnáček
162190bcb8 Mark collectionmodel_test as not requiring GUI
The test doesn't seem to acually need gui_required = true, so set it to
false.
2022-08-31 12:40:46 -07:00
Jonas Kvinge
86b92b20b7 macdeployqt: Add back extra changeInstallName 2022-08-30 21:35:11 +02:00
Jonas Kvinge
2f5b60d548 macdeployqt: Fix deploying libgcc
Fixes #1025
2022-08-30 19:33:59 +02:00
Jonas Kvinge
c19661c977 macdeploycheck: Fix check for libgcc 2022-08-30 19:32:59 +02:00
Jonas Kvinge
82d101ca27 macdeployqt: Merge changes from upstream 2022-08-30 17:48:16 +02:00
Jonas Kvinge
32f9c4e670 GstEnginePipeline: Parse album from stream title tag
Fixes #1023
2022-08-30 16:56:08 +02:00
Jonas Kvinge
34c7225ab7 Turn back git revision 2022-08-29 22:05:07 +02:00
Jonas Kvinge
b02eb502e4 Release 1.0.8 2022-08-29 17:54:05 +02:00
Jonas Kvinge
7d6b3dfd20 Update maketarball.sh.in 2022-08-29 17:53:49 +02:00
Strawbs Bot
3ac39968d2 Update translations 2022-08-29 01:16:24 +02:00
Jonas Kvinge
2b24ac54a0 Remove unused includes 2022-08-28 03:09:33 +02:00
Jonas Kvinge
6da6b794c7 VolumeSlider: Remove unused Background and Foreground 2022-08-28 02:52:40 +02:00
Jonas Kvinge
ed689e27c9 Transcoder: Change return type for QueuedJobsCount to qint64 2022-08-28 02:51:10 +02:00
Jonas Kvinge
186f9c3f18 SmartPlaylistWizard: Add missing override 2022-08-28 02:50:33 +02:00
Jonas Kvinge
42cdde3203 SmartPlaylistsViewContainer: Add missing override 2022-08-28 02:50:17 +02:00
Jonas Kvinge
b9091702e9 SmartPlaylistSearchTermWidget: Add missing override 2022-08-28 02:50:02 +02:00
Jonas Kvinge
d42ecbe74e SmartPlaylistQueryWizardPlugin: Add missing override 2022-08-28 02:49:45 +02:00
Jonas Kvinge
e6c5446d63 PlaylistQueryGenerator: Add missing override 2022-08-28 02:49:30 +02:00
Jonas Kvinge
cc277211b3 LibreFMScrobbler: Call base class for ReloadSettings 2022-08-28 02:48:47 +02:00
Jonas Kvinge
6703c15c52 RadioPlaylistItem: Fix header guard comment 2022-08-28 02:48:22 +02:00
Jonas Kvinge
4ecdaf573e MoodbarRenderer: Remove unused kNumHues 2022-08-28 02:47:45 +02:00
Jonas Kvinge
71ec3e61be signalchecker: Remove useless return 2022-08-28 02:46:23 +02:00
Jonas Kvinge
216fdb2393 FileSystemWatcherInterface: Change signal to non const 2022-08-28 02:45:56 +02:00
Jonas Kvinge
c39acc6e3c Database: Remove useless return 2022-08-28 02:45:34 +02:00
Jonas Kvinge
d97b0478a7 Fix typos 2022-08-28 02:44:37 +02:00
Jonas Kvinge
d15d64eb67 nsi: Add nghttp2.dll for MSVC 2022-08-27 23:43:39 +02:00
Jonas Kvinge
3acd2656ee Update Changelog 2022-08-27 23:14:56 +02:00
Jonas Kvinge
a4b003534a CI: Switch to libsoup 3 2022-08-27 19:36:29 +02:00
Jonas Kvinge
8752538a02 nsi: Switch to libsoup 3 2022-08-27 19:36:29 +02:00
Strawbs Bot
bde435753a Update translations 2022-08-25 01:02:52 +02:00
Jonas Kvinge
d347e6fc5f Add setting for turning off HTTP/2 2022-08-24 20:34:10 +02:00
Jonas Kvinge
27b01d3642 Analyzer::Base: Set Qt::WA_OpaquePaintEvent 2022-08-24 20:32:08 +02:00
Jonas Kvinge
b78677e285 nsi: Use libsoup 2 2022-08-22 23:19:49 +02:00
Jonas Kvinge
f8f6c74bcb CI: Use libsoup 2 2022-08-22 23:19:40 +02:00
Strawbs Bot
7ce123939b Update translations 2022-08-21 01:19:35 +02:00
Jonas Kvinge
e7b02cfb15 TagReaderGME: Use UTF-16 2022-08-21 00:36:06 +02:00
Jonas Kvinge
5931077b08 macdeployqt: Add GStreamer GME plugin 2022-08-20 19:52:32 +02:00
Jonas Kvinge
a521bdc0e3 TagReaderGME: Compare file extension case-insensitive 2022-08-20 19:08:58 +02:00
Eoin O'Neill
80da565609 Initial support for GME's VGM/SPC playback and tag management.
Co-Authored-By: Jonas Kvinge <jonas@jkvinge.net>
2022-08-20 18:33:13 +02:00
Jonas Kvinge
6bc46e4598 CollectionModel: Make separate_albums_by_grouping optional 2022-08-20 16:47:09 +02:00
Jonas Kvinge
6562258db5 CollectionModel: Make separating albums by grouping optional
Fixes #1018
2022-08-20 14:51:19 +02:00
Jonas Kvinge
c219995218 Update .gitignore 2022-08-19 21:15:08 +02:00
Jonas Kvinge
62035431ed Install Visual C++ runtime 2022-08-18 22:47:34 +02:00
Jonas Kvinge
fa1fbca7dc DirectSoundDeviceFinder: Remove __attribute__((stdcall)) 2022-08-15 19:55:55 +02:00
Strawbs Bot
391b7476b3 Update translations 2022-08-15 01:01:37 +02:00
Jonas Kvinge
9c04ce665f DirectSoundDeviceFinder: Add CALLBACK to EnumerateCallback 2022-08-14 10:37:31 +02:00
Jonas Kvinge
dd87268197 CI: Build Windows x86 2022-08-14 10:37:31 +02:00
Jonas Kvinge
8d9af59db2 MainWindow: Use different Sparkle URL for x86 2022-08-14 10:37:31 +02:00
Strawbs Bot
1b754a35ff Update translations 2022-08-14 01:01:17 +02:00
Jonas Kvinge
7230f91f61 CI: Update actions 2022-08-10 21:32:18 +02:00
Strawbs Bot
93edfc315c Update translations 2022-08-10 01:01:34 +02:00
Strawbs Bot
74c8269531 Update translations 2022-08-09 18:02:04 +02:00
Jonas Kvinge
acb6c0fc83 Use PlaylistFilter directly 2022-08-09 17:23:46 +02:00
Jonas Kvinge
553d4cce93 PlaylistContainer: Translate Undo/Redo
Fixes #1017
2022-08-09 17:04:59 +02:00
Jonas Kvinge
ca81f144e6 README: Add ICU to dependencies 2022-08-08 00:37:37 +02:00
Jonas Kvinge
ec84960347 nsi: Move icu to common files 2022-08-08 00:37:19 +02:00
Jonas Kvinge
38db0764af Require ICU 2022-08-07 20:23:23 +02:00
Jonas Kvinge
a647f63bb0 CI: Add macOS 2022-08-07 17:35:53 +02:00
Jonas Kvinge
5b7087cc9e nsi: Add files for MSVC 2022-08-07 12:32:31 +02:00
Jonas Kvinge
1a6fcd5da6 QSearchField: Replace use of C-style cast 2022-08-07 05:14:38 +02:00
Jonas Kvinge
e31dd9f553 MacSystemTrayIcon: Replace use of C-style cast 2022-08-07 05:14:05 +02:00
Jonas Kvinge
22844716d1 RadioView: Remove duplicate double clicked
Already done in AutoExpandingTreeView::ItemDoubleClicked

Fixes #1015
2022-08-07 02:40:09 +02:00
Jonas Kvinge
6abed76f70 nsi: Add gme 2022-08-06 17:02:00 +02:00
Jonas Kvinge
72ff502e0f OSDDBus: Set timeout 2022-07-31 03:28:13 +02:00
Jonas Kvinge
2b781df32b CI: Use openSUSE Leap 15.4 for source builder 2022-07-29 16:27:21 +02:00
Strawbs Bot
bf601c3906 Update translations 2022-07-29 01:25:43 +02:00
Jonas Kvinge
b753e0ebb0 CI: Remove macOS 2022-07-28 16:56:35 +02:00
Jonas Kvinge
564211aceb Prefer ICU to transliterate characters when available
Fixes #1008
2022-07-28 16:31:16 +02:00
Strawbs Bot
538c759fef Update translations 2022-07-28 01:02:19 +02:00
Jonas Kvinge
95edc34100 Update README and issue template 2022-07-27 20:43:03 +02:00
Jonas Kvinge
98682a2da9 Use C++17 fallthrough 2022-07-26 20:37:06 +02:00
Jonas Kvinge
33581fa61d PlaylistContainer: Fix filter text when switching playlist
Fixes #1005
2022-07-26 07:35:43 +02:00
Jonas Kvinge
b02e0aae98 PlaylistListSortFilterModel: Add header guard 2022-07-26 07:33:11 +02:00
Jonas Kvinge
6fa4e1cb6d Turn on git revision 2022-07-25 23:37:33 +02:00
Jonas Kvinge
9f2ec22e95 Release 1.0.7 2022-07-25 21:45:27 +02:00
Jonas Kvinge
65b2e506b1 CI: Remove Ubuntu Impish 2022-07-25 17:29:35 +02:00
Jonas Kvinge
8bc07b5286 nsi: Add libgpg-error-0.dll 2022-07-25 15:51:05 +02:00
Jonas Kvinge
fe0af63795 Update Changelog 2022-07-25 15:28:25 +02:00
Jonas Kvinge
44ce7f4c67 CI: Disable audio cd for Windows MinGW build 2022-07-25 15:27:17 +02:00
Jonas Kvinge
252936134b nsi: Remove gstwinrt-1.0-0.dll 2022-07-25 04:25:08 +02:00
Jonas Kvinge
e255b06945 macdeployqt: Update gstreamer plugins 2022-07-25 03:56:38 +02:00
Jonas Kvinge
d726568a23 nsi: Add gstwavenc 2022-07-25 03:55:38 +02:00
Jonas Kvinge
66880a6de7 nsi: Update gstreamer plugins 2022-07-25 03:23:34 +02:00
Jonas Kvinge
fda622a0c0 Update Changelog 2022-07-24 23:44:54 +02:00
Jonas Kvinge
efa7d10f40 nsi: Add twolame 2022-07-21 03:04:48 +02:00
Strawbs Bot
348a3fabab Update translations 2022-07-21 01:09:44 +02:00
Jonas Kvinge
1bbaee605c PlaylistParser: Check file extension case-insensitive 2022-07-20 14:56:55 +02:00
Jonas Kvinge
22edf7a2b3 TagReaderTagParser: Fix reading and writing rating 2022-07-20 11:44:46 +02:00
Jonas Kvinge
3ffcc29249 Add back save all playlists action 2022-07-20 01:09:00 +02:00
Jonas Kvinge
21f1fe52c0 Remove save all playlists action
It's hard-coded to m3u, needs a new select directory dialog with option to select different playlist formats.

Fixes #987
2022-07-18 22:56:32 +02:00
Jonas Kvinge
99840c9e4f Turn on git revision 2022-07-18 00:40:00 +02:00
479 changed files with 25923 additions and 23200 deletions

View File

@@ -1,105 +1,216 @@
BasedOnStyle: Chromium
Language: Cpp
Standard: Cpp11
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -1
AlignAfterOpenBracket: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignConsecutiveMacros: true
AlignEscapedNewlines: true
AlignOperands: false
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignEscapedNewlines: Left
AlignOperands: DontAlign
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
AllowShortLambdasOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: No
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BreakBeforeBraces: false
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Stroustrup
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeColon
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: true
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 0
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 2
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: BinPack
BasedOnStyle: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: true
IncludeBlocks: Preserve
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: AfterHash
IndentWidth: 2
IndentExternBlock: AfterExternBlock
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 100
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 0
PenaltyBreakBeforeFirstCallParameter: 0
PenaltyBreakComment: 0
PenaltyBreakFirstLessLess: 0
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 0
PenaltyBreakTemplateDeclaration: 0
PenaltyExcessCharacter: 0
PenaltyReturnTypeOnItsOwnLine: 0
PenaltyIndentedWhitespace: 0
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
RequiresClausePosition: OwnLine
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: false
SortJavaStaticImport: Before
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
UseCRLF: false
UseTab: Never
BreakBeforeBraces: Custom
BraceWrapping:
AfterFunction: false
AfterCaseLabel: false
AfterStruct: false
AfterClass: false
AfterEnum: false
AfterUnion: false
AfterControlStatement: Never
AfterNamespace: false
AfterObjCDeclaration: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: true
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

View File

@@ -7,13 +7,9 @@ assignees: ''
---
For technical issues, questions and discussion please use the forum on https://forum.strawberrymusicplayer.org/
Any issues related to feature requests will be closed. See the README for more details.
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/
- [ ] I have checked the [FAQ](https://wiki.strawberrymusicplayer.org/wiki/FAQ) for answers.
- [ ] I have checked the [Changelog](https://github.com/strawberrymusicplayer/strawberry/blob/master/Changelog) that the issue is not already fixed.
- [ ] I believe this issue is a bug, and not a general technical issue, question or feature requests that can be discussed on the [forum](https://forum.strawberrymusicplayer.org/).
**Describe the bug**
A clear and concise description of what the bug is.

1054
.github/workflows/build.yml vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

39
.gitignore vendored
View File

@@ -65,19 +65,6 @@ ui_*.h
*.moc
*.qm
# QtCreator
CMakeLists.txt.user*
*.pro.user
*.pro.user.*
*creator.user*
target_wrapper.*
compile_commands.json
*.kdev4
*.vscode
*.code-workspace
*.sublime-workspace
# Temporary files
*~
*.autosave
@@ -111,15 +98,23 @@ translations.pot
zanata.xml
.zanata-cache/
# Snap
parts/
prime/
stage/
*.snap
/snap/.snapcraft/
/*_source.tar.bz2
# QtCreator
CMakeLists.txt.user*
*.pro.user
*.pro.user.*
*creator.user*
target_wrapper.*
compile_commands.json
*.kdev4
*.vscode
*.code-workspace
*.sublime-workspace
# MSVC
CMakeSettings.json
/.vs/
/out/
/.vs
/out
# CLion
/.idea

View File

@@ -40,6 +40,8 @@
#include <QDir>
#include <QSet>
#include <QList>
#include <QVariant>
#include <QVariantMap>
#include <QStack>
#include <QDirIterator>
#include <QLibraryInfo>
@@ -187,9 +189,7 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
return info;
}
static const QRegularExpression regexp(QStringLiteral(
"^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
"current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$"));
static const QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$"));
QString output = otool.readAllStandardOutput();
QStringList outputLines = output.split("\n", Qt::SkipEmptyParts);
@@ -220,6 +220,8 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
for (const QString &outputLine : outputLines) {
const auto match = regexp.match(outputLine);
if (match.hasMatch()) {
if (match.captured(1) == info.installName)
continue; // Another arch reference to the same binary
DylibInfo dylib;
dylib.binaryPath = match.captured(1);
dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
@@ -300,11 +302,11 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
if (state == QtPath) {
// Check for library name part
if (part < parts.count() && parts.at(part).contains(".dylib")) {
info.frameworkDirectory += "/" + (qtPath + currentPart + "/").simplified();
info.frameworkDirectory += "/" + QString(qtPath + currentPart + "/").simplified();
state = DylibName;
continue;
} else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
info.frameworkDirectory += "/" + (qtPath + "lib/").simplified();
info.frameworkDirectory += "/" + QString(qtPath + "lib/").simplified();
state = FrameworkName;
continue;
} else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package.
@@ -865,6 +867,18 @@ void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework
if (!framework.installName.isEmpty() && framework.installName != framework.sourceFilePath) {
changeInstallName(framework.installName, deployedInstallName, binary);
}
// Workaround for the case when the library ID name is a symlink, while the dependencies
// specified using the canonical path to the library (QTBUG-56814)
QFileInfo fileInfo= QFileInfo(framework.installName);
QString canonicalInstallName = fileInfo.canonicalFilePath();
if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
changeInstallName(canonicalInstallName, deployedInstallName, binary);
// some libraries' inner dependencies (such as ffmpeg, nettle) use symbol link (QTBUG-100093)
QString innerDependency = fileInfo.canonicalPath() + "/" + fileInfo.fileName();
if (innerDependency != canonicalInstallName && innerDependency != framework.installName) {
changeInstallName(innerDependency, deployedInstallName, binary);
}
}
}
}
@@ -1078,7 +1092,7 @@ QString getLibInfix(const QStringList &deployedFrameworks)
{
QString libInfix;
for (const QString &framework : deployedFrameworks) {
if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework"))) {
if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework")) && !framework.contains(QStringLiteral("5Compat"))) {
Q_ASSERT(framework.length() >= 16);
// 16 == "QtCore" + ".framework"
const int lengthOfLibInfix = framework.length() - 16;
@@ -1277,6 +1291,7 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
// GStreamer plugins.
QStringList gstreamer_plugins = QStringList()
<< "libgstaes.dylib"
<< "libgstaiff.dylib"
<< "libgstapetag.dylib"
<< "libgstapp.dylib"
@@ -1295,13 +1310,16 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
<< "libgstcoreelements.dylib"
<< "libgstdash.dylib"
<< "libgstequalizer.dylib"
<< "libgstflac.dylib"
<< "libgstfaac.dylib"
<< "libgstfaad.dylib"
<< "libgstfdkaac.dylib"
<< "libgstflac.dylib"
<< "libgstgio.dylib"
//<< "libgstgme.dylib"
<< "libgsthls.dylib"
<< "libgsticydemux.dylib"
<< "libgstid3demux.dylib"
<< "libgstid3tag.dylib"
<< "libgstisomp4.dylib"
<< "libgstlame.dylib"
<< "libgstlibav.dylib"
@@ -1322,14 +1340,15 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
<< "libgstspeex.dylib"
<< "libgsttaglib.dylib"
<< "libgsttcp.dylib"
<< "libgsttwolame.dylib"
<< "libgsttypefindfunctions.dylib"
<< "libgstudp.dylib"
<< "libgstvolume.dylib"
<< "libgstvorbis.dylib"
<< "libgstwavenc.dylib"
<< "libgstwavpack.dylib"
<< "libgstwavparse.dylib"
<< "libgstxingmux.dylib"
<< "libgsthls.dylib";
<< "libgstxingmux.dylib";
QString gstreamer_plugins_dir = qgetenv("GST_PLUGIN_PATH");
if (gstreamer_plugins_dir.isEmpty()) {
@@ -1469,9 +1488,9 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
for (const QString &importPath : qmlImportPaths)
argumentList << "-importPath" << importPath;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath);
QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
#else
QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::QmlImportsPath);
#endif
argumentList.append( "-importPath");
argumentList.append(qmlImportsPath);
@@ -1483,7 +1502,7 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
return false;
}
qmlImportScanner.waitForFinished();
qmlImportScanner.waitForFinished(-1);
// log qmlimportscanner errors
qmlImportScanner.setReadChannel(QProcess::StandardError);

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.7)
include(CheckIncludeFiles)
include(CheckFunctionExists)

View File

@@ -1,7 +1,11 @@
cmake_minimum_required(VERSION 3.7)
project(strawberry)
cmake_minimum_required(VERSION 3.0)
cmake_policy(SET CMP0054 NEW)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
cmake_policy(SET CMP0074 NEW)
endif()
include(CheckCXXCompilerFlag)
include(CheckCXXSourceRuns)
@@ -12,13 +16,13 @@ include(cmake/Summary.cmake)
include(cmake/OptionalSource.cmake)
include(cmake/ParseArguments.cmake)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LINUX ON)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
set(FREEBSD ON)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
set(OPENBSD ON)
endif()
@@ -68,23 +72,22 @@ else()
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
$<$<COMPILE_LANGUAGE:CXX>:-Wold-style-cast>
)
endif()
option(BUILD_WERROR "Build with -Werror" OFF)
if(BUILD_WERROR)
list(APPEND COMPILE_OPTIONS -Werror)
option(BUILD_WERROR "Build with -Werror" OFF)
if(BUILD_WERROR)
list(APPEND COMPILE_OPTIONS -Werror)
endif()
endif()
add_compile_options(${COMPILE_OPTIONS})
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
if(CMAKE_BUILD_TYPE MATCHES "Release")
add_definitions(-DNDEBUG)
add_definitions(-DQT_NO_DEBUG_OUTPUT)
endif()
if(APPLE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks")
option(USE_RPATH "Use RPATH" APPLE)
if(USE_RPATH)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()
find_program(CCACHE_EXECUTABLE NAMES ccache)
@@ -94,6 +97,8 @@ if(CCACHE_EXECUTABLE)
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE})
endif()
option(USE_ICU "Use ICU" ON)
find_package(PkgConfig REQUIRED)
find_package(Boost REQUIRED)
find_package(Threads)
@@ -101,7 +106,14 @@ find_package(Backtrace)
if(Backtrace_FOUND)
set(HAVE_BACKTRACE ON)
endif()
find_package(Iconv)
if(USE_ICU)
find_package(ICU COMPONENTS uc i18n REQUIRED)
if(ICU_FOUND)
set(HAVE_ICU ON)
endif()
else()
find_package(Iconv)
endif()
find_package(GnuTLS REQUIRED)
find_package(Protobuf REQUIRED)
if(NOT Protobuf_PROTOC_EXECUTABLE)
@@ -500,9 +512,12 @@ endif()
add_definitions(
-DBOOST_BIND_NO_PLACEHOLDERS
-DQT_STRICT_ITERATORS
-DQT_NO_CAST_FROM_BYTEARRAY
-DQT_USE_QSTRINGBUILDER
-DQT_NO_URL_CAST_FROM_STRING
-DQT_NO_CAST_TO_ASCII
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
-DQT_NO_FOREACH
)
if(WIN32)

View File

@@ -2,10 +2,86 @@ Strawberry Music Player
=======================
ChangeLog
Version 1.0.11 (2022.12.30):
Bugfixes:
* Capitalize GLib application name so it appears nicely in GNOME and PulseAudio Volume Control (#1066).
* Fixed missing application icon for PulseAudio Volume Control (#1066).
* Ignore errors for missing albums when updating Tidal collection if there are results (#1061).
* Only run periodic collection scan when moitoring collection setting is on.
* Fixed an edge case where the context headline text was being cut short (#1067).
* Made "Show in file browser" support SpaceFM filemanager (#1073).
* Fixed incorrect tab order in edit tag dialog (#1075).
* Changed "FMPS_PlayCount" to "FMPS_Playcount" when saving tag (#1074).
* Fixed compilation tag read and write for MP4 (#1076).
* Removed incorrect use of "TPE1" for performer when reading ID3 tags (#1076).
* Disable tag fields for unsupported tags in tag editor.
* Don't allow organizing files without unique tags (track or title) for filename (#1077).
* Don't remove disc from album title when creating cover hash to allow different covers for each disc on an album (#1069).
* Fixed incorrect relative paths for song filenames when saving playlists if the saved playlist location is a symablic link to the song filename (#1071).
* Scrobble "Various Artists" as album artist (#1082).
Enhancements:
* Use system volume instead of own software volume when available (#1037).
* Improved Tidal and Qobuz support with timed requests.
* Support MPRIS2 xesam:userRating.
Version 1.0.10 (2022.10.21):
Bugfixes:
* Fixed "Could not open settings file for writing: No such file or directory" error before settings file is created.
* Fixed visual glitch on currently playing track (#1051).
* Fixed "Unknown error" on Tidal search (#1047).
* Fixed incomplete lyrics from Genius.
* Fixed icons not showing in the file view on some systems (#1024).
* Fixed issues with context and playing widget stopping when using VLC (#1054).
* (macOS) Fixed search field related crash when playlist toolbar is turned off.
Enhancements:
* Fixed narrowing conversions in connects.
* Fixed casts from QByteArray.
* Removed subdir for generated dbus files
* Removed use of fixed font in context (#1040).
* Improve Musixmatch lyrics search.
Version 1.0.9 (2022.09.03):
Bugfixes:
* Fixed parsing album title from radio stream metadata (#1023).
* (macOS) Fixed Strawberry not starting, incorrect rpath for libgcc_s.1.1.dylib (#1025).
* (macOS) Fixed HTTP streaming.
Version 1.0.8 (2022.08.29):
Bugfixes:
* Fixed backslash being appended to filter text when switching playlist (#1005).
* Fixed OSD notifications service registering taking too long to timeout when not available.
* Fixed radio stream added twice when double-clicked (#1015).
* Fixed translating undo and redo buttons (#1017).
Enhancements:
* Use ICU instead of iconv to transliterate characters for filenames.
* Make separating albums by grouping tag optional in collection group by album.
* Added support for video game music formats VGM and SPC.
* Added setting for explicitly turning on HTTP/2 for streaming. Strawberry will set the
libsoup SOUP_FORCE_HTTP1 environment variable when the HTTP/2 is not checked (#1016).
* (Windows|MSVC) Install Visual C++ runtime redistributable automatically in installer.
Version 1.0.7 (2022.07.25)
Bugfixes:
* Fixed checking file extension case-insensitive when loading and saving playlists.
* Fixed reading and saving rating with TagParser.
* (macOS/Windows) Fixed playlist column alignment. Applied patch for Qt bug QTBUG-103576 (#999).
* (Windows|MinGW) Fixed HLS streaming.
* (Windows|MSVC) Fixed MP3 encoding.
Enhancements
* Added option for selecting file extension when saving all playlists.
Version 1.0.6 (2022.07.17)
Bugfixes:
Bugfixes:
* Fixed certain albums not added to playlist in correct track order from search for Tidal and QObuz.
* Fixed songs not added to playlist in numeric order when added from file view with right click (#977).
* Fixed "Stop after this track" graying out next track in dynamic mode (#912).
@@ -198,7 +274,7 @@ Version 0.9.3 (2021.04.18)
Bugfixes:
* Fix "Show in file browser" to work with thunar.
* Check that the clicked rating position is to the right or left of the rectangle.
* Fix rescan when collection directory is removed and readded.
* Fix rescan when collection directory is removed and re-added.
* Create GLib main event loop on non-glib systems to fix stream discoverer.
* (macOS) Fix intermittent abort on startup.
* (macOS) Fix Tidal and Qobuz search field not showing.
@@ -535,7 +611,7 @@ Version 0.6.10 (2020.05.01)
* Made font and font sizes in context configurable.
* Splitting artist and song title to the relevant metadata when artist and song title is sent as title separated by a dash in streams.
* Added label to show collection pixmap disk cache used in settings.
* Icreased default collection pixmap disk cache to 360.
* Increased default collection pixmap disk cache to 360.
New features:
* Added back Tidal streaming support.
@@ -603,7 +679,7 @@ Version 0.6.7 (2019.11.27)
* Fixed "Pressing Previous in player" behaviour setting
* Fixed updating compilations where there are spaces or special characters in filenames
* Fixed cases where songs were stuck in "Various Artists" because not all songs in
the same compilation was removed from the model before readded with actual artist.
the same compilation was removed from the model before re-added with actual artist.
* Fixed a bug when importing playlists where metadata was reset
* Fixed scrobbler to also scrobble songs without album title
* Fixed text for replay gain setting not loading in backend setting
@@ -956,7 +1032,7 @@ Version 0.1.5 (2018.05.16)
Version 0.1.4 (2018.05.14)
* Fixed compliation with clang compiler
* Fixed compilation with clang compiler
* This release is mainly to get it working on openbsd and freebsd.

View File

@@ -1,4 +1,4 @@
:strawberry: Strawberry Music Player [![Build Status](https://github.com/strawberrymusicplayer/strawberry/workflows/C/C++%20CI/badge.svg)](https://github.com/strawberrymusicplayer/strawberry/actions)
:strawberry: Strawberry Music Player [![Build Status](https://github.com/strawberrymusicplayer/strawberry/workflows/build/badge.svg)](https://github.com/strawberrymusicplayer/strawberry/actions)
=======================
[![Sponsor](https://img.shields.io/badge/-Sponsor-green?logo=github)](https://github.com/sponsors/jonaski)
[![Patreon](https://img.shields.io/badge/patreon-donate-green.svg)](https://patreon.com/jonaskvinge)
@@ -23,6 +23,7 @@ Resources:
### :bangbang: Opening an issue:
* Read the FAQ: https://wiki.strawberrymusicplayer.org/wiki/FAQ
* Search for the issue to see if it is already solved, or if there is an open issue for it already. If there is an open issue already, you can comment on it if you have additional information that could be useful to us.
* For technical problems, discussion, questions and feature suggestions use the forum (https://forum.strawberrymusicplayer.org/) instead. The forum is better suited for discussion.
* We do not take feature requests from users on GitHub. Any issues related to feature requests will be closed. This does not necessarily mean that we won't add new features, but we don't have time to take feature requests or answer questions about new features from users. It is still possible to suggest or discuss new features on the forum (https://forum.strawberrymusicplayer.org/).
@@ -68,10 +69,11 @@ It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
To build Strawberry from source you need the following installed on your system with the additional development packages/headers:
* [CMake](https://cmake.org/)
* [GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/) or [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) compiler
* C/C++ compiler ([GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/) or [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/))
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) or [pkgconf](https://github.com/pkgconf/pkgconf)
* [Boost](https://www.boost.org/)
* [GLib](https://developer.gnome.org/glib/)
* [Qt 5.9 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
* [Qt 6 or Qt 5.9 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
* [Protobuf](https://developers.google.com/protocol-buffers/)
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
@@ -79,6 +81,7 @@ To build Strawberry from source you need the following installed on your system
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
* [GnuTLS](https://www.gnutls.org/)
* [TagLib 1.11.1 or higher](https://www.taglib.org/) or [TagParser](https://github.com/Martchus/tagparser)
* [ICU](https://unicode-org.github.io/icu/)
Optional dependencies:
@@ -100,15 +103,18 @@ You should also install the gstreamer plugins base and good, and optionally bad,
### Compile and install:
cd strawberry
mkdir build && cd build
mkdir build
cd build
cmake .. -DBUILD_WITH_QT6=ON
make -j$(nproc)
make -j $(nproc)
sudo make install
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
cmake .. -DBUILD_WITH_QT5=ON
To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/strawberrymusicplayer/strawberry-msvc
### :penguin: Packaging status
[![Packaging status](https://repology.org/badge/vertical-allrepos/strawberry.svg?exclude_unsupported=1)](https://repology.org/metapackage/strawberry/versions)

View File

@@ -1,6 +1,6 @@
set(STRAWBERRY_VERSION_MAJOR 1)
set(STRAWBERRY_VERSION_MINOR 0)
set(STRAWBERRY_VERSION_PATCH 6)
set(STRAWBERRY_VERSION_PATCH 11)
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF)
@@ -14,10 +14,6 @@ set(STRAWBERRY_VERSION_RPM_R "1")
set(STRAWBERRY_VERSION_PAC_V "${majorminorpatch}")
set(STRAWBERRY_VERSION_PAC_R "1")
if(${STRAWBERRY_VERSION_PATCH} EQUAL "0")
set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}")
endif(${STRAWBERRY_VERSION_PATCH} EQUAL "0")
if(STRAWBERRY_VERSION_PRERELEASE)
set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_DISPLAY} ${STRAWBERRY_VERSION_PRERELEASE}")
set(STRAWBERRY_VERSION_RPM_R "0.${STRAWBERRY_VERSION_PRERELEASE}")

1
debian/control.in vendored
View File

@@ -17,6 +17,7 @@ Build-Depends: debhelper (>= 11),
libasound2-dev,
libpulse-dev,
libtag1-dev,
libicu-dev,
@DEBIAN_BUILD_DEPENDS_QT_PACKAGES@,
libgstreamer1.0-dev,
libgstreamer-plugins-base1.0-dev,

View File

@@ -49,6 +49,7 @@ ${TAR} -cJf $name-$version.tar.xz \
--exclude="*.nsi" \
--exclude="*.kdev4" \
--exclude=".vscode" \
--exclude=".idea" \
--exclude="$root/.github" \
--exclude="$root/.travis.yml" \
--exclude="$root/.circleci" \
@@ -56,6 +57,7 @@ ${TAR} -cJf $name-$version.tar.xz \
--exclude="$root/CMakeLists.txt.user" \
--exclude="$root/.clang-format" \
--exclude="$root/build" \
--exclude="$root/cmake-build-debug" \
--exclude="$root/zanata.xml" \
--exclude="$root/.zanata-cache" \
--exclude="$root/debian/changelog" \

View File

@@ -9,6 +9,7 @@ TryExec=strawberry
Icon=strawberry
Terminal=false
Categories=AudioVideo;Player;Qt;Audio;
Keywords=Audio;Player;
StartupNotify=false
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
StartupWMClass=strawberry

View File

@@ -49,6 +49,8 @@ BuildRequires: pkgconfig(sqlite3) >= 3.9
BuildRequires: pkgconfig(taglib)
%endif
BuildRequires: pkgconfig(fftw3)
BuildRequires: pkgconfig(icu-uc)
BuildRequires: pkgconfig(icu-i18n)
%if "@QT_VERSION_MAJOR@" == "5" && ( 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} )
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Core)
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Gui)

View File

@@ -42,6 +42,10 @@
!define release
!endif
!if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
!define release
!endif
!if "@CMAKE_BUILD_TYPE@" == "Debug"
!define debug
!undef build_type
@@ -128,6 +132,9 @@ SetCompressor /SOLID lzma
!insertmacro MUI_PAGE_LICENSE COPYING
Page Custom LockedListPageShow
!insertmacro MUI_PAGE_DIRECTORY
!ifdef msvc
Page Custom InstallMSVCRuntime
!endif
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
@@ -182,6 +189,20 @@ done:
FunctionEnd
!ifdef msvc
!define vc_redist_file "vc_redist.${arch}.exe"
Function InstallMSVCRuntime
${registry::Read} "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${arch}" "Version" $R0 $R1
${If} $R0 == ""
SetDetailsView hide
inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
ExecWait '"$TEMP\${vc_redist_file}" /install /passive'
Delete "$TEMP\${vc_redist_file}"
SetDetailsView show
${EndIf}
FunctionEnd
!endif
Function .onInit
!insertmacro MUI_LANGDLL_DISPLAY
@@ -224,26 +245,28 @@ Section "Strawberry" Strawberry
File "avfilter-8.dll"
File "avformat-59.dll"
File "avutil-57.dll"
File "libFLAC-12.dll"
File "libbrotlicommon.dll"
File "libbrotlidec.dll"
File "libbrotlienc.dll"
File "libbs2b-0.dll"
File "libbz2.dll"
File "libcdio-19.dll"
File "libchromaprint.dll"
File "libdl.dll"
File "libfdk-aac-2.dll"
File "libffi-8.dll"
File "libFLAC-8.dll"
File "libfreetype-6.dll"
File "libfaac-0.dll"
File "libfaad-2.dll"
File "libfdk-aac-2.dll"
File "libffi-8.dll"
File "libfreetype-6.dll"
File "libgcrypt-20.dll"
File "libgio-2.0-0.dll"
File "libglib-2.0-0.dll"
File "libgme.dll"
File "libgmodule-2.0-0.dll"
File "libgmp-10.dll"
File "libgnutls-30.dll"
File "libgobject-2.0-0.dll"
File "libgpg-error-0.dll"
File "libgstadaptivedemux-1.0-0.dll"
File "libgstapp-1.0-0.dll"
File "libgstaudio-1.0-0.dll"
@@ -288,7 +311,8 @@ Section "Strawberry" Strawberry
File "libstdc++-6.dll"
File "libtag.dll"
File "libtasn1-6.dll"
File "libunistring-2.dll"
File "libtwolame-0.dll"
File "libunistring-5.dll"
File "libvorbis-0.dll"
File "libvorbisenc-2.dll"
File "libvorbisfile-3.dll"
@@ -330,10 +354,10 @@ Section "Strawberry" Strawberry
File "libssl-3-x64.dll"
!endif
File "FLAC.dll"
File "avcodec-58.dll"
File "avfilter-7.dll"
File "avformat-58.dll"
File "avresample-4.dll"
File "avutil-56.dll"
File "brotlicommon.dll"
File "brotlidec.dll"
@@ -341,9 +365,9 @@ Section "Strawberry" Strawberry
File "faad.dll"
File "fdk-aac.dll"
File "ffi-7.dll"
File "FLAC.dll"
File "gio-2.0-0.dll"
File "glib-2.0-0.dll"
File "gme.dll"
File "gmodule-2.0-0.dll"
File "gnutls.dll"
File "gobject-2.0-0.dll"
@@ -364,8 +388,9 @@ Section "Strawberry" Strawberry
File "gsttag-1.0-0.dll"
File "gsturidownloader-1.0-0.dll"
File "gstvideo-1.0-0.dll"
File "gstwinrt-1.0-0.dll"
File "harfbuzz.dll"
File "intl-8.dll"
File "jpeg62.dll"
File "libbs2b.dll"
File "libfaac_dll.dll"
File "libiconv.dll"
@@ -375,13 +400,14 @@ Section "Strawberry" Strawberry
File "libspeex.dll"
File "mpcdec.dll"
File "mpg123.dll"
File "nghttp2.dll"
File "ogg.dll"
File "opus.dll"
File "orc-0.4-0.dll"
File "postproc-55.dll"
File "psl-5.dll"
File "qtsparkle-qt6.dll"
File "soup-2.4-1.dll"
File "soup-3.0-0.dll"
File "sqlite3.dll"
File "swresample-3.dll"
File "swscale-5.dll"
@@ -391,19 +417,23 @@ Section "Strawberry" Strawberry
File "wavpackdll.dll"
!ifdef release
File "freetype.dll"
File "libpng16.dll"
File "libprotobuf.dll"
File "libxml2.dll"
File "pcre2-8.dll"
File "pcre2-16.dll"
File "twolame.dll"
File "zlib.dll"
!endif
!ifdef debug
File "freetyped.dll"
File "libpng16d.dll"
File "libprotobufd.dll"
File "libxml2d.dll"
File "pcre2-8d.dll"
File "pcre2-16d.dll"
File "twolamed.dll"
File "zlibd.dll"
!endif
@@ -411,8 +441,11 @@ Section "Strawberry" Strawberry
; Common files
File "icudt72.dll"
File "libfftw3-3.dll"
!ifdef msvc && debug
File "icuin72d.dll"
File "icuuc72d.dll"
File "Qt6Concurrentd.dll"
File "Qt6Cored.dll"
File "Qt6Guid.dll"
@@ -420,6 +453,8 @@ Section "Strawberry" Strawberry
File "Qt6Sqld.dll"
File "Qt6Widgetsd.dll"
!else
File "icuin72.dll"
File "icuuc72.dll"
File "Qt6Concurrent.dll"
File "Qt6Core.dll"
File "Qt6Gui.dll"
@@ -542,7 +577,6 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstaudiotestsrc.dll" "gstreamer-plugins\libgstaudiotestsrc.dll"
File "/oname=libgstautodetect.dll" "gstreamer-plugins\libgstautodetect.dll"
File "/oname=libgstbs2b.dll" "gstreamer-plugins\libgstbs2b.dll"
File "/oname=libgstcdio.dll" "gstreamer-plugins\libgstcdio.dll"
File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll"
File "/oname=libgstdash.dll" "gstreamer-plugins\libgstdash.dll"
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
@@ -552,9 +586,11 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstfdkaac.dll" "gstreamer-plugins\libgstfdkaac.dll"
File "/oname=libgstflac.dll" "gstreamer-plugins\libgstflac.dll"
File "/oname=libgstgio.dll" "gstreamer-plugins\libgstgio.dll"
File "/oname=libgstgme.dll" "gstreamer-plugins\libgstgme.dll"
File "/oname=libgsthls.dll" "gstreamer-plugins\libgsthls.dll"
File "/oname=libgsticydemux.dll" "gstreamer-plugins\libgsticydemux.dll"
File "/oname=libgstid3demux.dll" "gstreamer-plugins\libgstid3demux.dll"
File "/oname=libgstid3tag.dll" "gstreamer-plugins\libgstid3tag.dll"
File "/oname=libgstisomp4.dll" "gstreamer-plugins\libgstisomp4.dll"
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
File "/oname=libgstlibav.dll" "gstreamer-plugins\libgstlibav.dll"
@@ -574,17 +610,20 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstspeex.dll" "gstreamer-plugins\libgstspeex.dll"
File "/oname=libgsttaglib.dll" "gstreamer-plugins\libgsttaglib.dll"
File "/oname=libgsttcp.dll" "gstreamer-plugins\libgsttcp.dll"
File "/oname=libgsttwolame.dll" "gstreamer-plugins\libgsttwolame.dll"
File "/oname=libgsttypefindfunctions.dll" "gstreamer-plugins\libgsttypefindfunctions.dll"
File "/oname=libgstudp.dll" "gstreamer-plugins\libgstudp.dll"
File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll"
File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll"
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
File "/oname=libgstwavenc.dll" "gstreamer-plugins\libgstwavenc.dll"
File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
File "/oname=libgstxingmux.dll" "gstreamer-plugins\libgstxingmux.dll"
!endif ; MinGW
!ifdef msvc
File "/oname=gstaes.dll" "gstreamer-plugins\gstaes.dll"
File "/oname=gstaiff.dll" "gstreamer-plugins\gstaiff.dll"
File "/oname=gstapetag.dll" "gstreamer-plugins\gstapetag.dll"
File "/oname=gstapp.dll" "gstreamer-plugins\gstapp.dll"
@@ -608,9 +647,11 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=gstfdkaac.dll" "gstreamer-plugins\gstfdkaac.dll"
File "/oname=gstflac.dll" "gstreamer-plugins\gstflac.dll"
File "/oname=gstgio.dll" "gstreamer-plugins\gstgio.dll"
File "/oname=gstgme.dll" "gstreamer-plugins\gstgme.dll"
File "/oname=gsthls.dll" "gstreamer-plugins\gsthls.dll"
File "/oname=gsticydemux.dll" "gstreamer-plugins\gsticydemux.dll"
File "/oname=gstid3demux.dll" "gstreamer-plugins\gstid3demux.dll"
File "/oname=gstid3tag.dll" "gstreamer-plugins\gstid3tag.dll"
File "/oname=gstisomp4.dll" "gstreamer-plugins\gstisomp4.dll"
File "/oname=gstlame.dll" "gstreamer-plugins\gstlame.dll"
File "/oname=gstlibav.dll" "gstreamer-plugins\gstlibav.dll"
@@ -620,20 +661,23 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=gstopenmpt.dll" "gstreamer-plugins\gstopenmpt.dll"
File "/oname=gstopus.dll" "gstreamer-plugins\gstopus.dll"
File "/oname=gstopusparse.dll" "gstreamer-plugins\gstopusparse.dll"
File "/oname=gstpbtypes.dll" "gstreamer-plugins\gstpbtypes.dll"
File "/oname=gstplayback.dll" "gstreamer-plugins\gstplayback.dll"
File "/oname=gstreplaygain.dll" "gstreamer-plugins\gstreplaygain.dll"
File "/oname=gstrtp.dll" "gstreamer-plugins\gstrtp.dll"
File "/oname=gstrtsp.dll" "gstreamer-plugins\gstrtsp.dll"
File "/oname=gstspeex.dll" "gstreamer-plugins\gstspeex.dll"
File "/oname=gstsoup.dll" "gstreamer-plugins\gstsoup.dll"
File "/oname=gstspectrum.dll" "gstreamer-plugins\gstspectrum.dll"
File "/oname=gstspeex.dll" "gstreamer-plugins\gstspeex.dll"
File "/oname=gsttaglib.dll" "gstreamer-plugins\gsttaglib.dll"
File "/oname=gsttcp.dll" "gstreamer-plugins\gsttcp.dll"
File "/oname=gsttwolame.dll" "gstreamer-plugins\gsttwolame.dll"
File "/oname=gsttypefindfunctions.dll" "gstreamer-plugins\gsttypefindfunctions.dll"
File "/oname=gstudp.dll" "gstreamer-plugins\gstudp.dll"
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.dll"
File "/oname=gstwasapi.dll" "gstreamer-plugins\gstwasapi.dll"
;File "/oname=gstwasapi2.dll" "gstreamer-plugins\gstwasapi2.dll"
File "/oname=gstwavenc.dll" "gstreamer-plugins\gstwavenc.dll"
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
File "/oname=gstxingmux.dll" "gstreamer-plugins\gstxingmux.dll"
@@ -703,26 +747,28 @@ Section "Uninstall"
Delete "$INSTDIR\avfilter-8.dll"
Delete "$INSTDIR\avformat-59.dll"
Delete "$INSTDIR\avutil-57.dll"
Delete "$INSTDIR\libFLAC-12.dll"
Delete "$INSTDIR\libbrotlicommon.dll"
Delete "$INSTDIR\libbrotlidec.dll"
Delete "$INSTDIR\libbrotlienc.dll"
Delete "$INSTDIR\libbs2b-0.dll"
Delete "$INSTDIR\libbz2.dll"
Delete "$INSTDIR\libcdio-19.dll"
Delete "$INSTDIR\libchromaprint.dll"
Delete "$INSTDIR\libdl.dll"
Delete "$INSTDIR\libfdk-aac-2.dll"
Delete "$INSTDIR\libffi-8.dll"
Delete "$INSTDIR\libFLAC-8.dll"
Delete "$INSTDIR\libfreetype-6.dll"
Delete "$INSTDIR\libfaac-0.dll"
Delete "$INSTDIR\libfaad-2.dll"
Delete "$INSTDIR\libfdk-aac-2.dll"
Delete "$INSTDIR\libffi-8.dll"
Delete "$INSTDIR\libfreetype-6.dll"
Delete "$INSTDIR\libgcrypt-20.dll"
Delete "$INSTDIR\libgio-2.0-0.dll"
Delete "$INSTDIR\libglib-2.0-0.dll"
Delete "$INSTDIR\libgme.dll"
Delete "$INSTDIR\libgmodule-2.0-0.dll"
Delete "$INSTDIR\libgmp-10.dll"
Delete "$INSTDIR\libgnutls-30.dll"
Delete "$INSTDIR\libgobject-2.0-0.dll"
Delete "$INSTDIR\libgpg-error-0.dll"
Delete "$INSTDIR\libgstadaptivedemux-1.0-0.dll"
Delete "$INSTDIR\libgstapp-1.0-0.dll"
Delete "$INSTDIR\libgstaudio-1.0-0.dll"
@@ -767,7 +813,8 @@ Section "Uninstall"
Delete "$INSTDIR\libstdc++-6.dll"
Delete "$INSTDIR\libtag.dll"
Delete "$INSTDIR\libtasn1-6.dll"
Delete "$INSTDIR\libunistring-2.dll"
Delete "$INSTDIR\libtwolame-0.dll"
Delete "$INSTDIR\libunistring-5.dll"
Delete "$INSTDIR\libvorbis-0.dll"
Delete "$INSTDIR\libvorbisenc-2.dll"
Delete "$INSTDIR\libvorbisfile-3.dll"
@@ -809,10 +856,10 @@ Section "Uninstall"
Delete "$INSTDIR\libssl-3-x64.dll"
!endif
Delete "$INSTDIR\FLAC.dll"
Delete "$INSTDIR\avcodec-58.dll"
Delete "$INSTDIR\avfilter-7.dll"
Delete "$INSTDIR\avformat-58.dll"
Delete "$INSTDIR\avresample-4.dll"
Delete "$INSTDIR\avutil-56.dll"
Delete "$INSTDIR\brotlicommon.dll"
Delete "$INSTDIR\brotlidec.dll"
@@ -820,9 +867,9 @@ Section "Uninstall"
Delete "$INSTDIR\faad.dll"
Delete "$INSTDIR\fdk-aac.dll"
Delete "$INSTDIR\ffi-7.dll"
Delete "$INSTDIR\FLAC.dll"
Delete "$INSTDIR\gio-2.0-0.dll"
Delete "$INSTDIR\glib-2.0-0.dll"
Delete "$INSTDIR\gme.dll"
Delete "$INSTDIR\gmodule-2.0-0.dll"
Delete "$INSTDIR\gnutls.dll"
Delete "$INSTDIR\gobject-2.0-0.dll"
@@ -843,8 +890,9 @@ Section "Uninstall"
Delete "$INSTDIR\gsttag-1.0-0.dll"
Delete "$INSTDIR\gsturidownloader-1.0-0.dll"
Delete "$INSTDIR\gstvideo-1.0-0.dll"
Delete "$INSTDIR\gstwinrt-1.0-0.dll"
Delete "$INSTDIR\harfbuzz.dll"
Delete "$INSTDIR\intl-8.dll"
Delete "$INSTDIR\jpeg62.dll"
Delete "$INSTDIR\libbs2b.dll"
Delete "$INSTDIR\libfaac_dll.dll"
Delete "$INSTDIR\libiconv.dll"
@@ -854,13 +902,14 @@ Section "Uninstall"
Delete "$INSTDIR\libspeex.dll"
Delete "$INSTDIR\mpcdec.dll"
Delete "$INSTDIR\mpg123.dll"
Delete "$INSTDIR\nghttp2.dll"
Delete "$INSTDIR\ogg.dll"
Delete "$INSTDIR\opus.dll"
Delete "$INSTDIR\orc-0.4-0.dll"
Delete "$INSTDIR\postproc-55.dll"
Delete "$INSTDIR\psl-5.dll"
Delete "$INSTDIR\qtsparkle-qt6.dll"
Delete "$INSTDIR\soup-2.4-1.dll"
Delete "$INSTDIR\soup-3.0-0.dll"
Delete "$INSTDIR\sqlite3.dll"
Delete "$INSTDIR\swresample-3.dll"
Delete "$INSTDIR\swscale-5.dll"
@@ -870,19 +919,23 @@ Section "Uninstall"
Delete "$INSTDIR\wavpackdll.dll"
!ifdef release
Delete "$INSTDIR\freetype.dll"
Delete "$INSTDIR\libpng16.dll"
Delete "$INSTDIR\libprotobuf.dll"
Delete "$INSTDIR\libxml2.dll"
Delete "$INSTDIR\pcre2-8.dll"
Delete "$INSTDIR\pcre2-16.dll"
Delete "$INSTDIR\twolame.dll"
Delete "$INSTDIR\zlib.dll"
!endif
!ifdef debug
Delete "$INSTDIR\freetyped.dll"
Delete "$INSTDIR\libpng16d.dll"
Delete "$INSTDIR\libprotobufd.dll"
Delete "$INSTDIR\libxml2d.dll"
Delete "$INSTDIR\pcre2-8d.dll"
Delete "$INSTDIR\pcre2-16d.dll"
Delete "$INSTDIR\twolamed.dll"
Delete "$INSTDIR\zlibd.dll"
!endif
@@ -890,8 +943,11 @@ Section "Uninstall"
; Common files
Delete "$INSTDIR\icudt72.dll"
Delete "$INSTDIR\libfftw3-3.dll"
!ifdef msvc && debug
Delete "$INSTDIR\icuin72d.dll"
Delete "$INSTDIR\icuuc72d.dll"
Delete "$INSTDIR\Qt6Concurrentd.dll"
Delete "$INSTDIR\Qt6Cored.dll"
Delete "$INSTDIR\Qt6Guid.dll"
@@ -899,6 +955,8 @@ Section "Uninstall"
Delete "$INSTDIR\Qt6Sqld.dll"
Delete "$INSTDIR\Qt6Widgetsd.dll"
!else
Delete "$INSTDIR\icuin72.dll"
Delete "$INSTDIR\icuuc72.dll"
Delete "$INSTDIR\Qt6Concurrent.dll"
Delete "$INSTDIR\Qt6Core.dll"
Delete "$INSTDIR\Qt6Gui.dll"
@@ -954,7 +1012,6 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiotestsrc.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstautodetect.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstbs2b.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstcdio.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdash.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
@@ -964,9 +1021,11 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstfdkaac.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstflac.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstgio.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstgme.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsthls.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsticydemux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstid3demux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstid3tag.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstisomp4.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstlibav.dll"
@@ -986,11 +1045,13 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\libgstspeex.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsttaglib.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsttcp.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsttwolame.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsttypefindfunctions.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstudp.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavenc.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstxingmux.dll"
@@ -999,6 +1060,7 @@ Section "Uninstall"
; MSVC GStreamer plugins
!ifdef msvc
Delete "$INSTDIR\gstreamer-plugins\gstaes.dll"
Delete "$INSTDIR\gstreamer-plugins\gstaiff.dll"
Delete "$INSTDIR\gstreamer-plugins\gstapetag.dll"
Delete "$INSTDIR\gstreamer-plugins\gstapp.dll"
@@ -1022,9 +1084,11 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\gstfdkaac.dll"
Delete "$INSTDIR\gstreamer-plugins\gstflac.dll"
Delete "$INSTDIR\gstreamer-plugins\gstgio.dll"
Delete "$INSTDIR\gstreamer-plugins\gstgme.dll"
Delete "$INSTDIR\gstreamer-plugins\gsthls.dll"
Delete "$INSTDIR\gstreamer-plugins\gsticydemux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstid3demux.dll"
Delete "$INSTDIR\gstreamer-plugins\gstid3tag.dll"
Delete "$INSTDIR\gstreamer-plugins\gstisomp4.dll"
Delete "$INSTDIR\gstreamer-plugins\gstlame.dll"
Delete "$INSTDIR\gstreamer-plugins\gstlibav.dll"
@@ -1034,20 +1098,23 @@ Section "Uninstall"
Delete "$INSTDIR\gstreamer-plugins\gstopenmpt.dll"
Delete "$INSTDIR\gstreamer-plugins\gstopus.dll"
Delete "$INSTDIR\gstreamer-plugins\gstopusparse.dll"
Delete "$INSTDIR\gstreamer-plugins\gstpbtypes.dll"
Delete "$INSTDIR\gstreamer-plugins\gstplayback.dll"
Delete "$INSTDIR\gstreamer-plugins\gstreplaygain.dll"
Delete "$INSTDIR\gstreamer-plugins\gstrtp.dll"
Delete "$INSTDIR\gstreamer-plugins\gstrtsp.dll"
Delete "$INSTDIR\gstreamer-plugins\gstspeex.dll"
Delete "$INSTDIR\gstreamer-plugins\gstsoup.dll"
Delete "$INSTDIR\gstreamer-plugins\gstspectrum.dll"
Delete "$INSTDIR\gstreamer-plugins\gstspeex.dll"
Delete "$INSTDIR\gstreamer-plugins\gsttaglib.dll"
Delete "$INSTDIR\gstreamer-plugins\gsttcp.dll"
Delete "$INSTDIR\gstreamer-plugins\gsttwolame.dll"
Delete "$INSTDIR\gstreamer-plugins\gsttypefindfunctions.dll"
Delete "$INSTDIR\gstreamer-plugins\gstudp.dll"
Delete "$INSTDIR\gstreamer-plugins\gstvolume.dll"
Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwasapi2.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
Delete "$INSTDIR\gstreamer-plugins\gstxingmux.dll"

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.7)
set(SOURCES gstfastspectrum.cpp gstmoodbarplugin.cpp)

View File

@@ -47,7 +47,7 @@ class QMutex;
typedef void (*GstFastSpectrumInputData)(const guint8 *in, double *out, guint len, double max_value, guint op, guint nfft);
typedef std::function<void(double *magnitudes, int size)> OutputCallback;
using OutputCallback = std::function<void(double *magnitudes, int size)>;
struct GstFastSpectrum {
GstAudioFilter parent;

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.7)
set(SOURCES
core/logging.cpp

View File

@@ -126,7 +126,8 @@ class LoggedDebug : public DebugBase<LoggedDebug> {
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
if (message.startsWith(kMessageHandlerMagic)) {
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message.toUtf8().data() + kMessageHandlerMagicLength);
QByteArray message_data = message.toUtf8();
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLength);
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
return;
}

View File

@@ -35,9 +35,6 @@
class QIODevice;
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
// Reads and writes uint32 length encoded protobufs to a socket.
// This base QObject is separate from AbstractMessageHandler because moc can't handle templated classes.
// Use AbstractMessageHandler instead.
@@ -85,8 +82,8 @@ class AbstractMessageHandler : public _MessageHandlerBase {
AbstractMessageHandler(QIODevice *device, QObject *parent);
~AbstractMessageHandler() override { AbstractMessageHandler::AbortAll(); }
typedef MT MessageType;
typedef MessageReply<MT> ReplyType;
using MessageType = MT;
using ReplyType = MessageReply<MT>;
// Serialises the message and writes it to the socket.
// This version MUST be called from the thread in which the AbstractMessageHandler was created.

View File

@@ -76,8 +76,8 @@ class WorkerPool : public _WorkerPoolBase {
explicit WorkerPool(QObject *parent = nullptr);
~WorkerPool() override;
typedef typename HandlerType::MessageType MessageType;
typedef typename HandlerType::ReplyType ReplyType;
using MessageType = typename HandlerType::MessageType;
using ReplyType = typename HandlerType::ReplyType;
// Sets the name of the worker executable. This is looked for first in the current directory, and then in $PATH.
// You must call this before calling Start().

View File

@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.7)
set(MESSAGES tagreadermessages.proto)
set(SOURCES tagreaderbase.cpp)
if(USE_TAGLIB AND TAGLIB_FOUND)
list(APPEND SOURCES tagreadertaglib.cpp)
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
endif()
if(USE_TAGPARSER AND TAGPARSER_FOUND)

View File

@@ -25,3 +25,33 @@ const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
TagReaderBase::TagReaderBase() = default;
TagReaderBase::~TagReaderBase() = default;
void TagReaderBase::Decode(const QString &tag, std::string *output) {
output->assign(DataCommaSizeFromQString(tag));
}
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
if (POPM_rating < 0x01) return 0.0F;
else if (POPM_rating < 0x40) return 0.20F;
else if (POPM_rating < 0x80) return 0.40F;
else if (POPM_rating < 0xC0) return 0.60F;
else if (POPM_rating < 0xFC) return 0.80F;
return 1.0F;
}
int TagReaderBase::ConvertToPOPMRating(const float rating) {
if (rating < 0.20) return 0x00;
else if (rating < 0.40) return 0x01;
else if (rating < 0.60) return 0x40;
else if (rating < 0.80) return 0x80;
else if (rating < 1.0) return 0xC0;
return 0xFF;
}

View File

@@ -27,6 +27,9 @@
#include "tagreadermessages.pb.h"
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
/*
* This class holds all useful methods to read and write tags from/to files.
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
@@ -38,7 +41,7 @@ class TagReaderBase {
virtual bool IsMediaFile(const QString &filename) const = 0;
virtual void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
virtual bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
@@ -47,6 +50,11 @@ class TagReaderBase {
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
static void Decode(const QString &tag, std::string *output);
static float ConvertPOPMRating(const int POPM_rating);
static int ConvertToPOPMRating(const float rating);
protected:
static const std::string kEmbeddedCover;

View File

@@ -0,0 +1,299 @@
/*
* Strawberry Music Player
* Copyright 2022, Eoin O'Neill <eoinoneill1991@gmail.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/>.
*
*/
#include "tagreadergme.h"
#include <tag.h>
#include <apefile.h>
#include <QByteArray>
#include <QString>
#include <QChar>
#include <QFileInfo>
#include <QFile>
#include <QTextStream>
#include "utilities/timeconstants.h"
#include "core/logging.h"
#include "core/messagehandler.h"
#include "tagreaderbase.h"
#include "tagreadertaglib.h"
bool GME::IsSupportedFormat(const QFileInfo &file_info) {
return file_info.exists() && (file_info.completeSuffix().endsWith("spc", Qt::CaseInsensitive) || file_info.completeSuffix().endsWith("vgm"), Qt::CaseInsensitive);
}
bool GME::ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
if (file_info.completeSuffix().endsWith("spc"), Qt::CaseInsensitive) {
SPC::Read(file_info, song_info);
return true;
}
if (file_info.completeSuffix().endsWith("vgm", Qt::CaseInsensitive)) {
VGM::Read(file_info, song_info);
return true;
}
return false;
}
quint32 GME::UnpackBytes32(const char *const bytes, size_t length) {
Q_ASSERT(length <= 4 && length > 0);
quint32 value = 0;
for (size_t i = 0; i < length; i++) {
value |= static_cast<unsigned char>(bytes[i]) << (8 * i);
}
return value;
}
void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
QFile file(file_info.filePath());
if (!file.open(QIODevice::ReadOnly)) return;
qLog(Debug) << "Reading tags from SPC file" << file_info.fileName();
// Check for header -- more reliable than file name alone.
if (!file.read(33).startsWith(QString("SNES-SPC700").toLatin1())) return;
// First order of business -- get any tag values that exist within the core file information.
// These only allow for a certain number of bytes per field,
// so they will likely be overwritten either by the id666 standard or the APETAG format (as used by other players, such as foobar and winamp)
//
// Make sure to check id6 documentation before changing the read values!
file.seek(HAS_ID6_OFFSET);
bool has_id6 = (file.read(1)[0] == static_cast<char>(xID6_STATUS::ON));
file.seek(SONG_TITLE_OFFSET);
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
file.seek(GAME_TITLE_OFFSET);
song_info->set_album(QString::fromLatin1(file.read(32)).toStdString());
file.seek(ARTIST_OFFSET);
song_info->set_artist(QString::fromLatin1(file.read(32)).toStdString());
file.seek(INTRO_LENGTH_OFFSET);
QByteArray length_bytes = file.read(INTRO_LENGTH_SIZE);
quint64 length_in_sec = 0;
if (length_bytes.size() >= INTRO_LENGTH_SIZE) {
length_in_sec = ConvertSPCStringToNum(length_bytes);
if (!length_in_sec || length_in_sec >= 0x1FFF) {
// This means that parsing the length as a string failed, so get value LE.
length_in_sec = length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16);
}
if (length_in_sec < 0x1FFF) {
song_info->set_length_nanosec(length_in_sec * kNsecPerSec);
}
}
file.seek(FADE_LENGTH_OFFSET);
QByteArray fade_bytes = file.read(FADE_LENGTH_SIZE);
if (fade_bytes.size() >= FADE_LENGTH_SIZE) {
quint64 fade_length_in_ms = ConvertSPCStringToNum(fade_bytes);
if (fade_length_in_ms > 0x7FFF) {
fade_length_in_ms = fade_bytes[0] | (fade_bytes[1] << 8) | (fade_bytes[2] << 16) | (fade_bytes[3] << 24);
}
}
// Check for XID6 data -- this is infrequently used, but being able to fill in data from this is ideal before trying to rely on APETAG values.
// XID6 format follows EA's binary file format standard named "IFF"
file.seek(XID6_OFFSET);
if (has_id6 && file.read(4) == QString("xid6").toLatin1()) {
QByteArray xid6_head_data = file.read(4);
if (xid6_head_data.size() >= 4) {
qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3];
// This should be the size remaining for entire ID6 block, but it seems that most files treat this as the size of the remaining header space...
qLog(Debug) << file_info.fileName() << "has ID6 tag.";
while ((file.pos()) + 4 < XID6_OFFSET + xid6_size) {
QByteArray arr = file.read(4);
if (arr.size() < 4) break;
qint8 id = arr[0];
qint8 type = arr[1];
Q_UNUSED(id);
Q_UNUSED(type);
qint16 length = arr[2] | (arr[3] << 8);
file.read(GetNextMemAddressAlign32bit(length));
}
}
}
// Music Players that support SPC tend to support additional tagging data as
// an APETAG entry at the bottom of the file instead of writing into the xid6 tagging space.
// This is where a lot of the extra data for a file is stored, such as genre or replaygain data.
// This data is currently supported by TagLib, so we will simply use that for the remaining values.
TagLib::APE::File ape(file_info.filePath().toStdString().data());
if (ape.hasAPETag()) {
TagLib::Tag *tag = ape.tag();
if (!tag) return;
TagReaderTagLib::Decode(tag->artist(), song_info->mutable_artist());
TagReaderTagLib::Decode(tag->album(), song_info->mutable_album());
TagReaderTagLib::Decode(tag->title(), song_info->mutable_title());
TagReaderTagLib::Decode(tag->genre(), song_info->mutable_genre());
song_info->set_track(tag->track());
song_info->set_year(tag->year());
}
song_info->set_valid(true);
song_info->set_filetype(spb::tagreader::SongMetadata_FileType_SPC);
}
qint16 GME::SPC::GetNextMemAddressAlign32bit(qint16 input) {
return ((input + 0x3) & ~0x3);
// Plus 0x3 for rounding up (not down), AND NOT to flatten out on a 32 bit level.
}
quint64 GME::SPC::ConvertSPCStringToNum(const QByteArray &arr) {
quint64 result = 0;
for (auto it = arr.begin(); it != arr.end(); it++) {
unsigned int num = *it - '0';
if (num > 9) break;
result = (result * 10) + num; // Shift Left and add.
}
return result;
}
void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
QFile file(file_info.filePath());
if (!file.open(QIODevice::ReadOnly)) return;
qLog(Debug) << "Reading tags from VGM file" << file_info.fileName();
if (!file.read(4).startsWith(QString("Vgm ").toLatin1())) return;
file.seek(GD3_TAG_PTR);
QByteArray gd3_head = file.read(4);
if (gd3_head.size() < 4) return;
quint64 pt = GME::UnpackBytes32(gd3_head.constData(), gd3_head.size());
file.seek(SAMPLE_COUNT);
QByteArray sample_count_bytes = file.read(4);
file.seek(LOOP_SAMPLE_COUNT);
QByteArray loop_count_bytes = file.read(4);
quint64 length = 0;
if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) return;
file.seek(GD3_TAG_PTR + pt);
QByteArray gd3_version = file.read(4);
file.seek(file.pos() + 4);
QByteArray gd3_length_bytes = file.read(4);
quint32 gd3_length = GME::UnpackBytes32(gd3_length_bytes.constData(), gd3_length_bytes.size());
QByteArray gd3Data = file.read(gd3_length);
QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly);
// Stored as 16 bit UTF string, two bytes per letter.
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
fileTagStream.setEncoding(QStringConverter::Utf16);
#else
fileTagStream.setCodec("UTF-16");
#endif
QStringList strings = fileTagStream.readLine(0).split(QChar('\0'));
if (strings.count() < 10) return;
// VGM standard dictates string tag data exist in specific order.
// Order alternates between English and Japanese version of data.
// Read GD3 tag standard for more details.
song_info->set_title(strings[0].toStdString());
song_info->set_album(strings[2].toStdString());
song_info->set_artist(strings[6].toStdString());
song_info->set_year(strings[8].left(4).toInt());
song_info->set_length_nanosec(length * kNsecPerMsec);
song_info->set_valid(true);
song_info->set_filetype(spb::tagreader::SongMetadata_FileType_VGM);
}
bool GME::VGM::GetPlaybackLength(const QByteArray &sample_count_bytes, const QByteArray &loop_count_bytes, quint64 &out_length) {
if (sample_count_bytes.size() != 4) return false;
if (loop_count_bytes.size() != 4) return false;
quint64 sample_count = GME::UnpackBytes32(sample_count_bytes.constData(), sample_count_bytes.size());
if (sample_count <= 0) return false;
quint64 loop_sample_count = GME::UnpackBytes32(loop_count_bytes.constData(), loop_count_bytes.size());
if (loop_sample_count <= 0) {
out_length = sample_count * 1000 / SAMPLE_TIMEBASE;
return true;
}
quint64 intro_length_ms = (sample_count - loop_sample_count) * 1000 / SAMPLE_TIMEBASE;
quint64 loop_length_ms = (loop_sample_count) * 1000 / SAMPLE_TIMEBASE;
out_length = intro_length_ms + (loop_length_ms * 2) + GST_GME_LOOP_TIME_MS;
return true;
}
TagReaderGME::TagReaderGME() = default;
TagReaderGME::~TagReaderGME() = default;
bool TagReaderGME::IsMediaFile(const QString &filename) const {
QFileInfo fileinfo(filename);
return GME::IsSupportedFormat(fileinfo);
}
bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
QFileInfo fileinfo(filename);
return GME::ReadFile(fileinfo, song);
}
bool TagReaderGME::SaveFile(const QString&, const spb::tagreader::SongMetadata&) const {
return false;
}
QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const {
return QByteArray();
}
bool TagReaderGME::SaveEmbeddedArt(const QString&, const QByteArray&) {
return false;
}
bool TagReaderGME::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const {
return false;
}
bool TagReaderGME::SaveSongRatingToFile(const QString&, const spb::tagreader::SongMetadata&) const {
return false;
}

View File

@@ -0,0 +1,111 @@
/*
* Strawberry Music Player
* Copyright 2022, Eoin O'Neill <eoinoneill1991@gmail.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 TAGREADERGME_H
#define TAGREADERGME_H
#include <taglib/tstring.h>
#include <QByteArray>
#include <QString>
#include <QFileInfo>
#include "tagreaderbase.h"
#include "tagreadermessages.pb.h"
namespace GME {
bool IsSupportedFormat(const QFileInfo &file_info);
bool ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
uint32_t UnpackBytes32(const char *const arr, size_t length);
namespace SPC {
// SPC SPEC: http://vspcplay.raphnet.net/spc_file_format.txt
constexpr int HAS_ID6_OFFSET = 0x23;
constexpr int SONG_TITLE_OFFSET = 0x2E;
constexpr int GAME_TITLE_OFFSET = 0x4E;
constexpr int DUMPER_OFFSET = 0x6E;
constexpr int COMMENTS_OFFSET = 0x7E;
// It seems that intro length and fade length are inconsistent from file to file.
// It should be looked into within the GME source code to see how GStreamer gets its values for playback length.
constexpr int INTRO_LENGTH_OFFSET = 0xA9;
constexpr int INTRO_LENGTH_SIZE = 3;
constexpr int FADE_LENGTH_OFFSET = 0xAC;
constexpr int FADE_LENGTH_SIZE = 4;
constexpr int ARTIST_OFFSET = 0xB1;
constexpr int XID6_OFFSET = (0x101C0 + 64);
constexpr int NANO_PER_MS = 1000000;
enum xID6_STATUS {
ON = 0x26,
OFF = 0x27,
};
enum xID6_ID { SongName = 0x01, GameName = 0x02, ArtistName = 0x03 };
enum xID6_TYPE { Length = 0x0, String = 0x1, Integer = 0x4 };
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
qint16 GetNextMemAddressAlign32bit(qint16 input);
quint64 ConvertSPCStringToNum(const QByteArray &arr);
} // namespace SPC
namespace VGM {
// VGM SPEC:
// http://www.smspower.org/uploads/Music/vgmspec170.txt?sid=17c810c54633b6dd4982f92f718361c1
// GD3 TAG SPEC:
// http://www.smspower.org/uploads/Music/gd3spec100.txt
constexpr int GD3_TAG_PTR = 0x14;
constexpr int SAMPLE_COUNT = 0x18;
constexpr int LOOP_SAMPLE_COUNT = 0x20;
constexpr int SAMPLE_TIMEBASE = 44100;
constexpr int GST_GME_LOOP_TIME_MS = 8000;
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
// Takes in two QByteArrays, expected to be 4 bytes long. Desired length is returned via output parameter out_length. Returns false on error.
bool GetPlaybackLength(const QByteArray &sample_count_bytes, const QByteArray &loop_count_bytes, quint64 &out_length);
} // namespace VGM
} // namespace GME
// TagReaderGME
// Fulfills Strawberry's Intended interface for tag reading.
class TagReaderGME : public TagReaderBase {
public:
explicit TagReaderGME();
~TagReaderGME();
bool IsMediaFile(const QString &filename) const override;
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
QByteArray LoadEmbeddedArt(const QString &filename) const override;
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
};
#endif

View File

@@ -27,6 +27,8 @@ message SongMetadata {
S3M = 19;
XM = 20;
IT = 21;
SPC = 22;
VGM = 23;
CDDA = 90;
STREAM = 91;
}

View File

@@ -92,7 +92,7 @@
#include "core/logging.h"
#include "core/messagehandler.h"
#include "core/timeconstants.h"
#include "utilities/timeconstants.h"
class FileRefFactory {
public:
@@ -186,7 +186,7 @@ spb::tagreader::SongMetadata_FileType TagReaderTagLib::GuessFileType(TagLib::Fil
}
void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
const QFileInfo fileinfo(filename);
@@ -212,7 +212,7 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (fileref->isNull()) {
qLog(Info) << "TagLib hasn't been able to read" << filename << "file";
return;
return false;
}
song->set_filetype(GuessFileType(fileref.get()));
@@ -254,9 +254,7 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
}
if (TagLib::FLAC::File *file_flac = dynamic_cast<TagLib::FLAC::File *>(fileref->file())) {
song->set_bitdepth(file_flac->audioProperties()->bitsPerSample());
if (file_flac->xiphComment()) {
ParseOggTag(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
TagLib::List<TagLib::FLAC::Picture*> pictures = file_flac->pictureList();
@@ -299,15 +297,11 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
// content group
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(), song->mutable_performer());
// original artist/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(), song->mutable_albumartist());
@@ -402,6 +396,10 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
song->set_originalyear(TStringToQString(mp4_tag->item(kMP4_OriginalYear_ID).toStringList().toString('\n')).left(4).toInt());
}
if (mp4_tag->item("cpil").isValid()) {
song->set_compilation(mp4_tag->item("cpil").toBool());
}
{
TagLib::MP4::Item item = mp4_tag->item(kMP4_FMPS_Playcount_ID);
if (item.isValid()) {
@@ -517,6 +515,8 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (song->bitrate() <= 0) { song->set_bitrate(-1); }
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
return song->filetype() != spb::tagreader::SongMetadata_FileType_UNKNOWN;
}
void TagReaderTagLib::Decode(const TagLib::String &tag, std::string *output) {
@@ -526,12 +526,6 @@ void TagReaderTagLib::Decode(const TagLib::String &tag, std::string *output) {
}
void TagReaderTagLib::Decode(const QString &tag, std::string *output) {
output->assign(DataCommaSizeFromQString(tag));
}
void TagReaderTagLib::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const {
if (!map["COMPOSER"].isEmpty()) Decode(map["COMPOSER"].front(), song->mutable_composer());
@@ -693,7 +687,7 @@ bool TagReaderTagLib::SaveFile(const QString &filename, const spb::tagreader::So
tag->setItem("\251grp", TagLib::StringList(TagLib::String(song.grouping(), TagLib::String::UTF8)));
tag->setItem("\251lyr", TagLib::StringList(TagLib::String(song.lyrics(), TagLib::String::UTF8)));
tag->setItem("aART", TagLib::StringList(TagLib::String(song.albumartist(), TagLib::String::UTF8)));
tag->setItem("cpil", TagLib::StringList(song.compilation() ? "1" : "0"));
tag->setItem("cpil", TagLib::MP4::Item(song.compilation()));
}
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
@@ -1047,30 +1041,6 @@ TagLib::ID3v2::PopularimeterFrame *TagReaderTagLib::GetPOPMFrameFromTag(TagLib::
}
float TagReaderTagLib::ConvertPOPMRating(const int POPM_rating) {
if (POPM_rating < 0x01) return 0.0F;
else if (POPM_rating < 0x40) return 0.20F;
else if (POPM_rating < 0x80) return 0.40F;
else if (POPM_rating < 0xC0) return 0.60F;
else if (POPM_rating < 0xFC) return 0.80F;
return 1.0F;
}
int TagReaderTagLib::ConvertToPOPMRating(const float rating) {
if (rating < 0.20) return 0x00;
else if (rating < 0.40) return 0x01;
else if (rating < 0.60) return 0x40;
else if (rating < 0.80) return 0x80;
else if (rating < 1.0) return 0xC0;
return 0xFF;
}
bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
if (filename.isEmpty()) return false;
@@ -1094,14 +1064,14 @@ bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb
TagLib::APE::Tag *tag = wavpack_file->APETag(true);
if (!tag) return false;
if (song.playcount() > 0) {
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(static_cast<int>(song.playcount()))));
tag->setItem("FMPS_Playcount", TagLib::APE::Item("FMPS_Playcount", TagLib::String::number(static_cast<int>(song.playcount()))));
}
}
else if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(fileref->file())) {
TagLib::APE::Tag *tag = ape_file->APETag(true);
if (!tag) return false;
if (song.playcount() > 0) {
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(static_cast<int>(song.playcount()))));
tag->setItem("FMPS_Playcount", TagLib::APE::Item("FMPS_Playcount", TagLib::String::number(static_cast<int>(song.playcount()))));
}
}
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
@@ -1116,7 +1086,7 @@ bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb
TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true);
if (!tag) return false;
if (song.playcount() > 0) {
SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag);
SetUserTextFrame("FMPS_Playcount", QString::number(song.playcount()), tag);
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
if (frame) {
frame->setCounter(song.playcount());
@@ -1135,7 +1105,7 @@ bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb
TagLib::APE::Tag *tag = mpc_file->APETag(true);
if (!tag) return false;
if (song.playcount() > 0) {
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(static_cast<int>(song.playcount()))));
tag->setItem("FMPS_Playcount", TagLib::APE::Item("FMPS_Playcount", TagLib::String::number(static_cast<int>(song.playcount()))));
}
}
else if (TagLib::ASF::File *asf_file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {

View File

@@ -50,7 +50,7 @@ class TagReaderTagLib : public TagReaderBase {
bool IsMediaFile(const QString &filename) const override;
void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
QByteArray LoadEmbeddedArt(const QString &filename) const override;
@@ -59,12 +59,11 @@ class TagReaderTagLib : public TagReaderBase {
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
static void Decode(const TagLib::String &tag, std::string *output);
private:
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
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, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
@@ -79,8 +78,6 @@ class TagReaderTagLib : public TagReaderBase {
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
static float ConvertPOPMRating(const int POPM_rating);
static int ConvertToPOPMRating(const float rating);
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
private:

View File

@@ -93,13 +93,13 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
}
void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
qLog(Debug) << "Reading tags from" << filename;
const QFileInfo fileinfo(filename);
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return;
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
@@ -135,19 +135,19 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
taginfo.parseContainerFormat(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return;
return false;
}
taginfo.parseTracks(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return;
return false;
}
taginfo.parseTags(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return;
return false;
}
for (const TagParser::DiagMessage &msg : diag) {
@@ -206,7 +206,7 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
if (song->filetype() == spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_UNKNOWN) {
taginfo.close();
return;
return false;
}
for (const auto tag : taginfo.tags()) {
@@ -227,6 +227,10 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
song->set_art_automatic(kEmbeddedCover);
}
const float rating = ConvertPOPMRating(tag->value(TagParser::KnownField::Rating));
if (song->rating() <= 0 && rating > 0.0 && rating <= 1.0) {
song->set_rating(rating);
}
}
// Set integer fields to -1 if they're not valid
@@ -243,8 +247,12 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
taginfo.close();
return true;
}
catch(...) {
return false;
}
catch(...) {}
}
@@ -469,7 +477,7 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb
}
for (const auto tag : taginfo.tags()) {
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(song.rating()));
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
}
taginfo.applyChanges(diag, progress);
taginfo.close();

View File

@@ -39,7 +39,7 @@ class TagReaderTagParser : public TagReaderBase {
bool IsMediaFile(const QString &filename) const override;
void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
QByteArray LoadEmbeddedArt(const QString &filename) const override;

View File

@@ -91,7 +91,7 @@ int main(int argc, char **argv) {
qLog(Error) << "First line" << first_line << "does not match" << filepath;
success = false;
}
QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$"));
QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$"));
for (const QString &output_line : output_lines) {
//qDebug() << "Final check on" << filepath << output_line;
@@ -130,9 +130,6 @@ int main(int argc, char **argv) {
else if (library.startsWith("/System/Library/") || library.startsWith("/usr/lib/")) { // System library
continue;
}
else if (library.endsWith("libgcc_s.1.dylib")) { // fftw points to it for some reason.
continue;
}
else {
qLog(Error) << "File" << filepath << "points to" << library;
success = false;

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.7)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})

View File

@@ -34,28 +34,11 @@ void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
spb::tagreader::Message reply;
if (message.has_is_media_file_request()) {
reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
}
else if (message.has_read_file_request()) {
tag_reader_.ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
}
else if (message.has_save_file_request()) {
reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata()));
}
else if (message.has_load_embedded_art_request()) {
QByteArray data = tag_reader_.LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
}
else if (message.has_save_embedded_art_request()) {
reply.mutable_save_embedded_art_response()->set_success(tag_reader_.SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), static_cast<qint64>(message.save_embedded_art_request().data().size()))));
}
else if (message.has_save_song_playcount_to_file_request()) {
reply.mutable_save_song_playcount_to_file_response()->set_success(tag_reader_.SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata()));
}
else if (message.has_save_song_rating_to_file_request()) {
reply.mutable_save_song_rating_to_file_response()->set_success(tag_reader_.SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata()));
bool success = HandleMessage(message, reply, &tag_reader_);
if (!success) {
#if defined(USE_TAGLIB)
HandleMessage(message, reply, &tag_reader_gme_);
#endif
}
SendReply(message, &reply);
@@ -63,7 +46,50 @@ void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
}
void TagReaderWorker::DeviceClosed() {
AbstractMessageHandler<spb::tagreader::Message>::DeviceClosed();
QCoreApplication::exit();
}
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
if (message.has_is_media_file_request()) {
bool success = reader->IsMediaFile(QStringFromStdString(message.is_media_file_request().filename()));
reply.mutable_is_media_file_response()->set_success(success);
return success;
}
else if (message.has_read_file_request()) {
bool success = reader->ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
return success;
}
else if (message.has_save_file_request()) {
bool success = reader->SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata());
reply.mutable_save_file_response()->set_success(success);
return success;
}
else if (message.has_load_embedded_art_request()) {
QByteArray data = reader->LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
return true;
}
else if (message.has_save_embedded_art_request()) {
bool success = reader->SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), static_cast<qint64>(message.save_embedded_art_request().data().size())));
reply.mutable_save_embedded_art_response()->set_success(success);
return success;
}
else if (message.has_save_song_playcount_to_file_request()) {
bool success = reader->SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata());
reply.mutable_save_song_playcount_to_file_response()->set_success(success);
return success;
}
else if (message.has_save_song_rating_to_file_request()) {
bool success = reader->SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata());
reply.mutable_save_song_rating_to_file_response()->set_success(success);
return success;
}
return false;
}

View File

@@ -26,9 +26,11 @@
#include "core/messagehandler.h"
#if defined(USE_TAGLIB)
# include "tagreadertaglib.h"
# include "tagreadergme.h"
#elif defined(USE_TAGPARSER)
# include "tagreadertagparser.h"
#endif
#include "tagreadermessages.pb.h"
class QIODevice;
@@ -44,8 +46,12 @@ class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
void DeviceClosed() override;
private:
// Handle message using specific TagReaderBase implementation. Returns true on successful message handle.
bool HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase* reader);
#if defined(USE_TAGLIB)
TagReaderTagLib tag_reader_;
TagReaderGME tag_reader_gme_;
#elif defined(USE_TAGPARSER)
TagReaderTagParser tag_reader_;
#endif

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.7)
if(HAVE_TRANSLATIONS)
include(../cmake/Translations.cmake)
@@ -35,14 +35,28 @@ set(SOURCES
core/taskmanager.cpp
core/thread.cpp
core/urlhandler.cpp
core/utilities.cpp
core/imageutils.cpp
core/iconloader.cpp
core/standarditemiconloader.cpp
core/scopedtransaction.cpp
core/translations.cpp
core/systemtrayicon.cpp
utilities/strutils.cpp
utilities/envutils.cpp
utilities/colorutils.cpp
utilities/cryptutils.cpp
utilities/fileutils.cpp
utilities/diskutils.cpp
utilities/imageutils.cpp
utilities/macaddrutils.cpp
utilities/mimeutils.cpp
utilities/randutils.cpp
utilities/threadutils.cpp
utilities/timeutils.cpp
utilities/transliterate.cpp
utilities/xmlutils.cpp
utilities/filemanagerutils.cpp
engine/enginetype.cpp
engine/enginebase.cpp
engine/devicefinders.cpp
@@ -162,6 +176,8 @@ set(SOURCES
lyrics/musixmatchlyricsprovider.cpp
lyrics/chartlyricsprovider.cpp
providers/musixmatchprovider.cpp
settings/settingsdialog.cpp
settings/settingspage.cpp
settings/behavioursettingspage.cpp
@@ -186,6 +202,7 @@ set(SOURCES
dialogs/deleteconfirmationdialog.cpp
dialogs/lastfmimportdialog.cpp
dialogs/snapdialog.cpp
dialogs/saveplaylistsdialog.cpp
widgets/autoexpandingtreeview.cpp
widgets/busyindicator.cpp
@@ -417,6 +434,7 @@ set(HEADERS
dialogs/deleteconfirmationdialog.h
dialogs/lastfmimportdialog.h
dialogs/snapdialog.h
dialogs/saveplaylistsdialog.h
widgets/autoexpandingtreeview.h
widgets/busyindicator.h
@@ -442,7 +460,6 @@ set(HEADERS
widgets/qsearchfield.h
widgets/ratingwidget.h
widgets/forcescrollperpixel.h
widgets/resizabletextedit.h
osd/osdbase.h
osd/osdpretty.h
@@ -542,6 +559,7 @@ set(UI
dialogs/userpassdialog.ui
dialogs/lastfmimportdialog.ui
dialogs/snapdialog.ui
dialogs/saveplaylistsdialog.ui
widgets/trackslider.ui
widgets/fileview.ui
@@ -567,7 +585,7 @@ option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON)
if(NOT APPLE)
set(NOT_APPLE ON)
optional_source(NOT_APPLE SOURCES widgets/qsearchfield_nonmac.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h)
optional_source(NOT_APPLE SOURCES widgets/qsearchfield_qt.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h)
endif()
if(HAVE_GLOBALSHORTCUTS)
@@ -607,43 +625,42 @@ optional_source(HAVE_VLC SOURCES engine/vlcengine.cpp HEADERS engine/vlcengine.h
# DBUS and MPRIS - Unix specific
if(UNIX AND HAVE_DBUS)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
optional_source(HAVE_DBUS SOURCES core/mpris2.cpp HEADERS core/mpris2.h)
optional_source(HAVE_UDISKS2 SOURCES device/udisks2lister.cpp HEADERS device/udisks2lister.h)
# MPRIS 2.0 DBUS interfaces
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Player.xml core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player)
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.xml core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root)
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.TrackList.xml core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList)
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Player.xml core/mpris2.h mpris::Mpris2 mpris2_player Mpris2Player)
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.xml core/mpris2.h mpris::Mpris2 mpris2_root Mpris2Root)
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.TrackList.xml core/mpris2.h mpris::Mpris2 mpris2_tracklist Mpris2TrackList)
# MPRIS 2.1 DBUS interfaces
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Playlists.xml core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists)
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Playlists.xml core/mpris2.h mpris::Mpris2 mpris2_playlists Mpris2Playlists)
# org.freedesktop.Notifications DBUS interface
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.Notifications.xml dbus/notification)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.Notifications.xml notification)
# org.gnome.SettingsDaemon interface
qt_add_dbus_interface(SOURCES dbus/org.gnome.SettingsDaemon.MediaKeys.xml dbus/gnomesettingsdaemon)
qt_add_dbus_interface(SOURCES dbus/org.gnome.SettingsDaemon.MediaKeys.xml gnomesettingsdaemon)
# org.mate.SettingsDaemon interface
qt_add_dbus_interface(SOURCES dbus/org.mate.SettingsDaemon.MediaKeys.xml dbus/matesettingsdaemon)
qt_add_dbus_interface(SOURCES dbus/org.mate.SettingsDaemon.MediaKeys.xml matesettingsdaemon)
# org.kde.KGlobalAccel interface
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.xml dbus/kglobalaccel)
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.Component.xml dbus/kglobalaccelcomponent)
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.xml kglobalaccel)
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.Component.xml kglobalaccelcomponent)
if(HAVE_UDISKS2)
set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml PROPERTIES NO_NAMESPACE dbus/objectmanager INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml PROPERTIES NO_NAMESPACE dbus/udisks2filesystem INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Block.xml PROPERTIES NO_NAMESPACE dbus/udisks2block INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Drive.xml PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.DBus.ObjectManager.xml dbus/objectmanager)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Filesystem.xml dbus/udisks2filesystem)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Block.xml dbus/udisks2block)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Drive.xml dbus/udisks2drive)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Job.xml dbus/udisks2job)
set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml PROPERTIES NO_NAMESPACE objectmanager INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml PROPERTIES NO_NAMESPACE udisks2filesystem INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Block.xml PROPERTIES NO_NAMESPACE udisks2block INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Drive.xml PROPERTIES NO_NAMESPACE udisks2drive INCLUDE dbus/metatypes.h)
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml PROPERTIES NO_NAMESPACE udisks2job INCLUDE dbus/metatypes.h)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.DBus.ObjectManager.xml objectmanager)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Filesystem.xml udisks2filesystem)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Block.xml udisks2block)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Drive.xml udisks2drive)
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Job.xml udisks2job)
endif(HAVE_UDISKS2)
endif(UNIX AND HAVE_DBUS)
@@ -779,8 +796,8 @@ optional_source(HAVE_AUDIOCD
# Platform specific - macOS
optional_source(APPLE
SOURCES
utilities/macosutils.mm
core/scoped_nsautorelease_pool.mm
core/mac_utilities.mm
core/mac_startup.mm
core/macsystemtrayicon.mm
core/macfslistener.mm
@@ -801,8 +818,10 @@ optional_source(APPLE
# Platform specific - Windows
optional_source(WIN32
SOURCES
utilities/winutils.cpp
engine/directsounddevicefinder.cpp
engine/mmdevicefinder.cpp
core/scopedwchararray.cpp
core/windows7thumbbar.cpp
HEADERS
core/windows7thumbbar.h
@@ -941,9 +960,14 @@ link_directories(
${SQLITE_LIBRARY_DIRS}
${SINGLEAPPLICATION_LIBRARY_DIRS}
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
${Iconv_LIBRARY_DIRS}
)
if(HAVE_ICU)
link_directories(${ICU_LIBRARY_DIRS})
else()
link_directories(${Iconv_LIBRARY_DIRS})
endif()
if(HAVE_ALSA)
link_directories(${ALSA_LIBRARY_DIRS})
endif()
@@ -1055,11 +1079,21 @@ target_link_libraries(strawberry_lib PUBLIC
${QT_LIBRARIES}
${SINGLEAPPLICATION_LIBRARIES}
${SINGLECOREAPPLICATION_LIBRARIES}
${Iconv_LIBRARIES}
libstrawberry-common
libstrawberry-tagreader
)
if(HAVE_ICU)
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ICU_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${ICU_LIBRARIES})
else()
if(FREEBSD AND NOT Iconv_LIBRARIES)
set(Iconv_LIBRARIES iconv)
endif()
target_include_directories(strawberry_lib SYSTEM PRIVATE ${Iconv_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${Iconv_LIBRARIES})
endif()
if(HAVE_ALSA)
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS})
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})
@@ -1138,10 +1172,6 @@ if(HAVE_LIBMTP)
target_link_libraries(strawberry_lib PRIVATE ${LIBMTP_LIBRARIES})
endif()
if(FREEBSD)
target_link_libraries(strawberry_lib PRIVATE iconv)
endif()
if(APPLE)
target_link_libraries(strawberry_lib PRIVATE
"-framework AppKit"

View File

@@ -57,7 +57,11 @@ Analyzer::Base::Base(QWidget *parent, const uint scopeSize)
lastscope_(512),
new_frame_(false),
is_playing_(false),
timeout_(40) {}
timeout_(40) {
setAttribute(Qt::WA_OpaquePaintEvent, true);
}
Analyzer::Base::~Base() {
delete fht_;

View File

@@ -49,7 +49,7 @@ class QTimerEvent;
namespace Analyzer {
typedef std::vector<float> Scope;
using Scope = std::vector<float>;
class Base : public QWidget {
Q_OBJECT

View File

@@ -33,7 +33,6 @@
#include <QActionGroup>
#include <QSettings>
#include <QtEvents>
#include <QtDebug>
#include "analyzercontainer.h"

View File

@@ -27,7 +27,6 @@
#include <QList>
#include <QSettings>
#include <QtConcurrentRun>
#include <QtDebug>
#include "core/application.h"
#include "core/taskmanager.h"
@@ -35,9 +34,9 @@
#include "core/player.h"
#include "core/tagreaderclient.h"
#include "core/thread.h"
#include "core/utilities.h"
#include "core/song.h"
#include "core/logging.h"
#include "utilities/threadutils.h"
#include "collection.h"
#include "collectionwatcher.h"
#include "collectionbackend.h"

View File

@@ -31,7 +31,7 @@
#include <QThread>
#include "core/song.h"
#include "core/utilities.h"
#include "utilities/threadutils.h"
class Application;
class Thread;

View File

@@ -1314,10 +1314,6 @@ SongList CollectionBackend::GetCompilationSongs(const QString &album, const Quer
}
Song::Source CollectionBackend::Source() const {
return source_;
}
void CollectionBackend::CompilationsNeedUpdating() {
QMutexLocker l(db_->Mutex());

View File

@@ -72,11 +72,13 @@ class CollectionBackendInterface : public QObject {
Song::FileType filetype;
QString cue_path;
};
typedef QList<Album> AlbumList;
using AlbumList = QList<Album>;
virtual QString songs_table() const = 0;
virtual QString fts_table() const = 0;
virtual Song::Source source() const = 0;
virtual Database *db() const = 0;
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
@@ -139,6 +141,8 @@ class CollectionBackend : public CollectionBackendInterface {
void ReportErrors(const CollectionQuery &query);
Song::Source source() const override { return source_; }
Database *db() const override { return db_; }
QString songs_table() const override { return songs_table_; }
@@ -207,8 +211,6 @@ class CollectionBackend : public CollectionBackendInterface {
SongList SmartPlaylistsGetAllSongs();
SongList SmartPlaylistsFindSongs(const SmartPlaylistSearch &search);
Song::Source Source() const;
void AddOrUpdateSongsAsync(const SongList &songs);
void UpdateSongsBySongIDAsync(const SongMap &new_songs);

View File

@@ -29,7 +29,7 @@
#include "core/filesystemmusicstorage.h"
#include "core/iconloader.h"
#include "core/musicstorage.h"
#include "core/utilities.h"
#include "utilities/diskutils.h"
#include "directory.h"
#include "collectionbackend.h"
#include "collectiondirectorymodel.h"
@@ -51,7 +51,7 @@ void CollectionDirectoryModel::DirectoryDiscovered(const Directory &dir) {
QStandardItem *item = new QStandardItem(dir.path);
item->setData(dir.id, kIdRole);
item->setIcon(dir_icon_);
storage_ << std::make_shared<FilesystemMusicStorage>(dir.path, dir.id);
storage_ << std::make_shared<FilesystemMusicStorage>(backend_->source(), dir.path, dir.id);
appendRow(item);
}

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2022, 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
@@ -58,7 +59,12 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
: QWidget(parent),
ui_(new Ui_CollectionFilterWidget),
model_(nullptr),
group_by_dialog_(new GroupByDialog),
group_by_dialog_(new GroupByDialog(this)),
groupings_manager_(nullptr),
filter_age_menu_(nullptr),
group_by_menu_(nullptr),
collection_menu_(nullptr),
group_by_group_(nullptr),
filter_delay_(new QTimer(this)),
filter_applies_to_model_(true),
delay_behaviour_(DelayedOnLargeLibraries) {
@@ -114,13 +120,8 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
filter_ages_[ui_->filter_age_three_months] = 60 * 60 * 24 * 30 * 3;
filter_ages_[ui_->filter_age_year] = 60 * 60 * 24 * 365;
// "Group by ..."
group_by_group_ = CreateGroupByActions(this);
group_by_menu_ = new QMenu(tr("Group by"), this);
group_by_menu_->addActions(group_by_group_->actions());
QObject::connect(group_by_group_, &QActionGroup::triggered, this, &CollectionFilterWidget::GroupByClicked);
QObject::connect(ui_->save_grouping, &QAction::triggered, this, &CollectionFilterWidget::SaveGroupBy);
QObject::connect(ui_->manage_groupings, &QAction::triggered, this, &CollectionFilterWidget::ShowGroupingManager);
@@ -147,8 +148,8 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
if (model_) {
QObject::disconnect(model_, nullptr, this, nullptr);
QObject::disconnect(model_, nullptr, group_by_dialog_.get(), nullptr);
QObject::disconnect(group_by_dialog_.get(), nullptr, model_, nullptr);
QObject::disconnect(model_, nullptr, group_by_dialog_, nullptr);
QObject::disconnect(group_by_dialog_, nullptr, model_, nullptr);
QList<QAction*> filter_ages = filter_ages_.keys();
for (QAction *action : filter_ages) {
QObject::disconnect(action, &QAction::triggered, model_, nullptr);
@@ -158,9 +159,9 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
model_ = model;
// Connect signals
QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_.get(), &GroupByDialog::CollectionGroupingChanged);
QObject::connect(model_, &CollectionModel::GroupingChanged, group_by_dialog_, &GroupByDialog::CollectionGroupingChanged);
QObject::connect(model_, &CollectionModel::GroupingChanged, this, &CollectionFilterWidget::GroupingChanged);
QObject::connect(group_by_dialog_.get(), &GroupByDialog::Accepted, model_, &CollectionModel::SetGroupBy);
QObject::connect(group_by_dialog_, &GroupByDialog::Accepted, model_, &CollectionModel::SetGroupBy);
QList<QAction*> filter_ages = filter_ages_.keys();
for (QAction *action : filter_ages) {
@@ -176,15 +177,31 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
if (version == 1) {
model_->SetGroupBy(CollectionModel::Grouping(
CollectionModel::GroupBy(s.value(group_by(1), static_cast<int>(CollectionModel::GroupBy_AlbumArtist)).toInt()),
CollectionModel::GroupBy(s.value(group_by(2), static_cast<int>(CollectionModel::GroupBy_AlbumDisc)).toInt()),
CollectionModel::GroupBy(s.value(group_by(3), static_cast<int>(CollectionModel::GroupBy_None)).toInt())));
CollectionModel::GroupBy(s.value(group_by_key(1), static_cast<int>(CollectionModel::GroupBy_AlbumArtist)).toInt()),
CollectionModel::GroupBy(s.value(group_by_key(2), static_cast<int>(CollectionModel::GroupBy_AlbumDisc)).toInt()),
CollectionModel::GroupBy(s.value(group_by_key(3), static_cast<int>(CollectionModel::GroupBy_None)).toInt())), s.value(separate_albums_by_grouping_key(), false).toBool());
}
else {
model_->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_AlbumDisc, CollectionModel::GroupBy_None));
model_->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_AlbumDisc, CollectionModel::GroupBy_None), false);
}
s.endGroup();
}
}
void CollectionFilterWidget::SetSettingsGroup(const QString &settings_group) {
settings_group_ = settings_group;
saved_groupings_settings_group_ = SavedGroupingManager::GetSavedGroupingsSettingsGroup(settings_group);
UpdateGroupByActions();
}
void CollectionFilterWidget::SetSettingsPrefix(const QString &prefix) {
settings_prefix_ = prefix;
}
void CollectionFilterWidget::ReloadSettings() {
@@ -198,21 +215,10 @@ void CollectionFilterWidget::ReloadSettings() {
}
QString CollectionFilterWidget::group_by() {
QString CollectionFilterWidget::group_by_version() const {
if (settings_prefix_.isEmpty()) {
return QString("group_by");
}
else {
return QString("%1_group_by").arg(settings_prefix_);
}
}
QString CollectionFilterWidget::group_by_version() {
if (settings_prefix_.isEmpty()) {
return QString("group_by_version");
return "group_by_version";
}
else {
return QString("%1_group_by_version").arg(settings_prefix_);
@@ -220,7 +226,29 @@ QString CollectionFilterWidget::group_by_version() {
}
QString CollectionFilterWidget::group_by(const int number) { return group_by() + QString::number(number); }
QString CollectionFilterWidget::group_by_key() const {
if (settings_prefix_.isEmpty()) {
return "group_by";
}
else {
return QString("%1_group_by").arg(settings_prefix_);
}
}
QString CollectionFilterWidget::group_by_key(const int number) const { return group_by_key() + QString::number(number); }
QString CollectionFilterWidget::separate_albums_by_grouping_key() const {
if (settings_prefix_.isEmpty()) {
return "separate_albums_by_grouping";
}
else {
return QString("%1_separate_albums_by_grouping").arg(settings_prefix_);
}
}
void CollectionFilterWidget::UpdateGroupByActions() {
@@ -229,7 +257,7 @@ void CollectionFilterWidget::UpdateGroupByActions() {
delete group_by_group_;
}
group_by_group_ = CreateGroupByActions(this);
group_by_group_ = CreateGroupByActions(saved_groupings_settings_group_, this);
group_by_menu_->clear();
group_by_menu_->addActions(group_by_group_->actions());
QObject::connect(group_by_group_, &QActionGroup::triggered, this, &CollectionFilterWidget::GroupByClicked);
@@ -239,8 +267,7 @@ void CollectionFilterWidget::UpdateGroupByActions() {
}
QActionGroup *CollectionFilterWidget::CreateGroupByActions(QObject *parent) {
QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_groupings_settings_group, QObject *parent) {
QActionGroup *ret = new QActionGroup(parent);
@@ -267,9 +294,9 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(QObject *parent) {
sep1->setSeparator(true);
ret->addAction(sep1);
// read saved groupings
// Read saved groupings
QSettings s;
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
s.beginGroup(saved_groupings_settings_group);
int version = s.value("version").toInt();
if (version == 1) {
QStringList saved = s.childKeys();
@@ -316,20 +343,38 @@ QAction *CollectionFilterWidget::CreateGroupByAction(const QString &text, QObjec
void CollectionFilterWidget::SaveGroupBy() {
QString text = QInputDialog::getText(this, tr("Grouping Name"), tr("Grouping name:"));
if (!text.isEmpty() && model_) {
model_->SaveGrouping(text);
UpdateGroupByActions();
if (!model_) return;
QString name = QInputDialog::getText(this, tr("Grouping Name"), tr("Grouping name:"));
if (name.isEmpty()) return;
qLog(Debug) << "Saving current grouping to" << name;
QSettings s;
if (settings_group_.isEmpty() || settings_group_ == CollectionSettingsPage::kSettingsGroup) {
s.beginGroup(SavedGroupingManager::kSavedGroupingsSettingsGroup);
}
else {
s.beginGroup(QString(SavedGroupingManager::kSavedGroupingsSettingsGroup) + "_" + settings_group_);
}
QByteArray buffer;
QDataStream datastream(&buffer, QIODevice::WriteOnly);
datastream << model_->GetGroupBy();
s.setValue("version", "1");
s.setValue(name, buffer);
s.endGroup();
UpdateGroupByActions();
}
void CollectionFilterWidget::ShowGroupingManager() {
if (!groupings_manager_) {
groupings_manager_ = std::make_unique<SavedGroupingManager>();
groupings_manager_ = new SavedGroupingManager(saved_groupings_settings_group_, this);
QObject::connect(groupings_manager_, &SavedGroupingManager::UpdateGroupByActions, this, &CollectionFilterWidget::UpdateGroupByActions);
}
groupings_manager_->SetFilter(this);
groupings_manager_->UpdateModel();
groupings_manager_->show();
@@ -366,16 +411,16 @@ void CollectionFilterWidget::GroupByClicked(QAction *action) {
}
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping g) {
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping) {
if (!settings_group_.isEmpty()) {
// Save the settings
QSettings s;
s.beginGroup(settings_group_);
s.setValue(group_by_version(), 1);
s.setValue(group_by(1), static_cast<int>(g[0]));
s.setValue(group_by(2), static_cast<int>(g[1]));
s.setValue(group_by(3), static_cast<int>(g[2]));
s.setValue(group_by_key(1), static_cast<int>(g[0]));
s.setValue(group_by_key(2), static_cast<int>(g[1]));
s.setValue(group_by_key(3), static_cast<int>(g[2]));
s.setValue(separate_albums_by_grouping_key(), separate_albums_by_grouping);
s.endGroup();
}
@@ -386,6 +431,10 @@ void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping g)
void CollectionFilterWidget::CheckCurrentGrouping(const CollectionModel::Grouping g) {
if (!group_by_group_) {
UpdateGroupByActions();
}
for (QAction *action : group_by_group_->actions()) {
if (action->property("group_by").isNull()) continue;

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2022, 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
@@ -60,9 +61,8 @@ class CollectionFilterWidget : public QWidget {
void Init(CollectionModel *model);
static QActionGroup *CreateGroupByActions(QObject *parent);
static QActionGroup *CreateGroupByActions(const QString &saved_groupings_settings_group, QObject *parent);
void UpdateGroupByActions();
void SetFilterHint(const QString &hint);
void SetApplyFilterToCollection(bool filter_applies_to_model) { filter_applies_to_model_ = filter_applies_to_model; }
void SetDelayBehaviour(DelayBehaviour behaviour) { delay_behaviour_ = behaviour; }
@@ -73,12 +73,13 @@ class CollectionFilterWidget : public QWidget {
QMenu *menu() const { return collection_menu_; }
void AddMenuAction(QAction *action);
void SetSettingsGroup(const QString &group) { settings_group_ = group; }
void SetSettingsPrefix(const QString &prefix) { settings_prefix_ = prefix; }
void SetSettingsGroup(const QString &group);
void SetSettingsPrefix(const QString &prefix);
QString group_by();
QString group_by_version();
QString group_by(const int number);
QString group_by_version() const;
QString group_by_key() const;
QString group_by_key(const int number) const;
QString separate_albums_by_grouping_key() const;
void ReloadSettings();
@@ -86,6 +87,7 @@ class CollectionFilterWidget : public QWidget {
void FocusSearchField();
public slots:
void UpdateGroupByActions();
void SetQueryMode(QueryOptions::QueryMode query_mode);
void FocusOnFilter(QKeyEvent *e);
@@ -99,7 +101,7 @@ class CollectionFilterWidget : public QWidget {
void keyReleaseEvent(QKeyEvent *e) override;
private slots:
void GroupingChanged(const CollectionModel::Grouping g);
void GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
void GroupByClicked(QAction *action);
void SaveGroupBy();
void ShowGroupingManager();
@@ -115,8 +117,8 @@ class CollectionFilterWidget : public QWidget {
Ui_CollectionFilterWidget *ui_;
CollectionModel *model_;
std::unique_ptr<GroupByDialog> group_by_dialog_;
std::unique_ptr<SavedGroupingManager> groupings_manager_;
GroupByDialog *group_by_dialog_;
SavedGroupingManager *groupings_manager_;
QMenu *filter_age_menu_;
QMenu *group_by_menu_;
@@ -130,6 +132,7 @@ class CollectionFilterWidget : public QWidget {
DelayBehaviour delay_behaviour_;
QString settings_group_;
QString saved_groupings_settings_group_;
QString settings_prefix_;
};

View File

@@ -25,6 +25,7 @@
#include <functional>
#include <algorithm>
#include <utility>
#include <optional>
#include <QObject>
#include <QtGlobal>
@@ -52,7 +53,6 @@
#include <QNetworkDiskCache>
#include <QSettings>
#include <QStandardPaths>
#include <QtDebug>
#include "core/application.h"
#include "core/database.h"
@@ -71,7 +71,6 @@
#include "covermanager/albumcoverloaderresult.h"
#include "settings/collectionsettingspage.h"
const char *CollectionModel::kSavedGroupingsSettingsGroup = "SavedGroupings";
const int CollectionModel::kPrettyCoverSize = 32;
const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache";
@@ -86,6 +85,7 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
total_song_count_(0),
total_artist_count_(0),
total_album_count_(0),
separate_albums_by_grouping_(false),
artist_icon_(IconLoader::Load("folder-sound")),
album_icon_(IconLoader::Load("cdcase")),
init_task_id_(-1),
@@ -160,22 +160,6 @@ void CollectionModel::set_show_dividers(const bool show_dividers) {
}
void CollectionModel::SaveGrouping(const QString &name) {
qLog(Debug) << "Model, save to: " << name;
QByteArray buffer;
QDataStream ds(&buffer, QIODevice::WriteOnly);
ds << group_by_;
QSettings s;
s.beginGroup(kSavedGroupingsSettingsGroup);
s.setValue("version", "1");
s.setValue(name, buffer);
s.endGroup();
}
void CollectionModel::ReloadSettings() {
QSettings s;
@@ -239,13 +223,13 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
CollectionItem *container = root_;
QString key;
for (int i = 0; i < 3; ++i) {
GroupBy type = group_by_[i];
if (type == GroupBy_None) break;
GroupBy group_by = group_by_[i];
if (group_by == GroupBy_None) break;
if (!key.isEmpty()) key.append("-");
// Special case: if the song is a compilation and the current GroupBy level is Artists, then we want the Various Artists node :(
if (IsArtistGroupBy(type) && song.is_compilation()) {
if (IsArtistGroupBy(group_by) && song.is_compilation()) {
if (container->compilation_artist_node_ == nullptr) {
CreateCompilationArtistNode(true, container);
}
@@ -254,7 +238,7 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
}
else {
// Otherwise find the proper container at this level based on the item's key
key.append(ContainerKey(type, song));
key.append(ContainerKey(group_by, separate_albums_by_grouping_, song));
// Does it exist already?
if (container_nodes_[i].contains(key)) {
@@ -262,7 +246,7 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
}
else {
// Create the container
container = ItemFromSong(type, true, i == 0, container, song, i);
container = ItemFromSong(group_by, separate_albums_by_grouping_, true, i == 0, container, song, i);
container_nodes_[i].insert(key, container);
}
@@ -274,7 +258,7 @@ void CollectionModel::SongsDiscovered(const SongList &songs) {
if (!container->lazy_loaded && use_lazy_loading_) continue;
// We've gone all the way down to the deepest level and everything was already lazy loaded, so now we have to create the song in the container.
song_nodes_.insert(song.id(), ItemFromSong(GroupBy_None, true, false, container, song, -1));
song_nodes_.insert(song.id(), ItemFromSong(GroupBy_None, separate_albums_by_grouping_, true, false, container, song, -1));
}
}
@@ -311,11 +295,11 @@ CollectionItem *CollectionModel::CreateCompilationArtistNode(const bool signal,
}
QString CollectionModel::ContainerKey(const GroupBy type, const Song &song) {
QString CollectionModel::ContainerKey(const GroupBy group_by, const bool separate_albums_by_grouping, const Song &song) {
QString key;
switch (type) {
switch (group_by) {
case GroupBy_AlbumArtist:
key = TextOrUnknown(song.effective_albumartist());
break;
@@ -325,32 +309,32 @@ QString CollectionModel::ContainerKey(const GroupBy type, const Song &song) {
case GroupBy_Album:
key = TextOrUnknown(song.album());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id());
if (!song.grouping().isEmpty()) key.append("-" + song.grouping());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping());
break;
case GroupBy_AlbumDisc:
key = PrettyAlbumDisc(song.album(), song.disc());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id());
if (!song.grouping().isEmpty()) key.append("-" + song.grouping());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping());
break;
case GroupBy_YearAlbum:
key = PrettyYearAlbum(song.year(), song.album());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id());
if (!song.grouping().isEmpty()) key.append("-" + song.grouping());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping());
break;
case GroupBy_YearAlbumDisc:
key = PrettyYearAlbumDisc(song.year(), song.album(), song.disc());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id());
if (!song.grouping().isEmpty()) key.append("-" + song.grouping());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping());
break;
case GroupBy_OriginalYearAlbum:
key = PrettyYearAlbum(song.effective_originalyear(), song.album());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id());
if (!song.grouping().isEmpty()) key.append("-" + song.grouping());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping());
break;
case GroupBy_OriginalYearAlbumDisc:
key = PrettyYearAlbumDisc(song.effective_originalyear(), song.album(), song.disc());
if (!song.album_id().isEmpty()) key.append("-" + song.album_id());
if (!song.grouping().isEmpty()) key.append("-" + song.grouping());
if (separate_albums_by_grouping && !song.grouping().isEmpty()) key.append("-" + song.grouping());
break;
case GroupBy_Disc:
key = PrettyDisc(song.disc());
@@ -408,13 +392,13 @@ QString CollectionModel::ContainerKey(const GroupBy type, const Song &song) {
}
QString CollectionModel::DividerKey(const GroupBy type, CollectionItem *item) {
QString CollectionModel::DividerKey(const GroupBy group_by, CollectionItem *item) {
// Items which are to be grouped under the same divider must produce the same divider key. This will only get called for top-level items.
if (item->sort_text.isEmpty()) return QString();
switch (type) {
switch (group_by) {
case GroupBy_AlbumArtist:
case GroupBy_Artist:
case GroupBy_Album:
@@ -461,16 +445,16 @@ QString CollectionModel::DividerKey(const GroupBy type, CollectionItem *item) {
case GroupByCount:
return QString();
}
qLog(Error) << "Unknown GroupBy type" << type << "for item" << item->display_text;
qLog(Error) << "Unknown GroupBy" << group_by << "for item" << item->display_text;
return QString();
}
QString CollectionModel::DividerDisplayText(const GroupBy type, const QString &key) {
QString CollectionModel::DividerDisplayText(const GroupBy group_by, const QString &key) {
// Pretty display text for the dividers.
switch (type) {
switch (group_by) {
case GroupBy_AlbumArtist:
case GroupBy_Artist:
case GroupBy_Album:
@@ -511,10 +495,9 @@ QString CollectionModel::DividerDisplayText(const GroupBy type, const QString &k
case GroupBy_None:
case GroupByCount:
// fallthrough
;
break;
}
qLog(Error) << "Unknown GroupBy type" << type << "for divider key" << key;
qLog(Error) << "Unknown GroupBy" << group_by << "for divider key" << key;
return QString();
}
@@ -625,7 +608,7 @@ QString CollectionModel::AlbumIconPixmapCacheKey(const QModelIndex &idx) const {
idx_copy = idx_copy.parent();
}
return Song::TextForSource(backend_->Source()) + "/" + path.join("/");
return Song::TextForSource(backend_->source()) + "/" + path.join("/");
}
@@ -727,8 +710,8 @@ QVariant CollectionModel::data(const QModelIndex &idx, const int role) const {
if (use_pretty_covers_) {
bool is_album_node = false;
if (role == Qt::DecorationRole && item->type == CollectionItem::Type_Container) {
GroupBy container_type = group_by_[item->container_level];
is_album_node = IsAlbumGroupBy(container_type);
GroupBy container_group_by = group_by_[item->container_level];
is_album_node = IsAlbumGroupBy(container_group_by);
}
if (is_album_node) {
// It has const behaviour some of the time - that's ok right?
@@ -742,7 +725,7 @@ QVariant CollectionModel::data(const QModelIndex &idx, const int role) const {
QVariant CollectionModel::data(const CollectionItem *item, const int role) const {
GroupBy container_type = item->type == CollectionItem::Type_Container ? group_by_[item->container_level] : GroupBy_None;
GroupBy container_group_by = item->type == CollectionItem::Type_Container ? group_by_[item->container_level] : GroupBy_None;
switch (role) {
case Qt::DisplayRole:
@@ -752,7 +735,7 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const
case Qt::DecorationRole:
switch (item->type) {
case CollectionItem::Type_Container:
switch (container_type) {
switch (container_group_by) {
case GroupBy_Album:
case GroupBy_AlbumDisc:
case GroupBy_YearAlbum:
@@ -779,7 +762,7 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const
return item->type == CollectionItem::Type_Divider;
case Role_ContainerType:
return container_type;
return container_group_by;
case Role_Key:
return item->key;
@@ -850,26 +833,26 @@ CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) {
// Information about what we want the children to be
int child_level = parent == root_ ? 0 : parent->container_level + 1;
GroupBy child_type = child_level >= 3 ? GroupBy_None : group_by_[child_level];
GroupBy child_group_by = child_level >= 3 ? GroupBy_None : group_by_[child_level];
// Initialize the query. child_type says what type of thing we want (artists, songs, etc.)
// Initialize the query. child_group_by says what type of thing we want (artists, songs, etc.)
{
QMutexLocker l(backend_->db()->Mutex());
QSqlDatabase db(backend_->db()->Connect());
CollectionQuery q(db, backend_->songs_table(), backend_->fts_table(), query_options_);
InitQuery(child_type, &q);
InitQuery(child_group_by, separate_albums_by_grouping_, &q);
// Walk up through the item's parents adding filters as necessary
CollectionItem *p = parent;
while (p && p->type == CollectionItem::Type_Container) {
FilterQuery(group_by_[p->container_level], p, &q);
FilterQuery(group_by_[p->container_level], separate_albums_by_grouping_, p, &q);
p = p->parent;
}
// Artists GroupBy is special - we don't want compilation albums appearing
if (IsArtistGroupBy(child_type)) {
if (IsArtistGroupBy(child_group_by)) {
// Add the special Various artists node
if (show_various_artists_ && HasCompilations(db, q)) {
result.create_va = true;
@@ -903,7 +886,7 @@ void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::Q
// Information about what we want the children to be
int child_level = parent == root_ ? 0 : parent->container_level + 1;
GroupBy child_type = child_level >= 3 ? GroupBy_None : group_by_[child_level];
GroupBy child_group_by = child_level >= 3 ? GroupBy_None : group_by_[child_level];
if (result.create_va && parent->compilation_artist_node_ == nullptr) {
CreateCompilationArtistNode(signal, parent);
@@ -912,10 +895,10 @@ void CollectionModel::PostQuery(CollectionItem *parent, const CollectionModel::Q
// Step through the results
for (const SqlRow &row : result.rows) {
// Create the item - it will get inserted into the model here
CollectionItem *item = ItemFromQuery(child_type, signal, child_level == 0, parent, row, child_level);
CollectionItem *item = ItemFromQuery(child_group_by, separate_albums_by_grouping_, signal, child_level == 0, parent, row, child_level);
// Save a pointer to it for later
if (child_type == GroupBy_None) {
if (child_group_by == GroupBy_None) {
song_nodes_.insert(item->metadata.id(), item);
}
else {
@@ -1003,34 +986,52 @@ void CollectionModel::Reset() {
}
void CollectionModel::InitQuery(const GroupBy type, CollectionQuery *q) {
void CollectionModel::InitQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQuery *q) {
// Say what type of thing we want to get back from the database.
switch (type) {
// Say what group_by of thing we want to get back from the database.
switch (group_by) {
case GroupBy_AlbumArtist:
q->SetColumnSpec("DISTINCT effective_albumartist");
break;
case GroupBy_Artist:
q->SetColumnSpec("DISTINCT artist");
break;
case GroupBy_Album:
q->SetColumnSpec("DISTINCT album, album_id, grouping");
case GroupBy_Album:{
QString query("DISTINCT album, album_id");
if (separate_albums_by_grouping) query.append(", grouping");
q->SetColumnSpec(query);
break;
case GroupBy_AlbumDisc:
q->SetColumnSpec("DISTINCT album, album_id, disc, grouping");
}
case GroupBy_AlbumDisc:{
QString query("DISTINCT album, album_id, disc");
if (separate_albums_by_grouping) query.append(", grouping");
q->SetColumnSpec(query);
break;
case GroupBy_YearAlbum:
q->SetColumnSpec("DISTINCT year, album, album_id, grouping");
}
case GroupBy_YearAlbum:{
QString query("DISTINCT year, album, album_id");
if (separate_albums_by_grouping) query.append(", grouping");
q->SetColumnSpec(query);
break;
case GroupBy_YearAlbumDisc:
q->SetColumnSpec("DISTINCT year, album, album_id, disc, grouping");
}
case GroupBy_YearAlbumDisc:{
QString query("DISTINCT year, album, album_id, disc");
if (separate_albums_by_grouping) query.append(", grouping");
q->SetColumnSpec(query);
break;
case GroupBy_OriginalYearAlbum:
q->SetColumnSpec("DISTINCT year, originalyear, album, album_id, grouping");
}
case GroupBy_OriginalYearAlbum:{
QString query("DISTINCT year, originalyear, album, album_id");
if (separate_albums_by_grouping) query.append(", grouping");
q->SetColumnSpec(query);
break;
case GroupBy_OriginalYearAlbumDisc:
q->SetColumnSpec("DISTINCT year, originalyear, album, album_id, disc, grouping");
}
case GroupBy_OriginalYearAlbumDisc:{
QString query("DISTINCT year, originalyear, album, album_id, disc");
if (separate_albums_by_grouping) query.append(", grouping");
q->SetColumnSpec(query);
break;
}
case GroupBy_Disc:
q->SetColumnSpec("DISTINCT disc");
break;
@@ -1075,11 +1076,11 @@ void CollectionModel::InitQuery(const GroupBy type, CollectionQuery *q) {
}
void CollectionModel::FilterQuery(const GroupBy type, CollectionItem *item, CollectionQuery *q) {
void CollectionModel::FilterQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQuery *q) {
// Say how we want the query to be filtered. This is done once for each parent going up the tree.
switch (type) {
switch (group_by) {
case GroupBy_AlbumArtist:
if (IsCompilationArtistNode(item)) {
q->AddCompilationRequirement(true);
@@ -1103,33 +1104,33 @@ void CollectionModel::FilterQuery(const GroupBy type, CollectionItem *item, Coll
case GroupBy_Album:
q->AddWhere("album", item->metadata.album());
q->AddWhere("album_id", item->metadata.album_id());
q->AddWhere("grouping", item->metadata.grouping());
if (separate_albums_by_grouping) q->AddWhere("grouping", item->metadata.grouping());
break;
case GroupBy_AlbumDisc:
q->AddWhere("album", item->metadata.album());
q->AddWhere("album_id", item->metadata.album_id());
q->AddWhere("disc", item->metadata.disc());
q->AddWhere("grouping", item->metadata.grouping());
if (separate_albums_by_grouping) q->AddWhere("grouping", item->metadata.grouping());
break;
case GroupBy_YearAlbum:
q->AddWhere("year", item->metadata.year());
q->AddWhere("album", item->metadata.album());
q->AddWhere("album_id", item->metadata.album_id());
q->AddWhere("grouping", item->metadata.grouping());
if (separate_albums_by_grouping) q->AddWhere("grouping", item->metadata.grouping());
break;
case GroupBy_YearAlbumDisc:
q->AddWhere("year", item->metadata.year());
q->AddWhere("album", item->metadata.album());
q->AddWhere("album_id", item->metadata.album_id());
q->AddWhere("disc", item->metadata.disc());
q->AddWhere("grouping", item->metadata.grouping());
if (separate_albums_by_grouping) q->AddWhere("grouping", item->metadata.grouping());
break;
case GroupBy_OriginalYearAlbum:
q->AddWhere("year", item->metadata.year());
q->AddWhere("originalyear", item->metadata.originalyear());
q->AddWhere("album", item->metadata.album());
q->AddWhere("album_id", item->metadata.album_id());
q->AddWhere("grouping", item->metadata.grouping());
if (separate_albums_by_grouping) q->AddWhere("grouping", item->metadata.grouping());
break;
case GroupBy_OriginalYearAlbumDisc:
q->AddWhere("year", item->metadata.year());
@@ -1137,7 +1138,7 @@ void CollectionModel::FilterQuery(const GroupBy type, CollectionItem *item, Coll
q->AddWhere("album", item->metadata.album());
q->AddWhere("album_id", item->metadata.album_id());
q->AddWhere("disc", item->metadata.disc());
q->AddWhere("grouping", item->metadata.grouping());
if (separate_albums_by_grouping) q->AddWhere("grouping", item->metadata.grouping());
break;
case GroupBy_Disc:
q->AddWhere("disc", item->metadata.disc());
@@ -1179,15 +1180,15 @@ void CollectionModel::FilterQuery(const GroupBy type, CollectionItem *item, Coll
break;
case GroupBy_None:
case GroupByCount:
qLog(Error) << "Unknown GroupBy type" << type << "used in filter";
qLog(Error) << "Unknown GroupBy" << group_by << "used in filter";
break;
}
}
CollectionItem *CollectionModel::InitItem(const GroupBy type, const bool signal, CollectionItem *parent, const int container_level) {
CollectionItem *CollectionModel::InitItem(const GroupBy group_by, const bool signal, CollectionItem *parent, const int container_level) {
CollectionItem::Type item_type = type == GroupBy_None ? CollectionItem::Type_Song : CollectionItem::Type_Container;
CollectionItem::Type item_type = group_by == GroupBy_None ? CollectionItem::Type_Song : CollectionItem::Type_Container;
if (signal) beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
@@ -1200,25 +1201,25 @@ CollectionItem *CollectionModel::InitItem(const GroupBy type, const bool signal,
}
CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level) {
CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level) {
CollectionItem *item = InitItem(type, signal, parent, container_level);
CollectionItem *item = InitItem(group_by, signal, parent, container_level);
if (parent != root_ && !parent->key.isEmpty()) {
item->key = parent->key + "-";
}
switch (type) {
switch (group_by) {
case GroupBy_AlbumArtist:{
item->metadata.set_albumartist(row.value(0).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.albumartist());
item->sort_text = SortTextForArtist(item->metadata.albumartist());
break;
}
case GroupBy_Artist:{
item->metadata.set_artist(row.value(0).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.artist());
item->sort_text = SortTextForArtist(item->metadata.artist());
break;
@@ -1227,7 +1228,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
item->metadata.set_album(row.value(0).toString());
item->metadata.set_album_id(row.value(1).toString());
item->metadata.set_grouping(row.value(2).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.album());
item->sort_text = SortTextForArtist(item->metadata.album());
break;
@@ -1237,7 +1238,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
item->metadata.set_album_id(row.value(1).toString());
item->metadata.set_disc(row.value(2).toInt());
item->metadata.set_grouping(row.value(3).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyAlbumDisc(item->metadata.album(), item->metadata.disc());
item->sort_text = item->metadata.album() + SortTextForNumber(qMax(0, item->metadata.disc()));
break;
@@ -1247,7 +1248,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
item->metadata.set_album(row.value(1).toString());
item->metadata.set_album_id(row.value(2).toString());
item->metadata.set_grouping(row.value(3).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyYearAlbum(item->metadata.year(), item->metadata.album());
item->sort_text = SortTextForNumber(qMax(0, item->metadata.year())) + item->metadata.grouping() + item->metadata.album();
break;
@@ -1258,7 +1259,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
item->metadata.set_album_id(row.value(2).toString());
item->metadata.set_disc(row.value(3).toInt());
item->metadata.set_grouping(row.value(4).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyYearAlbumDisc(item->metadata.year(), item->metadata.album(), item->metadata.disc());
item->sort_text = SortTextForNumber(qMax(0, item->metadata.year())) + item->metadata.album() + SortTextForNumber(qMax(0, item->metadata.disc()));
break;
@@ -1269,7 +1270,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
item->metadata.set_album(row.value(2).toString());
item->metadata.set_album_id(row.value(3).toString());
item->metadata.set_grouping(row.value(4).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyYearAlbum(item->metadata.effective_originalyear(), item->metadata.album());
item->sort_text = SortTextForNumber(qMax(0, item->metadata.effective_originalyear())) + item->metadata.grouping() + item->metadata.album();
break;
@@ -1281,14 +1282,14 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
item->metadata.set_album_id(row.value(3).toString());
item->metadata.set_disc(row.value(4).toInt());
item->metadata.set_grouping(row.value(5).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = PrettyYearAlbumDisc(item->metadata.effective_originalyear(), item->metadata.album(), item->metadata.disc());
item->sort_text = SortTextForNumber(qMax(0, item->metadata.effective_originalyear())) + item->metadata.album() + SortTextForNumber(qMax(0, item->metadata.disc()));
break;
}
case GroupBy_Disc:{
item->metadata.set_disc(row.value(0).toInt());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int disc = qMax(0, row.value(0).toInt());
item->display_text = PrettyDisc(disc);
item->sort_text = SortTextForNumber(disc);
@@ -1296,7 +1297,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
}
case GroupBy_Year:{
item->metadata.set_year(row.value(0).toInt());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int year = qMax(0, item->metadata.year());
item->display_text = QString::number(year);
item->sort_text = SortTextForNumber(year) + " ";
@@ -1304,7 +1305,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
}
case GroupBy_OriginalYear:{
item->metadata.set_originalyear(row.value(0).toInt());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int year = qMax(0, item->metadata.originalyear());
item->display_text = QString::number(year);
item->sort_text = SortTextForNumber(year) + " ";
@@ -1312,35 +1313,35 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
}
case GroupBy_Genre:{
item->metadata.set_genre(row.value(0).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.genre());
item->sort_text = SortTextForArtist(item->metadata.genre());
break;
}
case GroupBy_Composer:{
item->metadata.set_composer(row.value(0).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.composer());
item->sort_text = SortTextForArtist(item->metadata.composer());
break;
}
case GroupBy_Performer:{
item->metadata.set_performer(row.value(0).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.performer());
item->sort_text = SortTextForArtist(item->metadata.performer());
break;
}
case GroupBy_Grouping:{
item->metadata.set_grouping(row.value(0).toString());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = TextOrUnknown(item->metadata.grouping());
item->sort_text = SortTextForArtist(item->metadata.grouping());
break;
}
case GroupBy_FileType:{
item->metadata.set_filetype(static_cast<Song::FileType>(row.value(0).toInt()));
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
item->display_text = item->metadata.TextForFiletype();
item->sort_text = item->metadata.TextForFiletype();
break;
@@ -1349,7 +1350,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
item->metadata.set_filetype(static_cast<Song::FileType>(row.value(0).toInt()));
item->metadata.set_samplerate(row.value(1).toInt());
item->metadata.set_bitdepth(row.value(2).toInt());
QString key = ContainerKey(type, item->metadata);
QString key = ContainerKey(group_by, separate_albums_by_grouping, item->metadata);
item->key.append(key);
item->display_text = key;
item->sort_text = key;
@@ -1357,7 +1358,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
}
case GroupBy_Samplerate:{
item->metadata.set_samplerate(row.value(0).toInt());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int samplerate = qMax(0, item->metadata.samplerate());
item->display_text = QString::number(samplerate);
item->sort_text = SortTextForNumber(samplerate) + " ";
@@ -1365,7 +1366,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
}
case GroupBy_Bitdepth:{
item->metadata.set_bitdepth(row.value(0).toInt());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int bitdepth = qMax(0, item->metadata.bitdepth());
item->display_text = QString::number(bitdepth);
item->sort_text = SortTextForNumber(bitdepth) + " ";
@@ -1373,7 +1374,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
}
case GroupBy_Bitrate:{
item->metadata.set_bitrate(row.value(0).toInt());
item->key.append(ContainerKey(type, item->metadata));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
const int bitrate = qMax(0, item->metadata.bitrate());
item->display_text = QString::number(bitrate);
item->sort_text = SortTextForNumber(bitrate) + " ";
@@ -1393,31 +1394,31 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy type, const bool si
break;
}
FinishItem(type, signal, create_divider, parent, item);
FinishItem(group_by, signal, create_divider, parent, item);
return item;
}
CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level) {
CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level) {
CollectionItem *item = InitItem(type, signal, parent, container_level);
CollectionItem *item = InitItem(group_by, signal, parent, container_level);
if (parent != root_ && !parent->key.isEmpty()) {
item->key = parent->key + "-";
}
switch (type) {
switch (group_by) {
case GroupBy_AlbumArtist:{
item->metadata.set_albumartist(s.effective_albumartist());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.effective_albumartist());
item->sort_text = SortTextForArtist(s.effective_albumartist());
break;
}
case GroupBy_Artist:{
item->metadata.set_artist(s.artist());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.artist());
item->sort_text = SortTextForArtist(s.artist());
break;
@@ -1426,7 +1427,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
item->metadata.set_album(s.album());
item->metadata.set_album_id(s.album_id());
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.album());
item->sort_text = SortTextForArtist(s.album());
break;
@@ -1436,7 +1437,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
item->metadata.set_album_id(s.album_id());
item->metadata.set_disc(s.disc());
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = PrettyAlbumDisc(s.album(), s.disc());
item->sort_text = s.album() + SortTextForNumber(qMax(0, s.disc()));
break;
@@ -1446,7 +1447,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
item->metadata.set_album(s.album());
item->metadata.set_album_id(s.album_id());
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = PrettyYearAlbum(s.year(), s.album());
item->sort_text = SortTextForNumber(qMax(0, s.year())) + s.grouping() + s.album();
break;
@@ -1457,7 +1458,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
item->metadata.set_album_id(s.album_id());
item->metadata.set_disc(s.disc());
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = PrettyYearAlbumDisc(s.year(), s.album(), s.disc());
item->sort_text = SortTextForNumber(qMax(0, s.year())) + s.album() + SortTextForNumber(qMax(0, s.disc()));
break;
@@ -1468,7 +1469,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
item->metadata.set_album(s.album());
item->metadata.set_album_id(s.album_id());
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = PrettyYearAlbum(s.effective_originalyear(), s.album());
item->sort_text = SortTextForNumber(qMax(0, s.effective_originalyear())) + s.grouping() + s.album();
break;
@@ -1480,14 +1481,14 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
item->metadata.set_album_id(s.album_id());
item->metadata.set_disc(s.disc());
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = PrettyYearAlbumDisc(s.effective_originalyear(), s.album(), s.disc());
item->sort_text = SortTextForNumber(qMax(0, s.effective_originalyear())) + s.album() + SortTextForNumber(qMax(0, s.disc()));
break;
}
case GroupBy_Disc:{
item->metadata.set_disc(s.disc());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int disc = qMax(0, s.disc());
item->display_text = PrettyDisc(disc);
item->sort_text = SortTextForNumber(disc);
@@ -1495,7 +1496,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
}
case GroupBy_Year:{
item->metadata.set_year(s.year());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int year = qMax(0, s.year());
item->display_text = QString::number(year);
item->sort_text = SortTextForNumber(year) + " ";
@@ -1503,7 +1504,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
}
case GroupBy_OriginalYear:{
item->metadata.set_originalyear(s.effective_originalyear());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int year = qMax(0, s.effective_originalyear());
item->display_text = QString::number(year);
item->sort_text = SortTextForNumber(year) + " ";
@@ -1511,35 +1512,35 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
}
case GroupBy_Genre:{
item->metadata.set_genre(s.genre());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.genre());
item->sort_text = SortTextForArtist(s.genre());
break;
}
case GroupBy_Composer:{
item->metadata.set_composer(s.composer());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.composer());
item->sort_text = SortTextForArtist(s.composer());
break;
}
case GroupBy_Performer:{
item->metadata.set_performer(s.performer());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.performer());
item->sort_text = SortTextForArtist(s.performer());
break;
}
case GroupBy_Grouping:{
item->metadata.set_grouping(s.grouping());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = TextOrUnknown(s.grouping());
item->sort_text = SortTextForArtist(s.grouping());
break;
}
case GroupBy_FileType:{
item->metadata.set_filetype(s.filetype());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
item->display_text = s.TextForFiletype();
item->sort_text = s.TextForFiletype();
break;
@@ -1548,7 +1549,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
item->metadata.set_filetype(s.filetype());
item->metadata.set_samplerate(s.samplerate());
item->metadata.set_bitdepth(s.bitdepth());
QString key = ContainerKey(type, s);
QString key = ContainerKey(group_by, separate_albums_by_grouping, s);
item->key.append(key);
item->display_text = key;
item->sort_text = key;
@@ -1556,7 +1557,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
}
case GroupBy_Samplerate:{
item->metadata.set_samplerate(s.samplerate());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int samplerate = qMax(0, s.samplerate());
item->display_text = QString::number(samplerate);
item->sort_text = SortTextForNumber(samplerate) + " ";
@@ -1564,7 +1565,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
}
case GroupBy_Bitdepth:{
item->metadata.set_bitdepth(s.bitdepth());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int bitdepth = qMax(0, s.bitdepth());
item->display_text = QString::number(bitdepth);
item->sort_text = SortTextForNumber(bitdepth) + " ";
@@ -1572,7 +1573,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
}
case GroupBy_Bitrate:{
item->metadata.set_bitrate(s.bitrate());
item->key.append(ContainerKey(type, s));
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
const int bitrate = qMax(0, s.bitrate());
item->display_text = QString::number(bitrate);
item->sort_text = SortTextForNumber(bitrate) + " ";
@@ -1593,16 +1594,16 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy type, const bool sig
}
}
FinishItem(type, signal, create_divider, parent, item);
FinishItem(group_by, signal, create_divider, parent, item);
if (s.url().scheme() == "cdda") item->lazy_loaded = true;
return item;
}
void CollectionModel::FinishItem(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item) {
void CollectionModel::FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item) {
if (type == GroupBy_None) item->lazy_loaded = true;
if (group_by == GroupBy_None) item->lazy_loaded = true;
if (signal) {
endInsertRows();
@@ -1610,7 +1611,7 @@ 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);
QString divider_key = DividerKey(group_by, item);
if (!divider_key.isEmpty()) {
item->sort_text.prepend(divider_key + " ");
}
@@ -1622,7 +1623,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->display_text = DividerDisplayText(group_by, divider_key);
divider->sort_text = divider_key + " ";
divider->lazy_loaded = true;
@@ -1876,12 +1877,15 @@ bool CollectionModel::canFetchMore(const QModelIndex &parent) const {
}
void CollectionModel::SetGroupBy(const Grouping g) {
void CollectionModel::SetGroupBy(const Grouping g, const std::optional<bool> separate_albums_by_grouping) {
group_by_ = g;
if (separate_albums_by_grouping) {
separate_albums_by_grouping_ = separate_albums_by_grouping.value();
}
ResetAsync();
emit GroupingChanged(g);
emit GroupingChanged(g, separate_albums_by_grouping_);
}

View File

@@ -24,6 +24,8 @@
#include "config.h"
#include <optional>
#include <QtGlobal>
#include <QObject>
#include <QAbstractItemModel>
@@ -64,8 +66,6 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
explicit CollectionModel(CollectionBackend *backend, Application *app, QObject *parent = nullptr);
~CollectionModel() override;
static const char *kSavedGroupingsSettingsGroup;
static const int kPrettyCoverSize;
static const char *kPixmapDiskCacheDir;
@@ -160,9 +160,6 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
// Whether or not to show letters heading in the collection view
void set_show_dividers(const bool show_dividers);
// Save the current grouping
void SaveGrouping(const QString &name);
// Reload settings.
void ReloadSettings();
@@ -195,15 +192,15 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
void ExpandAll(CollectionItem *item = nullptr) const;
const CollectionModel::Grouping GetGroupBy() const { return group_by_; }
void SetGroupBy(const CollectionModel::Grouping g);
void SetGroupBy(const CollectionModel::Grouping g, const std::optional<bool> separate_albums_by_grouping = std::optional<bool>());
static QString ContainerKey(const GroupBy type, const Song &song);
static QString ContainerKey(const GroupBy group_by, const bool separate_albums_by_grouping, const Song &song);
signals:
void TotalSongCountUpdated(int count);
void TotalArtistCountUpdated(int count);
void TotalAlbumCountUpdated(int count);
void GroupingChanged(CollectionModel::Grouping g);
void GroupingChanged(CollectionModel::Grouping g, bool separate_albums_by_grouping);
public slots:
void SetFilterAge(const int age);
@@ -247,22 +244,22 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
// Functions for working with queries and creating items.
// When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items.
// Filters are added for each parent item, restricting the songs returned to a particular album or artist for example.
static void InitQuery(const GroupBy type, CollectionQuery *q);
static void FilterQuery(const GroupBy type, CollectionItem *item, CollectionQuery *q);
static void InitQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQuery *q);
static void FilterQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQuery *q);
// Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
CollectionItem *ItemFromQuery(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
CollectionItem *ItemFromSong(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level);
CollectionItem *ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
CollectionItem *ItemFromSong(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level);
// The "Various Artists" node is an annoying special case.
CollectionItem *CreateCompilationArtistNode(const bool signal, CollectionItem *parent);
// Helpers for ItemFromQuery and ItemFromSong
CollectionItem *InitItem(const GroupBy type, const bool signal, CollectionItem *parent, const int container_level);
void FinishItem(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item);
CollectionItem *InitItem(const GroupBy group_by, const bool signal, CollectionItem *parent, const int container_level);
void FinishItem(const GroupBy group_by, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item);
static QString DividerKey(const GroupBy type, CollectionItem *item);
static QString DividerDisplayText(const GroupBy type, const QString &key);
static QString DividerKey(const GroupBy group_by, CollectionItem *item);
static QString DividerDisplayText(const GroupBy group_by, const QString &key);
// Helpers
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
@@ -284,6 +281,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
QueryOptions query_options_;
Grouping group_by_;
bool separate_albums_by_grouping_;
// Keyed on database ID
QMap<int, CollectionItem*> song_nodes_;
@@ -310,7 +308,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
AlbumCoverLoaderOptions cover_loader_options_;
typedef QPair<CollectionItem*, QString> ItemAndCacheKey;
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
QMap<quint64, ItemAndCacheKey> pending_art_;
QSet<QString> pending_cache_keys_;
};

View File

@@ -50,9 +50,9 @@
#include "core/application.h"
#include "core/iconloader.h"
#include "core/mimedata.h"
#include "core/utilities.h"
#include "core/musicstorage.h"
#include "core/deletefiles.h"
#include "utilities/filemanagerutils.h"
#include "collection.h"
#include "collectionbackend.h"
#include "collectiondirectorymodel.h"

View File

@@ -38,20 +38,18 @@
#include <QList>
#include <QSet>
#include <QTimer>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QImage>
#include <QSettings>
#include <QtDebug>
#include "core/filesystemwatcherinterface.h"
#include "core/logging.h"
#include "core/timeconstants.h"
#include "core/tagreaderclient.h"
#include "core/taskmanager.h"
#include "core/imageutils.h"
#include "utilities/imageutils.h"
#include "utilities/timeconstants.h"
#include "directory.h"
#include "collectionbackend.h"
#include "collectionwatcher.h"
@@ -177,7 +175,7 @@ void CollectionWatcher::ReloadSettings() {
}
}
if (mark_songs_unavailable_ && !periodic_scan_timer_->isActive()) {
if (monitor_ && scan_on_startup_ && mark_songs_unavailable_ && !periodic_scan_timer_->isActive()) {
periodic_scan_timer_->start();
}
else if (!mark_songs_unavailable_ && periodic_scan_timer_->isActive()) {
@@ -444,7 +442,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
QStringList files_on_disk;
SubdirectoryList my_new_subdirs;
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist any more.
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore.
// If one has been removed, "rescan" it to get the deleted songs
SubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
for (const Subdirectory &prev_subdir : previous_subdirs) {
@@ -464,7 +462,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
if (child_info.isDir()) {
if (!t->HasSeenSubdir(child)) {
// We haven't seen this subdirectory before - add it to a list and later we'll tell the backend about it and scan it.
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
Subdirectory new_subdir;
new_subdir.directory_id = -1;
new_subdir.path = child;
@@ -761,7 +759,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
const bool cue_deleted,
ScanTransaction *t) {
// If a CUE got deleted, we turn it's first section into the new 'raw' (cueless) song and we just remove the rest of the sections from the collection
// If a CUE got deleted, we turn it's first section into the new 'raw' (cueless) song, and we just remove the rest of the sections from the collection
const Song &matching_song = matching_songs.first();
if (cue_deleted) {
for (const Song &song : matching_songs) {
@@ -805,7 +803,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
// Ignore FILEs pointing to other media files.
// Also, watch out for incorrect media files.
// Playlist parser for CUEs considers every entry in sheet valid and we don't want invalid media getting into collection!
// Playlist parser for CUEs considers every entry in sheet valid, and we don't want invalid media getting into collection!
QString file_nfd = file.normalized(QString::NormalizationForm_D);
SongList cue_congs = cue_parser_->Load(&cue_file, matching_cue, path, false);
cue_file.close();

View File

@@ -40,7 +40,7 @@ struct Directory {
};
Q_DECLARE_METATYPE(Directory)
typedef QList<Directory> DirectoryList;
using DirectoryList = QList<Directory>;
Q_DECLARE_METATYPE(DirectoryList)
@@ -53,7 +53,7 @@ struct Subdirectory {
};
Q_DECLARE_METATYPE(Subdirectory)
typedef QList<Subdirectory> SubdirectoryList;
using SubdirectoryList = QList<Subdirectory>;
Q_DECLARE_METATYPE(SubdirectoryList)
#endif // DIRECTORY_H

View File

@@ -63,11 +63,7 @@ struct tag_group_by {};
class GroupByDialogPrivate {
private:
typedef multi_index_container<
Mapping,
indexed_by<
ordered_unique<tag<tag_index>, member<Mapping, int, &Mapping::combo_box_index> >,
ordered_unique<tag<tag_group_by>, member<Mapping, CollectionModel::GroupBy, &Mapping::group_by> > > > MappingContainer;
using MappingContainer = multi_index_container<Mapping, indexed_by<ordered_unique<tag<tag_index>, member<Mapping, int, &Mapping::combo_box_index>>, ordered_unique<tag<tag_group_by>, member<Mapping, CollectionModel::GroupBy, &Mapping::group_by>>>>;
public:
MappingContainer mapping_;
@@ -108,22 +104,31 @@ GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_Grou
GroupByDialog::~GroupByDialog() = default;
void GroupByDialog::Reset() {
ui_->combobox_first->setCurrentIndex(2); // Album Artist
ui_->combobox_second->setCurrentIndex(3); // Album
ui_->combobox_second->setCurrentIndex(4); // Album Disc
ui_->combobox_third->setCurrentIndex(0); // None
ui_->checkbox_separate_albums_by_grouping->setChecked(false);
}
void GroupByDialog::accept() {
emit Accepted(CollectionModel::Grouping(
p_->mapping_.get<tag_index>().find(ui_->combobox_first->currentIndex())->group_by,
p_->mapping_.get<tag_index>().find(ui_->combobox_second->currentIndex())->group_by,
p_->mapping_.get<tag_index>().find(ui_->combobox_third->currentIndex())->group_by)
p_->mapping_.get<tag_index>().find(ui_->combobox_third->currentIndex())->group_by),
ui_->checkbox_separate_albums_by_grouping->isChecked()
);
QDialog::accept();
}
void GroupByDialog::CollectionGroupingChanged(const CollectionModel::Grouping g) {
void GroupByDialog::CollectionGroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping) {
ui_->combobox_first->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[0])->combo_box_index);
ui_->combobox_second->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[1])->combo_box_index);
ui_->combobox_third->setCurrentIndex(p_->mapping_.get<tag_group_by>().find(g[2])->combo_box_index);
ui_->checkbox_separate_albums_by_grouping->setChecked(separate_albums_by_grouping);
}

View File

@@ -45,11 +45,11 @@ class GroupByDialog : public QDialog {
~GroupByDialog() override;
public slots:
void CollectionGroupingChanged(const CollectionModel::Grouping g);
void CollectionGroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
void accept() override;
signals:
void Accepted(CollectionModel::Grouping g);
void Accepted(CollectionModel::Grouping g, bool separate_albums_by_grouping);
private slots:
void Reset();

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>354</width>
<height>246</height>
<width>394</width>
<height>273</height>
</rect>
</property>
<property name="windowTitle">
@@ -370,6 +370,13 @@
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_separate_albums_by_grouping">
<property name="text">
<string>Separate albums by grouping tag</string>
</property>
</widget>
</item>
<item>
<spacer name="spacer_bottom">
<property name="orientation">

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2015, Nick Lanham <nick@afternight.org>
* Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2022, 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,35 +22,31 @@
#include "config.h"
#include <QDialog>
#include <QWidget>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include <QAbstractItemModel>
#include <QIODevice>
#include <QDataStream>
#include <QByteArray>
#include <QList>
#include <QVariant>
#include <QByteArray>
#include <QString>
#include <QStringList>
#include <QIODevice>
#include <QDataStream>
#include <QKeySequence>
#include <QPushButton>
#include <QTreeView>
#include <QSettings>
#include <QtDebug>
#include "core/logging.h"
#include "core/iconloader.h"
#include "collectionfilterwidget.h"
#include "collectionmodel.h"
#include "savedgroupingmanager.h"
#include "ui_savedgroupingmanager.h"
SavedGroupingManager::SavedGroupingManager(QWidget *parent)
const char *SavedGroupingManager::kSavedGroupingsSettingsGroup = "SavedGroupings";
SavedGroupingManager::SavedGroupingManager(const QString &saved_groupings_settings_group, QWidget *parent)
: QDialog(parent),
ui_(new Ui_SavedGroupingManager),
model_(new QStandardItemModel(0, 4, this)),
filter_(nullptr) {
saved_groupings_settings_group_(saved_groupings_settings_group) {
ui_->setupUi(this);
@@ -71,7 +67,17 @@ SavedGroupingManager::SavedGroupingManager(QWidget *parent)
SavedGroupingManager::~SavedGroupingManager() {
delete ui_;
delete model_;
}
QString SavedGroupingManager::GetSavedGroupingsSettingsGroup(const QString &settings_group) {
if (settings_group.isEmpty() || settings_group == CollectionSettingsPage::kSettingsGroup) {
return kSavedGroupingsSettingsGroup;
}
else {
return QString(kSavedGroupingsSettingsGroup) + "_" + settings_group;
}
}
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g) {
@@ -151,7 +157,7 @@ void SavedGroupingManager::UpdateModel() {
model_->setRowCount(0); // don't use clear, it deletes headers
QSettings s;
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
s.beginGroup(saved_groupings_settings_group_);
int version = s.value("version").toInt();
if (version == 1) {
QStringList saved = s.childKeys();
@@ -186,7 +192,7 @@ void SavedGroupingManager::Remove() {
if (ui_->list->selectionModel()->hasSelection()) {
QSettings s;
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
s.beginGroup(saved_groupings_settings_group_);
for (const QModelIndex &idx : ui_->list->selectionModel()->selectedRows()) {
if (idx.isValid()) {
qLog(Debug) << "Remove saved grouping: " << model_->item(idx.row(), 0)->text();
@@ -196,7 +202,8 @@ void SavedGroupingManager::Remove() {
s.endGroup();
}
UpdateModel();
filter_->UpdateGroupByActions();
emit UpdateGroupByActions();
}

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2015, Nick Lanham <nick@afternight.org>
* Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-2022, 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
@@ -40,14 +40,20 @@ class SavedGroupingManager : public QDialog {
Q_OBJECT
public:
explicit SavedGroupingManager(QWidget *parent = nullptr);
explicit SavedGroupingManager(const QString &saved_groupings_settings_group, QWidget *parent = nullptr);
~SavedGroupingManager() override;
static const char *kSavedGroupingsSettingsGroup;
static QString GetSavedGroupingsSettingsGroup(const QString &settings_group);
void UpdateModel();
void SetFilter(CollectionFilterWidget *filter) { filter_ = filter; }
static QString GroupByToString(const CollectionModel::GroupBy g);
signals:
void UpdateGroupByActions();
private slots:
void UpdateButtonState();
void Remove();
@@ -55,7 +61,7 @@ class SavedGroupingManager : public QDialog {
private:
Ui_SavedGroupingManager *ui_;
QStandardItemModel *model_;
CollectionFilterWidget *filter_;
QString saved_groupings_settings_group_;
};
#endif // SAVEDGROUPINGMANAGER_H

View File

@@ -21,6 +21,8 @@
#cmakedefine HAVE_MUSICBRAINZ
#cmakedefine HAVE_GLOBALSHORTCUTS
#cmakedefine HAVE_X11_GLOBALSHORTCUTS
#cmakedefine HAVE_ICU
#cmakedefine USE_INSTALL_PREFIX
#cmakedefine HAVE_GSTREAMER

View File

@@ -37,7 +37,7 @@
#include <QContextMenuEvent>
#include <QPaintEvent>
#include "core/imageutils.h"
#include "utilities/imageutils.h"
#include "covermanager/albumcoverchoicecontroller.h"
#include "contextview.h"

View File

@@ -51,8 +51,9 @@
#include "core/application.h"
#include "core/player.h"
#include "core/song.h"
#include "core/utilities.h"
#include "core/iconloader.h"
#include "utilities/strutils.h"
#include "utilities/timeutils.h"
#include "widgets/resizabletextedit.h"
#include "engine/engine_fwd.h"
#include "engine/enginebase.h"
@@ -434,8 +435,8 @@ void ContextView::NoSong() {
widget_album_->show();
}
textedit_top_->setStyleSheet("font: 20pt 'Open Sans', 'FreeSans', 'FreeSerif', 'Liberation Serif'; font-weight: Regular;");
textedit_top_->setText(tr("No song playing"));
textedit_top_->setFont(QFont(font_headline_, font_size_headline_ * 1.6));
textedit_top_->SetText(tr("No song playing"));
QString html;
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
@@ -450,27 +451,28 @@ void ContextView::NoSong() {
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
html += "<br />";
label_stop_summary_->setStyleSheet(QString("font: %1pt \"%2\"; font-weight: regular;").arg(font_size_normal_).arg(font_normal_));
label_stop_summary_->setFont(QFont(font_normal_, font_size_normal_));
label_stop_summary_->setText(html);
}
void ContextView::UpdateFonts() {
QString font_style = QString("font: %2pt \"%1\"; font-weight: regular;").arg(font_normal_).arg(font_size_normal_);
QFont font(font_normal_, font_size_normal_);
font.setBold(false);
for (QLabel *l : labels_play_all_) {
l->setStyleSheet(font_style);
l->setFont(font);
}
for (QTextEdit *e : textedit_play_) {
e->setStyleSheet(font_style);
e->setFont(font);
}
}
void ContextView::SetSong() {
textedit_top_->setStyleSheet(QString("font: %2pt \"%1\"; font-weight: regular;").arg(font_headline_).arg(font_size_headline_));
textedit_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
textedit_top_->setFont(QFont(font_headline_, font_size_headline_));
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
label_stop_summary_->clear();
@@ -584,7 +586,7 @@ void ContextView::SetSong() {
}
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
textedit_play_lyrics_->setText(lyrics_);
textedit_play_lyrics_->SetText(lyrics_);
textedit_play_lyrics_->show();
}
else {
@@ -599,7 +601,7 @@ void ContextView::SetSong() {
void ContextView::UpdateSong(const Song &song) {
textedit_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true)));
textedit_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());
@@ -655,6 +657,8 @@ void ContextView::UpdateSong(const Song &song) {
song_playing_ = song;
widget_stacked_->updateGeometry();
}
void ContextView::ResetSong() {
@@ -681,7 +685,7 @@ void ContextView::UpdateLyrics(const quint64 id, const QString &provider, const
lyrics_id_ = -1;
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
textedit_play_lyrics_->setText(lyrics_);
textedit_play_lyrics_->SetText(lyrics_);
textedit_play_lyrics_->show();
}
else {

View File

@@ -22,7 +22,6 @@
#include <QApplication>
#include <QObject>
#include <QVariant>
#include <QPalette>
#include <QColor>
#include <QSettings>

View File

@@ -29,12 +29,10 @@
#include <QObject>
#include <QThread>
#include <QVariant>
#include <QString>
#include "core/lazy.h"
#include "core/tagreaderclient.h"
#include "core/song.h"
#include "core/logging.h"
#include "database.h"

View File

@@ -24,7 +24,6 @@
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include <type_traits>
#include <QtGlobal>
#include <QObject>

View File

@@ -21,7 +21,6 @@
#include "config.h"
#include <cstddef>
#include <sqlite3.h>
#include <boost/scope_exit.hpp>
@@ -31,20 +30,15 @@
#include <QIODevice>
#include <QDir>
#include <QFile>
#include <QList>
#include <QByteArray>
#include <QVariant>
#include <QString>
#include <QStringBuilder>
#include <QStringList>
#include <QRegularExpression>
#include <QUrl>
#include <QSqlDriver>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QStandardPaths>
#include <QtDebug>
#include "core/logging.h"
#include "taskmanager.h"
@@ -373,7 +367,6 @@ void Database::ExecSchemaCommandsFromFile(QSqlDatabase &db, const QString &filen
QFile schema_file(filename);
if (!schema_file.open(QIODevice::ReadOnly)) {
qFatal("Couldn't open schema file %s for reading: %s", filename.toUtf8().constData(), schema_file.errorString().toUtf8().constData());
return;
}
QByteArray data = schema_file.readAll();
QString schema = QString::fromUtf8(data);
@@ -391,9 +384,9 @@ void Database::ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int s
QStringList commands;
commands = schema.split(QRegularExpression("; *\n\n"));
// We don't want this list to reflect possible DB schema changes so we initialize it before executing any statements.
// If no outer transaction is provided the song tables need to be queried before beginning an inner transaction! Otherwise
// DROP TABLE commands on song tables may fail due to database locks.
// We don't want this list to reflect possible DB schema changes, so we initialize it before executing any statements.
// If no outer transaction is provided the song tables need to be queried before beginning an inner transaction!
// Otherwise DROP TABLE commands on song tables may fail due to database locks.
const QStringList song_tables(SongsTables(db, schema_version));
if (!in_transaction) {
@@ -543,7 +536,8 @@ void Database::DoBackup() {
bool Database::OpenDatabase(const QString &filename, sqlite3 **connection) {
int ret = sqlite3_open(filename.toUtf8(), connection);
const QByteArray filename_data = filename.toUtf8();
int ret = sqlite3_open(filename_data.constData(), connection);
if (ret != 0) {
if (*connection) {
const char *error_message = sqlite3_errmsg(*connection);

View File

@@ -27,17 +27,14 @@
#include <QFile>
#include <QFileInfo>
#include <QString>
#include <QUrl>
#include <QtDebug>
#include "core/logging.h"
#include "utilities.h"
#include "song.h"
#include "utilities/fileutils.h"
#include "musicstorage.h"
#include "filesystemmusicstorage.h"
FilesystemMusicStorage::FilesystemMusicStorage(const QString &root, const std::optional<int> collection_directory_id) : root_(root), collection_directory_id_(collection_directory_id) {}
FilesystemMusicStorage::FilesystemMusicStorage(const Song::Source source, const QString &root, const std::optional<int> collection_directory_id) : source_(source), root_(root), collection_directory_id_(collection_directory_id) {}
bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
@@ -61,7 +58,7 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
return false;
}
// Remove the destination file if it exists and we want to overwrite
// Remove the destination file if it exists, and we want to overwrite
if (job.overwrite_) {
if (dest.exists()) QFile::remove(dest.absoluteFilePath());
if (!cover_dest.filePath().isEmpty() && cover_dest.exists()) QFile::remove(cover_dest.absoluteFilePath());
@@ -109,12 +106,7 @@ bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
if (job.use_trash_) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
if (fileInfo.isDir()) {
return Utilities::MoveToTrashRecursive(path);
}
else {
return QFile::moveToTrash(path);
}
return QFile::moveToTrash(path);
#else
return false;
#endif

View File

@@ -28,12 +28,14 @@
#include <QString>
#include "song.h"
#include "musicstorage.h"
class FilesystemMusicStorage : public virtual MusicStorage {
public:
explicit FilesystemMusicStorage(const QString &root, const std::optional<int> collection_directory_id = std::optional<int>());
explicit FilesystemMusicStorage(const Song::Source source, const QString &root, const std::optional<int> collection_directory_id = std::optional<int>());
Song::Source source() const override { return source_; }
QString LocalPath() const override { return root_; }
std::optional<int> collection_directory_id() const override { return collection_directory_id_; }
@@ -41,6 +43,7 @@ class FilesystemMusicStorage : public virtual MusicStorage {
bool DeleteFromStorage(const DeleteJob &job) override;
private:
Song::Source source_;
QString root_;
std::optional<int> collection_directory_id_;

View File

@@ -40,7 +40,7 @@ class FileSystemWatcherInterface : public QObject {
static FileSystemWatcherInterface *Create(QObject *parent = nullptr);
signals:
void PathChanged(const QString &path);
void PathChanged(QString path);
};
#endif

View File

@@ -52,7 +52,7 @@ void IconLoader::Init() {
}
QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_size, const int max_size) {
QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fixed_size, const int min_size, const int max_size) {
QIcon ret;
@@ -69,7 +69,7 @@ QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_
sizes << fixed_size;
}
if (system_icons_) {
if (system_icon && system_icons_) {
IconMapper::IconProperties icon_prop;
if (IconMapper::iconmapper_.contains(name)) {
icon_prop = IconMapper::iconmapper_[name];

View File

@@ -26,7 +26,7 @@
class IconLoader {
public:
static void Init();
static QIcon Load(const QString &name, const int fixed_size = 0, const int min_size = 0, const int max_size = 0);
static QIcon Load(const QString &name, const bool system_icon = true, const int fixed_size = 0, const int min_size = 0, const int max_size = 0);
private:
explicit IconLoader() {}
static bool system_icons_;

View File

@@ -17,7 +17,8 @@
*
*/
#pragma once
#ifndef ICONMAPPER_H
#define ICONMAPPER_H
#include "config.h"
@@ -135,3 +136,6 @@ static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non
};
} // namespace IconMapper
#endif // ICONMAPPER_H

View File

@@ -1,29 +1,44 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, 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 MAC_STARTUP_H
#define MAC_STARTUP_H
#include "config.h"
#include <CoreFoundation/CFDictionary.h>
#include <QString>
#include <QWidget>
#include <QKeySequence>
class QObject;
class QWidget;
#ifdef __OBJC__
@class NSEvent;
#else
class NSEvent;
#endif
class PlatformInterface;
class GlobalShortcutsBackendMacOS;
class PlatformInterface {
public:
PlatformInterface() = default;
virtual ~PlatformInterface() {}
// Called when the application should show itself.
virtual void Activate() = 0;
virtual bool LoadUrl(const QString &url) = 0;
private:
Q_DISABLE_COPY(PlatformInterface)
};
namespace mac {
void MacMain();
@@ -32,6 +47,9 @@ void SetApplicationHandler(PlatformInterface *handler);
void EnableFullScreen(const QWidget &main_window);
QKeySequence KeySequenceFromNSEvent(NSEvent *event);
void DumpDictionary(CFDictionaryRef dict);
} // namespace mac
#endif

View File

@@ -45,13 +45,12 @@
#include "config.h"
#include "platforminterface.h"
#include "mac_delegate.h"
#include "mac_startup.h"
#include "mac_utilities.h"
#include "utilities.h"
#include "scoped_cftyperef.h"
#include "core/logging.h"
#include "core/scoped_nsautorelease_pool.h"
#include "scoped_nsautorelease_pool.h"
#include "globalshortcuts/globalshortcutsmanager.h"
#include "globalshortcuts/globalshortcutsbackend-macos.h"

View File

@@ -82,7 +82,7 @@ class MacSystemTrayIconPrivate {
// Don't look now.
// This must be called after our custom NSApplicationDelegate has been set.
[(AppDelegate*)([NSApp delegate]) setDockMenu:dock_menu_];
[reinterpret_cast<AppDelegate*>([NSApp delegate]) setDockMenu:dock_menu_];
ClearNowPlaying();
}

View File

@@ -74,13 +74,10 @@
#include <QClipboard>
#include "core/logging.h"
#include "core/networkaccessmanager.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "utilities.h"
#include "timeconstants.h"
#include "commandlineoptions.h"
#include "mimedata.h"
#include "iconloader.h"
@@ -95,10 +92,15 @@
#include "filesystemmusicstorage.h"
#include "deletefiles.h"
#ifdef Q_OS_MACOS
# include "mac_startup.h"
# include "macsystemtrayicon.h"
#else
# include "qtsystemtrayicon.h"
#endif
#include "networkaccessmanager.h"
#include "utilities/envutils.h"
#include "utilities/filemanagerutils.h"
#include "utilities/timeconstants.h"
#include "engine/enginetype.h"
#include "engine/enginebase.h"
#include "engine/engine_fwd.h"
@@ -135,6 +137,7 @@
#include "playlist/playlistmanager.h"
#include "playlist/playlistsequence.h"
#include "playlist/playlistview.h"
#include "playlist/playlistfilter.h"
#include "queue/queue.h"
#include "queue/queueview.h"
#include "playlistparsers/playlistparser.h"
@@ -222,7 +225,11 @@ const int kTrackPositionUpdateTimeMs = 1000;
#ifdef HAVE_QTSPARKLE
# ifdef _MSC_VER
# ifdef _M_X64
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-msvc-x64";
# else
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-msvc-x86";
# endif
# else
# ifdef __x86_64__
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-mingw-x64";
@@ -358,24 +365,24 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
// Add tabs to the fancy tab widget
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry"), tr("Context"));
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music"), tr("Collection"));
ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps"), tr("Queue"));
ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist"), tr("Playlists"));
ui_->tabs->AddTab(smartplaylists_view_, "smartplaylists", IconLoader::Load("view-media-playlist"), tr("Smart playlists"));
ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open"), tr("Files"));
ui_->tabs->AddTab(radio_view_, "radios", IconLoader::Load("radio"), tr("Radios"));
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry", false), tr("Context"));
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music", false), tr("Collection"));
ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps", false), tr("Queue"));
ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist", false), tr("Playlists"));
ui_->tabs->AddTab(smartplaylists_view_, "smartplaylists", IconLoader::Load("view-media-playlist", false), tr("Smart playlists"));
ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open", false), tr("Files"));
ui_->tabs->AddTab(radio_view_, "radios", IconLoader::Load("radio", false), tr("Radios"));
#ifndef Q_OS_WIN
ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device"), tr("Devices"));
ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device", false), tr("Devices"));
#endif
#ifdef HAVE_SUBSONIC
ui_->tabs->AddTab(subsonic_view_, "subsonic", IconLoader::Load("subsonic"), tr("Subsonic"));
ui_->tabs->AddTab(subsonic_view_, "subsonic", IconLoader::Load("subsonic", false), tr("Subsonic"));
#endif
#ifdef HAVE_TIDAL
ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal"), tr("Tidal"));
ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal", false), tr("Tidal"));
#endif
#ifdef HAVE_QOBUZ
ui_->tabs->AddTab(qobuz_view_, "qobuz", IconLoader::Load("qobuz"), tr("Qobuz"));
ui_->tabs->AddTab(qobuz_view_, "qobuz", IconLoader::Load("qobuz", false), tr("Qobuz"));
#endif
// Add the playing widget to the fancy tab widget
@@ -394,8 +401,8 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
app_->player()->SetEqualizer(equalizer_.get());
app_->player()->Init();
EngineChanged(app_->player()->engine()->type());
int volume = static_cast<int>(app_->player()->GetVolume());
ui_->volume->setValue(volume);
const uint volume = app_->player()->GetVolume();
ui_->volume->SetValue(volume);
VolumeChanged(volume);
// Models
@@ -578,7 +585,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
ui_->stop_button->setMenu(stop_menu);
// Player connections
QObject::connect(ui_->volume, &VolumeSlider::valueChanged, app_->player(), &Player::SetVolume);
QObject::connect(ui_->volume, &VolumeSlider::valueChanged, app_->player(), &Player::SetVolumeFromSlider);
QObject::connect(app_->player(), &Player::EngineChanged, this, &MainWindow::EngineChanged);
QObject::connect(app_->player(), &Player::Error, this, &MainWindow::ShowErrorDialog);
@@ -601,7 +608,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
QObject::connect(app_->player(), &Player::Stopped, osd_, &OSDBase::Stopped);
QObject::connect(app_->player(), &Player::PlaylistFinished, osd_, &OSDBase::PlaylistFinished);
QObject::connect(app_->player(), &Player::VolumeChanged, osd_, &OSDBase::VolumeChanged);
QObject::connect(app_->player(), &Player::VolumeChanged, ui_->volume, &VolumeSlider::setValue);
QObject::connect(app_->player(), &Player::VolumeChanged, ui_->volume, &VolumeSlider::SetValue);
QObject::connect(app_->player(), &Player::ForceShowOSD, this, &MainWindow::ForceShowOSD);
QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &MainWindow::SongChanged);
@@ -963,7 +970,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
if (tray_icon_->IsSystemTrayAvailable() && tray_icon_->isVisible()) {
break;
}
// fallthrough
[[fallthrough]];
case BehaviourSettingsPage::Startup_Remember:
default: {
@@ -1196,6 +1203,7 @@ void MainWindow::SaveSettings() {
SaveGeometry();
SavePlaybackStatus();
app_->player()->SaveVolume();
ui_->tabs->SaveSettings(kSettingsGroup);
ui_->playlist->view()->SaveSettings();
app_->scrobbler()->WriteCache();
@@ -1346,7 +1354,7 @@ void MainWindow::SendNowPlaying() {
}
void MainWindow::VolumeChanged(const int volume) {
void MainWindow::VolumeChanged(const uint volume) {
ui_->action_mute->setChecked(volume == 0);
tray_icon_->MuteButtonStateChanged(volume == 0);
}
@@ -1509,9 +1517,9 @@ void MainWindow::PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscro
if (!idx.isValid()) return;
int row = idx.row();
if (idx.model() == app_->playlist_manager()->current()->proxy()) {
if (idx.model() == app_->playlist_manager()->current()->filter()) {
// 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();
row = app_->playlist_manager()->current()->filter()->mapToSource(idx).row();
}
app_->playlist_manager()->SetActiveToCurrent();
@@ -1524,9 +1532,9 @@ void MainWindow::PlaylistDoubleClick(const QModelIndex &idx) {
if (!idx.isValid()) return;
QModelIndex source_idx = idx;
if (idx.model() == app_->playlist_manager()->current()->proxy()) {
if (idx.model() == app_->playlist_manager()->current()->filter()) {
// The index was in the proxy model (might've been filtered), so we need to get the actual row in the source model.
source_idx = app_->playlist_manager()->current()->proxy()->mapToSource(idx);
source_idx = app_->playlist_manager()->current()->filter()->mapToSource(idx);
}
switch (doubleclick_playlist_addmode_) {
@@ -1765,7 +1773,7 @@ void MainWindow::AddToPlaylistFromAction(QAction *action) {
// Get the selected playlist items
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (!item) continue;
@@ -1804,8 +1812,8 @@ void MainWindow::PlaylistMenuHidden() {
void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &index) {
QModelIndex source_index = index;
if (index.model() == app_->playlist_manager()->current()->proxy()) {
source_index = app_->playlist_manager()->current()->proxy()->mapToSource(index);
if (index.model() == app_->playlist_manager()->current()->filter()) {
source_index = app_->playlist_manager()->current()->filter()->mapToSource(index);
}
playlist_menu_index_ = source_index;
@@ -1844,7 +1852,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
for (const QModelIndex &idx : selection) {
const QModelIndex src_idx = app_->playlist_manager()->current()->proxy()->mapToSource(idx);
const QModelIndex src_idx = app_->playlist_manager()->current()->filter()->mapToSource(idx);
if (!src_idx.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(src_idx.row());
@@ -2048,7 +2056,7 @@ void MainWindow::RescanSongs() {
SongList songs;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
if (!item) continue;
@@ -2073,7 +2081,7 @@ void MainWindow::EditTracks() {
PlaylistItemList items;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
if (!item) continue;
@@ -2120,7 +2128,7 @@ void MainWindow::RenumberTracks() {
}
for (const QModelIndex &proxy_index : indexes) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (!item) continue;
@@ -2151,7 +2159,7 @@ void MainWindow::SelectionSetValue() {
QVariant column_value = app_->playlist_manager()->current()->data(playlist_menu_index_);
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (!item) continue;
@@ -2265,7 +2273,7 @@ void MainWindow::ShowInCollection() {
SongList songs;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (item && item->IsLocalCollectionItem()) {
@@ -2518,7 +2526,7 @@ void MainWindow::AddFilesToTranscoder() {
QStringList filenames;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
if (!item) continue;
@@ -2630,7 +2638,7 @@ void MainWindow::PlaylistOrganizeSelected(const bool copy) {
SongList songs;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (!item) continue;
@@ -2652,7 +2660,7 @@ void MainWindow::PlaylistOpenInBrowser() {
QList<QUrl> urls;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
urls << QUrl(source_index.sibling(source_index.row(), Playlist::Column_Filename).data().toString());
}
@@ -2665,7 +2673,7 @@ void MainWindow::PlaylistCopyUrl() {
QList<QUrl> urls;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (!item) continue;
@@ -2686,7 +2694,7 @@ void MainWindow::PlaylistQueue() {
QModelIndexList indexes;
indexes.reserve(selected_rows.count());
for (const QModelIndex &proxy_index : selected_rows) {
indexes << app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
indexes << app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
}
app_->playlist_manager()->current()->queue()->ToggleTracks(indexes);
@@ -2699,7 +2707,7 @@ void MainWindow::PlaylistQueuePlayNext() {
QModelIndexList indexes;
indexes.reserve(selected_rows.count());
for (const QModelIndex &proxy_index : selected_rows) {
indexes << app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
indexes << app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
}
app_->playlist_manager()->current()->queue()->InsertFirst(indexes);
@@ -2712,7 +2720,7 @@ void MainWindow::PlaylistSkip() {
QModelIndexList indexes;
indexes.reserve(selected_rows.count());
for (const QModelIndex &proxy_index : selected_rows) {
indexes << app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
indexes << app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
}
app_->playlist_manager()->current()->SkipTracks(indexes);
@@ -2726,7 +2734,7 @@ void MainWindow::PlaylistCopyToDevice() {
SongList songs;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (!item) continue;
@@ -2875,7 +2883,7 @@ void MainWindow::PlaylistViewSelectionModelChanged() {
void MainWindow::PlaylistCurrentChanged(const QModelIndex &proxy_current) {
const QModelIndex source_current = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_current);
const QModelIndex source_current = app_->playlist_manager()->current()->filter()->mapToSource(proxy_current);
// If the user moves the current index using the keyboard and then presses
// F2, we don't want that editing the last column that was right clicked on.
@@ -2927,7 +2935,7 @@ void MainWindow::AutoCompleteTags() {
// Get the selected songs and start fetching tags for them
SongList songs;
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue;
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
if (!item) continue;
@@ -3117,12 +3125,12 @@ 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));
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", true, 22));
else
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22)); // TODO: Create a faint version of the icon
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", true, 22)); // TODO: Create a faint version of the icon
}
else {
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble-disabled", 22));
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble-disabled", true, 22));
}
}
@@ -3144,7 +3152,7 @@ void MainWindow::PlaylistDelete() {
QStringList files;
bool is_current_item = false;
for (const QModelIndex &proxy_idx : ui_->playlist->view()->selectionModel()->selectedRows()) {
QModelIndex source_idx = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_idx);
QModelIndex source_idx = app_->playlist_manager()->current()->filter()->mapToSource(proxy_idx);
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row());
if (!item || !item->Metadata().url().isLocalFile()) continue;
QString filename = item->Metadata().url().toLocalFile();
@@ -3167,7 +3175,7 @@ void MainWindow::PlaylistDelete() {
app_->player()->Next();
}
std::shared_ptr<MusicStorage> storage = std::make_shared<FilesystemMusicStorage>("/");
std::shared_ptr<MusicStorage> storage = std::make_shared<FilesystemMusicStorage>(Song::Source_LocalFile, "/");
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
//QObject::connect(delete_files, &DeleteFiles::Finished, this, &MainWindow::DeleteFinished);
delete_files->Start(selected_songs);

View File

@@ -48,12 +48,12 @@
#include <QSettings>
#include <QtEvents>
#include "core/lazy.h"
#include "core/tagreaderclient.h"
#include "core/song.h"
#include "lazy.h"
#include "platforminterface.h"
#include "song.h"
#include "tagreaderclient.h"
#include "engine/enginetype.h"
#include "engine/engine_fwd.h"
#include "mac_startup.h"
#include "osd/osdbase.h"
#include "collection/collectionmodel.h"
#include "playlist/playlist.h"
@@ -187,7 +187,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void StopAfterCurrent();
void SongChanged(const Song &song);
void VolumeChanged(const int volume);
void VolumeChanged(const uint volume);
void CopyFilesToCollection(const QList<QUrl> &urls);
void MoveFilesToCollection(const QList<QUrl> &urls);

View File

@@ -448,7 +448,7 @@
<x>0</x>
<y>0</y>
<width>1131</width>
<height>26</height>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menu_music">
@@ -750,7 +750,7 @@
</action>
<action name="action_save_all_playlists">
<property name="text">
<string>&amp;Save all playlists</string>
<string>&amp;Save all playlists...</string>
</property>
</action>
<action name="action_next_playlist">
@@ -903,7 +903,6 @@
</customwidget>
</customwidgets>
<resources>
<include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources>
<connections/>

View File

@@ -71,13 +71,7 @@ struct tag_by_pointer {};
class MergedProxyModelPrivate {
private:
typedef multi_index_container<
Mapping*,
indexed_by<
hashed_unique<tag<tag_by_source>,
member<Mapping, QModelIndex, &Mapping::source_index> >,
ordered_unique<tag<tag_by_pointer>, identity<Mapping*> > > >
MappingContainer;
using MappingContainer = multi_index_container<Mapping *, indexed_by<hashed_unique<tag<tag_by_source>, member<Mapping, QModelIndex, &Mapping::source_index>>, ordered_unique<tag<tag_by_pointer>, identity<Mapping *>>>>;
public:
MappingContainer mappings_;

View File

@@ -21,9 +21,8 @@
#include "config.h"
#include <cstdlib>
#include <memory>
#include <algorithm>
#include <cmath>
#include <QApplication>
#include <QCoreApplication>
@@ -41,17 +40,16 @@
#include <QDBusMessage>
#include <QDBusArgument>
#include <QDBusObjectPath>
#include <QtDebug>
#include "core/logging.h"
#include "mpris_common.h"
#include "mpris2.h"
#include "timeconstants.h"
#include "song.h"
#include "application.h"
#include "player.h"
#include "utilities/timeconstants.h"
#include "engine/enginebase.h"
#include "playlist/playlist.h"
#include "playlist/playlistitem.h"
@@ -60,10 +58,10 @@
#include "covermanager/currentalbumcoverloader.h"
#include "covermanager/albumcoverloaderresult.h"
#include <core/mpris2_player.h>
#include <core/mpris2_playlists.h>
#include <core/mpris2_root.h>
#include <core/mpris2_tracklist.h>
#include "mpris2_player.h"
#include "mpris2_playlists.h"
#include "mpris2_root.h"
#include "mpris2_tracklist.h"
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
arg.beginStructure();
@@ -178,7 +176,9 @@ void Mpris2::EngineStateChanged(Engine::State newState) {
}
void Mpris2::VolumeChanged() { EmitNotification("Volume"); }
void Mpris2::VolumeChanged() {
EmitNotification("Volume");
}
void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); }
@@ -190,15 +190,15 @@ void Mpris2::RepeatModeChanged() {
}
void Mpris2::EmitNotification(const QString &name, const QVariant &val) {
EmitNotification(name, val, "org.mpris.MediaPlayer2.Player");
void Mpris2::EmitNotification(const QString &name, const QVariant &value) {
EmitNotification(name, value, "org.mpris.MediaPlayer2.Player");
}
void Mpris2::EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity) {
void Mpris2::EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity) {
QDBusMessage msg = QDBusMessage::createSignal(kMprisObjectPath, kFreedesktopPath, "PropertiesChanged");
QVariantMap map;
map.insert(name, val);
map.insert(name, value);
QVariantList args = QVariantList() << mprisEntity << map << QStringList();
msg.setArguments(args);
QDBusConnection::sessionBus().send(msg);
@@ -401,6 +401,13 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &re
else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
cover_url = result.temp_cover_url;
}
else if (song.art_manual().isValid() && song.art_manual().isLocalFile() && song.art_manual().path() != Song::kManuallyUnsetCover && song.art_manual().path() != Song::kEmbeddedCover) {
cover_url = song.art_manual();
}
else if (song.art_automatic().isValid() && song.art_automatic().isLocalFile() && song.art_automatic().path() != Song::kManuallyUnsetCover && song.art_automatic().path() != Song::kEmbeddedCover) {
cover_url = song.art_automatic();
}
if (cover_url.isValid()) AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_);
AddMetadata("year", song.year(), &last_metadata_);
@@ -414,8 +421,8 @@ double Mpris2::Volume() const {
return app_->player()->GetVolume() / 100.0;
}
void Mpris2::SetVolume(const double value) {
app_->player()->SetVolume(static_cast<uint>(std::max(std::min(lround(value * 100.0), 100L), 0L)));
void Mpris2::SetVolume(const double volume) {
app_->player()->SetVolume(static_cast<uint>(qBound(0L, lround(volume * 100.0), 100L)));
}
qint64 Mpris2::Position() const {

View File

@@ -45,8 +45,8 @@ class Application;
class Song;
class Playlist;
typedef QList<QVariantMap> TrackMetadata;
typedef QList<QDBusObjectPath> Track_Ids;
using TrackMetadata = QList<QVariantMap>;
using Track_Ids = QList<QDBusObjectPath>;
Q_DECLARE_METATYPE(TrackMetadata)
struct MprisPlaylist {
@@ -54,7 +54,7 @@ struct MprisPlaylist {
QString name;
QString icon; // Uri
};
typedef QList<MprisPlaylist> MprisPlaylistList;
using MprisPlaylistList = QList<MprisPlaylist>;
Q_DECLARE_METATYPE(MprisPlaylist)
Q_DECLARE_METATYPE(MprisPlaylistList)
@@ -145,7 +145,7 @@ class Mpris2 : public QObject {
void SetShuffle(bool enable);
QVariantMap Metadata() const;
double Volume() const;
void SetVolume(const double value);
void SetVolume(const double volume);
qint64 Position() const;
double MaximumRate() const;
double MinimumRate() const;
@@ -215,8 +215,8 @@ class Mpris2 : public QObject {
private:
void EmitNotification(const QString &name);
void EmitNotification(const QString &name, const QVariant &val);
void EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity);
void EmitNotification(const QString &name, const QVariant &value);
void EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity);
QString PlaybackStatus(Engine::State state) const;

View File

@@ -44,7 +44,7 @@ class MultiSortFilterProxy : public QSortFilterProxyModel {
private:
int Compare(const QVariant &left, const QVariant &right) const;
typedef QPair<int, Qt::SortOrder> SortSpec;
using SortSpec = QPair<int, Qt::SortOrder>;
QList<SortSpec> sorting_;
};

View File

@@ -33,6 +33,7 @@
#include <QMetaType>
#include <QString>
#include <QList>
#include <QImage>
#include "song.h"
@@ -55,7 +56,7 @@ class MusicStorage {
Transcode_Unsupported = 3,
};
typedef std::function<void(float progress)> ProgressFunction;
using ProgressFunction = std::function<void(float progress)>;
struct CopyJob {
CopyJob() : overwrite_(false), remove_original_(false), albumcover_(false) {}
@@ -67,6 +68,7 @@ class MusicStorage {
bool albumcover_;
QString cover_source_;
QString cover_dest_;
QImage cover_image_;
ProgressFunction progress_;
QString playlist_;
};
@@ -77,6 +79,7 @@ class MusicStorage {
bool use_trash_;
};
virtual Song::Source source() const = 0;
virtual QString LocalPath() const { return QString(); }
virtual std::optional<int> collection_directory_id() const { return std::optional<int>(); }

View File

@@ -22,12 +22,10 @@
#include <QtGlobal>
#include <QMutex>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QNetworkProxy>
#include <QtDebug>
#include <QSettings>
#include "core/logging.h"

View File

@@ -20,7 +20,6 @@
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QNetworkReply>
#include <QTimerEvent>

View File

@@ -0,0 +1,40 @@
/*
* Strawberry Music Player
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, 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 PLATFORMINTERFACE_H
#define PLATFORMINTERFACE_H
#include <QObject>
#include <QString>
class PlatformInterface {
public:
PlatformInterface() = default;
virtual ~PlatformInterface() {}
// Called when the application should show itself.
virtual void Activate() = 0;
virtual bool LoadUrl(const QString &url) = 0;
private:
Q_DISABLE_COPY(PlatformInterface)
};
#endif // PLATFORMINTERFACE_H

View File

@@ -28,20 +28,17 @@
#include <QtGlobal>
#include <QObject>
#include <QSortFilterProxyModel>
#include <QList>
#include <QMap>
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QDateTime>
#include <QSettings>
#include <QtDebug>
#include "core/logging.h"
#include "utilities/timeconstants.h"
#include "song.h"
#include "timeconstants.h"
#include "urlhandler.h"
#include "application.h"
@@ -58,6 +55,7 @@
#include "collection/collectionbackend.h"
#include "playlist/playlist.h"
#include "playlist/playlistfilter.h"
#include "playlist/playlistitem.h"
#include "playlist/playlistmanager.h"
#include "playlist/playlistsequence.h"
@@ -85,29 +83,24 @@ Player::Player(Application *app, QObject *parent)
autoscroll_(Playlist::AutoScroll_Maybe),
last_state_(Engine::Empty),
nb_errors_received_(0),
volume_(100),
volume_before_mute_(100),
last_pressed_previous_(QDateTime::currentDateTime()),
continue_on_error_(false),
greyout_(true),
menu_previousmode_(BehaviourSettingsPage::PreviousBehaviour_DontRestart),
seek_step_sec_(10),
volume_control_(true),
play_offset_nanosec_(0) {
settings_.beginGroup(kSettingsGroup);
QSettings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
Engine::EngineType enginetype = Engine::EngineTypeFromName(s.value("engine", EngineName(Engine::GStreamer)).toString().toLower());
s.endGroup();
CreateEngine(enginetype);
}
Player::~Player() {
settings_.endGroup();
}
Engine::EngineType Player::CreateEngine(Engine::EngineType enginetype) {
Engine::EngineType use_enginetype(Engine::None);
@@ -183,6 +176,7 @@ void Player::Init() {
QObject::connect(engine_.get(), &EngineBase::TrackAboutToEnd, this, &Player::TrackAboutToEnd);
QObject::connect(engine_.get(), &EngineBase::TrackEnded, this, &Player::TrackEnded);
QObject::connect(engine_.get(), &EngineBase::MetaData, this, &Player::EngineMetadataReceived);
QObject::connect(engine_.get(), &EngineBase::VolumeChanged, this, &Player::SetVolumeFromEngine);
// Equalizer
QObject::connect(equalizer_, &Equalizer::StereoBalancerEnabledChanged, app_->player()->engine(), &EngineBase::SetStereoBalancerEnabled);
@@ -195,17 +189,10 @@ void Player::Init() {
engine_->SetEqualizerEnabled(equalizer_->is_equalizer_enabled());
engine_->SetEqualizerParameters(equalizer_->preamp_value(), equalizer_->gain_values());
s.beginGroup(BackendSettingsPage::kSettingsGroup);
volume_control_ = s.value("volume_control", true).toBool();
s.endGroup();
if (volume_control_) {
int volume = settings_.value("volume", 100).toInt();
SetVolume(volume);
}
ReloadSettings();
LoadVolume();
}
void Player::ReloadSettings() {
@@ -222,15 +209,30 @@ void Player::ReloadSettings() {
seek_step_sec_ = s.value("seek_step_sec", 10).toInt();
s.endGroup();
s.beginGroup(BackendSettingsPage::kSettingsGroup);
bool volume_control = s.value("volume_control", true).toBool();
if (!volume_control && GetVolume() != 100) SetVolume(100);
s.endGroup();
engine_->ReloadSettings();
}
void Player::LoadVolume() {
QSettings s;
s.beginGroup(kSettingsGroup);
const uint volume = s.value("volume", 100).toInt();
s.endGroup();
SetVolume(volume);
}
void Player::SaveVolume() {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("volume", volume_);
s.endGroup();
}
void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
if (loading_async_.contains(result.original_url_)) {
@@ -385,7 +387,7 @@ void Player::NextItem(const Engine::TrackChangeFlags change, const Playlist::Aut
if (change == Engine::Auto) {
const PlaylistSequence::RepeatMode repeat_mode = active_playlist->sequence()->repeat_mode();
if (repeat_mode != PlaylistSequence::Repeat_Off) {
if ((repeat_mode == PlaylistSequence::Repeat_Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->proxy()->rowCount())) {
if ((repeat_mode == PlaylistSequence::Repeat_Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->filter()->rowCount())) {
// We received too many "Error" state changes: probably looping over a playlist which contains only unavailable elements: stop now.
nb_errors_received_ = 0;
Stop();
@@ -448,7 +450,7 @@ void Player::PlayPlaylistInternal(const Engine::TrackChangeFlags change, const P
bool Player::HandleStopAfter(const Playlist::AutoScroll autoscroll) {
if (app_->playlist_manager()->active()->stop_after_current()) {
// Find what the next track would've been, and mark that one as current so it plays next time the user presses Play.
// Find what the next track would've been, and mark that one as current, so it plays next time the user presses Play.
const int next_row = app_->playlist_manager()->active()->next_row();
if (next_row != -1) {
app_->playlist_manager()->active()->set_current_row(next_row, autoscroll, true);
@@ -604,7 +606,7 @@ void Player::PreviousItem(const Engine::TrackChangeFlags change) {
void Player::EngineStateChanged(const Engine::State state) {
if (Engine::Error == state) {
if (state == Engine::Error) {
nb_errors_received_++;
}
else {
@@ -624,7 +626,7 @@ void Player::EngineStateChanged(const Engine::State state) {
break;
case Engine::Error:
emit Error();
// fallthrough
[[fallthrough]];
case Engine::Empty:
case Engine::Idle:
pause_time_ = QDateTime();
@@ -643,20 +645,38 @@ uint Player::GetVolume() const {
}
void Player::SetVolume(const uint value) {
void Player::SetVolumeFromSlider(const int value) {
uint old_volume = engine_->volume();
uint volume = qBound(0U, value, 100U);
settings_.setValue("volume", volume);
engine_->SetVolume(volume);
if (volume != old_volume) {
const uint volume = static_cast<uint>(qBound(0, value, 100));
if (volume != volume_) {
volume_ = volume;
engine_->SetVolume(volume);
emit VolumeChanged(volume);
}
}
void Player::SetVolumeFromEngine(const uint volume) {
const uint new_volume = qBound(0U, volume, 100U);
if (new_volume != volume_) {
volume_ = new_volume;
emit VolumeChanged(new_volume);
}
}
void Player::SetVolume(const uint volume) {
const uint new_volume = qBound(0U, volume, 100U);
if (new_volume != volume_) {
volume_ = new_volume;
engine_->SetVolume(new_volume);
emit VolumeChanged(new_volume);
}
}
void Player::VolumeUp() {
uint old_volume = GetVolume();
@@ -791,8 +811,6 @@ PlaylistItemPtr Player::GetItemAt(const int pos) const {
void Player::Mute() {
if (!volume_control_) return;
const uint current_volume = engine_->volume();
if (current_volume == 0) {
@@ -843,7 +861,7 @@ void Player::TrackAboutToEnd() {
if (engine_->is_autocrossfade_enabled()) {
// Crossfade is on, so just start playing the next track. The current one will fade out, and the new one will fade in
// But, if there's no next track and we don't want to fade out, then do nothing and just let the track finish to completion.
// But, if there's no next track, and we don't want to fade out, then do nothing and just let the track finish to completion.
if (!engine_->is_fadeout_enabled() && !has_next_row) return;
// If the next track is on the same album (or same cue file),
@@ -854,7 +872,7 @@ void Player::TrackAboutToEnd() {
}
}
// Crossfade is off, so start preloading the next track so we don't get a gap between songs.
// Crossfade is off, so start preloading the next track, so we don't get a gap between songs.
if (!has_next_row || !next_item) return;
QUrl url = next_item->StreamUrl();

View File

@@ -32,7 +32,6 @@
#include <QDateTime>
#include <QString>
#include <QUrl>
#include <QSettings>
#include "urlhandler.h"
#include "engine/engine_fwd.h"
@@ -71,6 +70,8 @@ class PlayerInterface : public QObject {
public slots:
virtual void ReloadSettings() = 0;
virtual void LoadVolume() = 0;
virtual void SaveVolume() = 0;
// Manual track change to the specified track
virtual void PlayAt(const int index, const quint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0;
@@ -84,7 +85,9 @@ class PlayerInterface : public QObject {
virtual void Next() = 0;
virtual void Previous() = 0;
virtual void PlayPlaylist(const QString &playlist_name) = 0;
virtual void SetVolume(const uint value) = 0;
virtual void SetVolumeFromEngine(const uint volume) = 0;
virtual void SetVolumeFromSlider(const int value) = 0;
virtual void SetVolume(const uint volume) = 0;
virtual void VolumeUp() = 0;
virtual void VolumeDown() = 0;
virtual void SeekTo(const quint64 seconds) = 0;
@@ -132,7 +135,6 @@ class Player : public PlayerInterface {
public:
explicit Player(Application *app, QObject *parent);
~Player() override;
static const char *kSettingsGroup;
@@ -158,6 +160,8 @@ class Player : public PlayerInterface {
public slots:
void ReloadSettings() override;
void LoadVolume() override;
void SaveVolume() override;
void PlayAt(const int index, const quint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override;
void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Always) override;
@@ -166,7 +170,9 @@ class Player : public PlayerInterface {
void Next() override;
void Previous() override;
void PlayPlaylist(const QString &playlist_name) override;
void SetVolume(const uint value) override;
void SetVolumeFromSlider(const int value) override;
void SetVolumeFromEngine(const uint volume) override;
void SetVolume(const uint volume) override;
void VolumeUp() override;
void VolumeDown() override;
void SeekTo(const quint64 seconds) override;
@@ -223,8 +229,6 @@ class Player : public PlayerInterface {
AnalyzerContainer *analyzer_;
Equalizer *equalizer_;
QSettings settings_;
PlaylistItemPtr current_item_;
Engine::TrackChangeFlags stream_change_type_;
@@ -235,6 +239,7 @@ class Player : public PlayerInterface {
QMap<QString, UrlHandler*> url_handlers_;
QList<QUrl> loading_async_;
uint volume_;
uint volume_before_mute_;
QDateTime last_pressed_previous_;
@@ -243,8 +248,6 @@ class Player : public PlayerInterface {
BehaviourSettingsPage::PreviousBehaviour menu_previousmode_;
int seek_step_sec_;
bool volume_control_;
QDateTime pause_time_;
quint64 play_offset_nanosec_;

View File

@@ -29,12 +29,10 @@
#include <QIcon>
#include <QString>
#include <QUrl>
#include <QSettings>
#include "song.h"
#include "iconloader.h"
#include "qtsystemtrayicon.h"
#include "settings/behavioursettingspage.h"
SystemTrayIcon::SystemTrayIcon(QObject *parent)
: QSystemTrayIcon(parent),

View File

@@ -20,7 +20,7 @@
template<typename CFT>
class ScopedCFTypeRef {
public:
typedef CFT element_type;
using element_type = CFT;
explicit ScopedCFTypeRef(CFT object = nullptr) : object_(object) {}

View File

@@ -25,8 +25,6 @@
#include <glib-object.h>
#include <QtDebug>
template<typename T>
class ScopedGObject {
public:

View File

@@ -0,0 +1,29 @@
/*
* Strawberry Music Player
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, 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 <QString>
#include "scopedwchararray.h"
ScopedWCharArray::ScopedWCharArray(const QString &str)
: chars_(str.length()), data_(new wchar_t[chars_ + 1]) {
str.toWCharArray(data_.get());
data_[chars_] = '\0';
}

View File

@@ -0,0 +1,48 @@
/*
* Strawberry Music Player
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, 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 SCOPEDWCHARARRAY_H
#define SCOPEDWCHARARRAY_H
#include <memory>
#include <QObject>
#include <QString>
class ScopedWCharArray {
public:
explicit ScopedWCharArray(const QString &str);
QString ToString() const { return QString::fromWCharArray(data_.get()); }
wchar_t *get() const { return data_.get(); }
explicit operator wchar_t *() const { return get(); }
qint64 characters() const { return chars_; }
qint64 bytes() const { return (chars_ + 1) * sizeof(wchar_t); }
private:
Q_DISABLE_COPY(ScopedWCharArray)
qint64 chars_;
std::unique_ptr<wchar_t[]> data_;
};
#endif // SCOPEDWCHARARRAY_H

View File

@@ -35,7 +35,6 @@ gulong CheckedGConnect(gpointer source, const char *signal, GCallback callback,
if (!g_signal_parse_name(signal, G_OBJECT_TYPE(source), &signal_id, &detail, false)) {
qFatal("Connecting to invalid signal: %s", signal);
return 0;
}
GSignalQuery query;
@@ -44,7 +43,6 @@ gulong CheckedGConnect(gpointer source, const char *signal, GCallback callback,
int signal_params = static_cast<int>(query.n_params) + 2;
if (signal_params != callback_param_count) {
qFatal("Connecting callback to signal with different parameters counts");
return 0;
}
return g_signal_connect(source, signal, G_CALLBACK(callback), data);

View File

@@ -38,26 +38,22 @@
#include <QFileInfo>
#include <QDir>
#include <QSharedData>
#include <QHash>
#include <QByteArray>
#include <QVariant>
#include <QVariantMap>
#include <QString>
#include <QStringList>
#include <QRegularExpression>
#include <QUrl>
#include <QImage>
#include <QIcon>
#include <QSqlQuery>
#include <QStandardPaths>
#include <QtDebug>
#include "core/logging.h"
#include "core/messagehandler.h"
#include "core/iconloader.h"
#include "engine/enginebase.h"
#include "timeconstants.h"
#include "utilities.h"
#include "utilities/strutils.h"
#include "utilities/timeutils.h"
#include "utilities/cryptutils.h"
#include "utilities/timeconstants.h"
#include "song.h"
#include "application.h"
#include "sqlquery.h"
@@ -65,6 +61,9 @@
#include "sqlrow.h"
#include "tagreadermessages.pb.h"
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
const QStringList Song::kColumns = QStringList() << "title"
<< "album"
<< "artist"
@@ -149,14 +148,13 @@ const QString Song::kEmbeddedCover = "(embedded)";
const QRegularExpression Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const QRegularExpression Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|([0-9]{1,4}) *Remaster|Explicit) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const QRegularExpression Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Remastered Version|([0-9]{1,4}) *Remaster) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
const QString Song::kVariousArtists("various artists");
const QStringList Song::kArticles = QStringList() << "the " << "a " << "an ";
const QStringList Song::kAcceptedExtensions = QStringList() << "wav" << "flac" << "wv" << "ogg" << "oga" << "opus" << "spx" << "ape" << "mpc"
<< "mp2" << "mp3" << "m4a" << "mp4" << "aac" << "asf" << "asx" << "wma"
<< "aif << aiff" << "mka" << "tta" << "dsf" << "dsd"
<< "ac3" << "dts";
<< "ac3" << "dts" << "spc" << "vgm";
struct Song::Private : public QSharedData {
@@ -226,7 +224,6 @@ struct Song::Private : public QSharedData {
float rating_; // Database rating, initial rating read from tag.
QUrl stream_url_; // Temporary stream url set by url handler.
QImage image_; // Album Cover image set by album cover loader.
bool init_from_file_; // Whether this song was loaded from a file using taglib.
bool suspicious_tags_; // Whether our encoding guesser thinks these tags might be incorrectly encoded.
@@ -355,6 +352,73 @@ void Song::set_embedded_cover() { d->art_automatic_ = QUrl::fromLocalFile(kEmbed
void Song::clear_art_automatic() { d->art_automatic_.clear(); }
void Song::clear_art_manual() { d->art_manual_.clear(); }
bool Song::additional_tags_supported() const {
return d->filetype_ == FileType_FLAC ||
d->filetype_ == FileType_WavPack ||
d->filetype_ == FileType_OggFlac ||
d->filetype_ == FileType_OggVorbis ||
d->filetype_ == FileType_OggOpus ||
d->filetype_ == FileType_OggSpeex ||
d->filetype_ == FileType_MPEG ||
d->filetype_ == FileType_MP4 ||
d->filetype_ == FileType_MPC ||
d->filetype_ == FileType_APE;
}
bool Song::albumartist_supported() const {
return additional_tags_supported();
}
bool Song::composer_supported() const {
return additional_tags_supported();
}
bool Song::performer_supported() const {
return d->filetype_ == FileType_FLAC ||
d->filetype_ == FileType_WavPack ||
d->filetype_ == FileType_OggFlac ||
d->filetype_ == FileType_OggVorbis ||
d->filetype_ == FileType_OggOpus ||
d->filetype_ == FileType_OggSpeex ||
d->filetype_ == FileType_MPEG ||
d->filetype_ == FileType_MPC ||
d->filetype_ == FileType_APE;
}
bool Song::grouping_supported() const {
return additional_tags_supported();
}
bool Song::genre_supported() const {
return additional_tags_supported();
}
bool Song::compilation_supported() const {
return additional_tags_supported();
}
bool Song::rating_supported() const {
return d->filetype_ == FileType_FLAC ||
d->filetype_ == FileType_WavPack ||
d->filetype_ == FileType_OggFlac ||
d->filetype_ == FileType_OggVorbis ||
d->filetype_ == FileType_OggOpus ||
d->filetype_ == FileType_OggSpeex ||
d->filetype_ == FileType_MPEG ||
d->filetype_ == FileType_MP4 ||
d->filetype_ == FileType_ASF ||
d->filetype_ == FileType_MPC ||
d->filetype_ == FileType_APE;
}
bool Song::comment_supported() const {
return additional_tags_supported();
}
bool Song::lyrics_supported() const {
return additional_tags_supported();
}
bool Song::save_embedded_cover_supported(const FileType filetype) {
return filetype == FileType_FLAC ||
@@ -367,7 +431,6 @@ bool Song::save_embedded_cover_supported(const FileType filetype) {
const QUrl &Song::stream_url() const { return d->stream_url_; }
const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
const QImage &Song::image() const { return d->image_; }
bool Song::init_from_file() const { return d->init_from_file_; }
const QString &Song::cue_path() const { return d->cue_path_; }
@@ -481,7 +544,6 @@ void Song::set_cue_path(const QString &v) { d->cue_path_ = v; }
void Song::set_rating(const float v) { d->rating_ = v; }
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
void Song::set_image(const QImage &i) { d->image_ = i; }
QString Song::JoinSpec(const QString &table) {
return Utilities::Prepend(table + ".", kColumns).join(", ");
@@ -604,6 +666,8 @@ QString Song::TextForFiletype(FileType filetype) {
case Song::FileType_XM: return "Module Music Format";
case Song::FileType_IT: return "Module Music Format";
case Song::FileType_CDDA: return "CDDA";
case Song::FileType_SPC: return "SNES SPC700";
case Song::FileType_VGM: return "VGM";
case Song::FileType_Stream: return "Stream";
case Song::FileType_Unknown:
default: return QObject::tr("Unknown");
@@ -634,6 +698,8 @@ QString Song::ExtensionForFiletype(FileType filetype) {
case Song::FileType_S3M: return "s3m";
case Song::FileType_XM: return "xm";
case Song::FileType_IT: return "it";
case Song::FileType_SPC: return "spc";
case Song::FileType_VGM: return "vgm";
case Song::FileType_Unknown:
default: return "dat";
}
@@ -710,6 +776,8 @@ Song::FileType Song::FiletypeByMimetype(const QString &mimetype) {
else if (mimetype.compare("audio/x-ape", Qt::CaseInsensitive) == 0 || mimetype.compare("application/x-ape", Qt::CaseInsensitive) == 0 || mimetype.compare("audio/x-ffmpeg-parsed-ape", Qt::CaseInsensitive) == 0) return Song::FileType_APE;
else if (mimetype.compare("audio/x-mod", Qt::CaseInsensitive) == 0) return Song::FileType_MOD;
else if (mimetype.compare("audio/x-s3m", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
else if (mimetype.compare("audio/x-spc", Qt::CaseInsensitive) == 0) return Song::FileType_SPC;
else if (mimetype.compare("audio/x-vgm", Qt::CaseInsensitive) == 0) return Song::FileType_VGM;
else return Song::FileType_Unknown;
@@ -733,6 +801,8 @@ Song::FileType Song::FiletypeByDescription(const QString &text) {
else if (text.compare("audio/x-ffmpeg-parsed-ape", Qt::CaseInsensitive) == 0) return Song::FileType_APE;
else if (text.compare("Module Music Format (MOD)", Qt::CaseInsensitive) == 0) return Song::FileType_MOD;
else if (text.compare("Module Music Format (MOD)", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
else if (text.compare("SNES SPC700", Qt::CaseInsensitive) == 0) return Song::FileType_SPC;
else if (text.compare("VGM", Qt::CaseInsensitive) == 0) return Song::FileType_VGM;
else return Song::FileType_Unknown;
}
@@ -760,6 +830,8 @@ Song::FileType Song::FiletypeByExtension(const QString &ext) {
else if (ext.compare("s3m", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
else if (ext.compare("xm", Qt::CaseInsensitive) == 0) return Song::FileType_XM;
else if (ext.compare("it", Qt::CaseInsensitive) == 0) return Song::FileType_IT;
else if (ext.compare("spc", Qt::CaseInsensitive) == 0) return Song::FileType_SPC;
else if (ext.compare("vgm", Qt::CaseInsensitive) == 0) return Song::FileType_VGM;
else return Song::FileType_Unknown;
@@ -1006,12 +1078,9 @@ void Song::InitFromFilePartial(const QString &filename, const QFileInfo &fileinf
void Song::InitArtManual() {
QString album = effective_album();
album.remove(Song::kAlbumRemoveDisc);
// If we don't have an art, check if we have one in the cache
if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty() && !effective_albumartist().isEmpty() && !album.isEmpty()) {
QString filename(Utilities::Sha1CoverHash(effective_albumartist(), album).toHex() + ".jpg");
if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty() && !effective_albumartist().isEmpty() && !effective_album().isEmpty()) {
QString filename(Utilities::Sha1CoverHash(effective_albumartist(), effective_album()).toHex() + ".jpg");
QString path(ImageCacheDir(d->source_) + "/" + filename);
if (QFile::exists(path)) {
d->art_manual_ = QUrl::fromLocalFile(path);
@@ -1539,6 +1608,10 @@ void Song::ToXesam(QVariantMap *map) const {
AddMetadataAsList("xesam:composer", composer(), map);
AddMetadata("xesam:useCount", static_cast<int>(playcount()), map);
if (rating() != -1.0) {
AddMetadata("xesam:userRating", rating(), map);
}
}
void Song::MergeUserSetData(const Song &other, const bool merge_playcount, const bool merge_rating) {

View File

@@ -37,7 +37,6 @@
#include <QRegularExpression>
#include <QUrl>
#include <QFileInfo>
#include <QImage>
#include <QIcon>
class SqlQuery;
@@ -106,6 +105,8 @@ class Song {
FileType_S3M = 19,
FileType_XM = 20,
FileType_IT = 21,
FileType_SPC = 22,
FileType_VGM = 23,
FileType_CDDA = 90,
FileType_Stream = 91,
};
@@ -131,8 +132,6 @@ class Song {
static const QRegularExpression kAlbumRemoveMisc;
static const QRegularExpression kTitleRemoveMisc;
static const QString kVariousArtists;
static const QStringList kArticles;
static const QStringList kAcceptedExtensions;
@@ -297,9 +296,19 @@ class Song {
static bool save_embedded_cover_supported(const FileType filetype);
bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
bool additional_tags_supported() const;
bool albumartist_supported() const;
bool composer_supported() const;
bool performer_supported() const;
bool grouping_supported() const;
bool genre_supported() const;
bool compilation_supported() const;
bool rating_supported() const;
bool comment_supported() const;
bool lyrics_supported() const;
const QUrl &stream_url() const;
const QUrl &effective_stream_url() const;
const QImage &image() const;
bool init_from_file() const;
// Pretty accessors
@@ -378,7 +387,6 @@ class Song {
void set_rating(const float v);
void set_stream_url(const QUrl &v);
void set_image(const QImage &i);
// Comparison functions
bool IsMetadataEqual(const Song &other) const;
@@ -403,8 +411,8 @@ class Song {
QSharedDataPointer<Private> d;
};
typedef QList<Song> SongList;
typedef QMap<QString, Song> SongMap;
using SongList = QList<Song>;
using SongMap = QMap<QString, Song>;
Q_DECLARE_METATYPE(Song)
Q_DECLARE_METATYPE(SongList)

View File

@@ -21,7 +21,6 @@
#include "config.h"
#include <cstdlib>
#include <memory>
#include <algorithm>
@@ -37,13 +36,11 @@
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QList>
#include <QSet>
#include <QTimer>
#include <QString>
#include <QUrl>
#include <QEventLoop>
#include <QtDebug>
#include "core/logging.h"
@@ -646,7 +643,7 @@ void SongLoader::EndOfStreamReached() {
if (state_ == Finished) break;
// It looks like a playlist, so parse it
// fallthrough
[[fallthrough]];
case WaitingForData:
// It's a playlist and we've got all the data - finish and parse it
StopTypefindAsync(true);

View File

@@ -19,10 +19,8 @@
#include "config.h"
#include <QSqlQuery>
#include <QMap>
#include <QVariant>
#include <QVariantList>
#include <QString>
#include <QUrl>

View File

@@ -53,6 +53,6 @@ class SqlRow {
};
typedef QList<SqlRow> SqlRowList;
using SqlRowList = QList<SqlRow>;
#endif // SQLROW_H

View File

@@ -26,7 +26,6 @@
#include <QStandardItem>
#include <QMap>
#include <QSet>
#include <QImage>
#include <QPixmap>
#include <QIcon>

View File

@@ -23,7 +23,6 @@
**
****************************************************************************/
//#pragma once
#ifndef STYLEHELPER_H
#define STYLEHELPER_H

View File

@@ -26,7 +26,6 @@
#include <QtGlobal>
#include <QObject>
#include <QWidget>
#include <QHash>
#include <QTimer>
#include <QIODevice>
#include <QTextStream>
@@ -35,7 +34,6 @@
#include <QColor>
#include <QPalette>
#include <QEvent>
#include <QtDebug>
#include "core/logging.h"
#include "stylesheetloader.h"

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