Compare commits

...

153 Commits
1.0.0 ... 1.0.1

Author SHA1 Message Date
Jonas Kvinge
a45cb25002 Release 1.0.1 2022-01-08 15:20:32 +01:00
Jonas Kvinge
21ec116769 Update Changelog 2022-01-08 14:42:53 +01:00
Jonas Kvinge
1d4775a94b Simplify stylesheet on QToolButton 2022-01-08 13:18:54 +01:00
Jonas Kvinge
4cde9a7e9e Fix padding on toolbuttons with menu button popup
Fixes #795
2022-01-08 13:10:48 +01:00
Jonas Kvinge
709ba72777 CollectionWatcher: Exclude hidden files 2022-01-07 21:16:38 +01:00
Strawbs Bot
0e56b8c575 Update translations 2022-01-07 01:05:42 +01:00
Jonas Kvinge
558a087e37 CollectionWatcher: Resume collection scan when adding directory 2022-01-06 23:29:20 +01:00
Jonas Kvinge
7d96fe8b65 CollectionWatcher: Abort collection scan on exit 2022-01-06 23:14:10 +01:00
Jonas Kvinge
2f3e8986ab AlbumCoverChoiceController: Clear manually set cover when setting embedded cover
Possible fix for #858
2022-01-06 02:14:33 +01:00
Strawbs Bot
df359ba0fa Update translations 2022-01-06 01:09:25 +01:00
Jonas Kvinge
31fa60884f Tagreader: Fix comment 2022-01-06 00:08:48 +01:00
Jonas Kvinge
15be227920 TagReader: Various fixes to embedded cover handling
- Add support for saving embedded for Opus
- Fix deleting cover for Ogg Vorbis, Opus and Speex
- Don't load empty covers
2022-01-06 00:06:20 +01:00
Jonas Kvinge
fdedfd54c7 Moodbar: Disable moodbar loader for CUE songs 2022-01-05 18:59:14 +01:00
Strawbs Bot
7d55eb4b44 Update translations 2022-01-03 12:42:15 +01:00
Jonas Kvinge
61ac7ae31e GeniusLyricsProvider: Move index past start tag 2022-01-02 21:47:16 +01:00
Jonas Kvinge
353e141036 Add Qt 5 warning 2022-01-02 14:48:45 +01:00
Jonas Kvinge
cb4332842f Save MP4 tags as UTF-8
Fixes #830
2022-01-02 02:47:42 +01:00
Jonas Kvinge
004d219c97 CollectionWatcher: Fix notify on path change, improve debug output
Possible fix for #860
2022-01-01 01:32:41 +01:00
Jonas Kvinge
94561e6815 Windows: Include ibgstxingmux.dll in installer 2021-12-27 18:12:55 +01:00
Jonas Kvinge
ce3af4961b GeniusLyricsProvider: Fix parsing of different HTML pages 2021-12-19 20:59:38 +01:00
Jonas Kvinge
bbd81e7d9c kglobalaccel: Attempt to register media shortcuts too 2021-12-18 20:18:37 +01:00
Jonas Kvinge
8b1d198efd Remove unused Avahi 2021-12-18 19:46:23 +01:00
Jonas Kvinge
7a3bafc961 Add Tumbleweed to CI 2021-12-18 13:00:33 +01:00
Jonas Kvinge
512eed9b04 Remove CMake release config from strawberry.spec 2021-12-14 22:16:44 +01:00
Strawbs Bot
38adf640e9 Update translations 2021-12-13 01:02:34 +01:00
Jonas Kvinge
f4d8d73970 Always save metadata in playlist for Tidal, Qobuz and Subsonic songs
Fixes #851
2021-12-12 01:43:46 +01:00
Strawbs Bot
5a50285dee Update translations 2021-12-08 12:29:11 +01:00
Jonas Kvinge
ca4ea0719f Playlist: Fix updating summary after reloading items
Fixes #848
2021-12-08 01:35:49 +01:00
Strawbs Bot
4c6251bf28 Update translations 2021-11-29 01:02:44 +01:00
Alexey Vazhnov
22c12c7366 Add dist/scripts/import-from-clementine.sh
Shell script for importing database from Clementine.

Fixes #838
Fixes #248
2021-11-28 13:14:45 +01:00
Jonas Kvinge
8f49d1591f Support more CUE filenames
Fixes #835
2021-11-27 20:28:00 +01:00
Jonas Kvinge
a97dbab2ae CollectionWatcher: Rename fileinfo variable 2021-11-27 19:25:25 +01:00
Jonas Kvinge
4cb8261d3b CueParser: Fix parsing tracks with 1-digit minutes
Fixes #836
2021-11-27 18:15:03 +01:00
Strawbs Bot
059e2d740f Update translations 2021-11-22 01:02:26 +01:00
Strawbs Bot
587d1c9d74 Add Spanish (Mexico) 2021-11-20 17:28:02 +01:00
Strawbs Bot
ab56c96170 Add Spanish (Argentina) 2021-11-20 17:27:23 +01:00
Strawbs Bot
efa4270429 Add Spanish/Spain 2021-11-20 17:14:43 +01:00
Strawbs Bot
0fbe5d888a Update translations 2021-11-13 01:02:59 +01:00
Strawbs Bot
b68f81179e Update translations 2021-11-11 01:05:36 +01:00
Jonas Kvinge
6a8c1af5f9 GstEnginePipeline: Set port-pattern for jackaudiosink 2021-11-11 00:58:00 +01:00
Jonas Kvinge
98f287559b GstEngine: Allow custom device for jackaudiosink 2021-11-11 00:58:00 +01:00
Strawbs Bot
1a53d85f6a Update translations 2021-11-10 01:19:03 +01:00
Jonas Kvinge
05b168aa04 Fix incorrect playlist column filesize for streams 2021-11-09 21:16:44 +01:00
Jonas Kvinge
a68d856074 AlbumCoverLoader: Change ID to quint64 2021-11-09 20:48:43 +01:00
Jonas Kvinge
d5dac9c6cc Add musicbrainz icon 2021-11-09 20:07:48 +01:00
Adam Hill
e9f7f42c03 PlaylistView: Fix for currenttrack_bar blocky rendering 2021-11-09 19:45:41 +01:00
Uint
7b8265d4a3 Prevent filtering when filter toolbar is hidden, clear when disabled 2021-11-09 19:41:23 +01:00
Jonas Kvinge
5715f4c2cb Remove use of macros in Song and TagReader 2021-11-09 19:34:43 +01:00
Jonas Kvinge
350debb66c Utilities: Add static_cast in Hmac() 2021-11-09 19:34:43 +01:00
Jonas Kvinge
267cd3660b InternetSearchSortModel: Replace use of macro 2021-11-09 19:34:43 +01:00
Jonas Kvinge
bc1c0c3c6d SqlQuery: Add more BindValue methods 2021-11-09 19:34:43 +01:00
Jonas Kvinge
c420f69da8 SqlRow: Replace class with a new that takes values by column field name 2021-11-09 19:34:43 +01:00
Jonas Kvinge
2151d96303 Use PlaylistItemList 2021-11-09 19:34:43 +01:00
Jonas Kvinge
c48c65d0ce InternetService: Change parameters to const 2021-11-09 19:34:43 +01:00
Jonas Kvinge
1c0b706b94 TranscoderOptionsVorbis: Remove use of macros 2021-11-09 19:34:43 +01:00
Jonas Kvinge
2dca3d6f5a gstfastspectrum: Remove some macros 2021-11-09 19:34:43 +01:00
Jonas Kvinge
0992636f8c playlistparsers: Change qt_endl macro to constexpr 2021-11-09 19:34:43 +01:00
Jonas Kvinge
86d3f2b4ed SliderSlider: Remove unused macros 2021-11-09 19:34:43 +01:00
Jonas Kvinge
b78b4038f6 InternetService: Fix overloaded signal 2021-11-09 19:34:43 +01:00
Jonas Kvinge
84c14349dc ContextAlbumsModel: Remove unused SqlRow 2021-11-09 19:34:43 +01:00
Strawbs Bot
9916e34006 Update translations 2021-11-09 01:05:40 +01:00
Jonas Kvinge
a729b42e5d Tidal: Fix reading keyId 2021-11-08 20:44:12 +01:00
Jonas Kvinge
01f8129ed0 Improve URL handler, return error for encrypted Tidal streams 2021-11-08 20:25:22 +01:00
Jonas Kvinge
fd85763fb4 Tidal: Make sure OAuth is enabled when sending access token 2021-11-08 19:47:17 +01:00
Jonas Kvinge
c278ffe2cb CI: Remove Windows Qt 5 builder 2021-11-05 21:41:30 +01:00
Jonas Kvinge
902c76a781 Remove Qt 5 support from Windows nsi 2021-11-05 21:38:09 +01:00
Jonas Kvinge
4daed0070a WorkerPool: Add missing config.h include 2021-11-04 00:42:49 +01:00
Jonas Kvinge
a4d055b3ac Remove obsolete cmake version check 2021-11-04 00:05:06 +01:00
Jonas Kvinge
e6ae6c6f04 Remove Fedora 33 2021-11-03 22:11:17 +01:00
Jonas Kvinge
de54ceeb40 Add Fedora 35 to CI 2021-11-03 21:21:12 +01:00
Strawbs Bot
77fe00df30 Update translations 2021-11-03 01:02:23 +01:00
Strawbs Bot
4ae3e63dea Update translations 2021-11-01 01:20:05 +01:00
Jonas Kvinge
3329839dbe CollectionWatcher: Add overwrite_rating_ to initialization list 2021-10-31 23:26:22 +01:00
Jonas Kvinge
ca10920bb5 Add option for overwriting database rating 2021-10-31 23:24:32 +01:00
Jonas Kvinge
b41957ce5c Add back css style for QToolButton only on macOS
Fixes #819
2021-10-31 16:55:33 +01:00
Jonas Kvinge
3db6699018 WorkerPool: Use variable 2021-10-31 14:48:42 +01:00
Jonas Kvinge
5284ca90ef Forward messages from tagreader worker process on Windows 2021-10-31 13:51:23 +01:00
Jonas Kvinge
628cdff4fa TagReaderTagParser: Return false from SaveSongPlaycountToFile 2021-10-31 13:19:52 +01:00
Jonas Kvinge
af0c6f9233 Logging: Use stdout unless critical or fatal, and flush stream 2021-10-31 13:18:26 +01:00
Jonas Kvinge
7697bbfa4e Logging: Formatting 2021-10-31 13:18:26 +01:00
buckmelanoma
642a455a9c Add icon mappings for download and scrobble
Adds mappings for download (Cover Manager) and scrobble (Import data from last.fm):
2021-10-31 02:23:15 +01:00
Strawbs Bot
6abfa2859b Update translations 2021-10-31 01:20:21 +02:00
buckmelanoma
20c2225d8f Add icon names for missing Tools menu entries
I noticed the abort_collection_scan and console menu entries were missing matching icons.  This change adds appropriate icons for each action.
2021-10-31 00:37:57 +02:00
Jonas Kvinge
5e7759b697 Utilities: Change return type for GetThreadId and SetThreadIOPriority to long 2021-10-31 00:26:02 +02:00
Jonas Kvinge
1ce8d2b31d RadioView: Remove overloaded signal 2021-10-31 00:24:16 +02:00
Jonas Kvinge
9fbecb5af6 macdeployqt: Use static QFile::exists() 2021-10-30 19:23:08 +02:00
Jonas Kvinge
1223469be9 Song: Add playcount and rating to Song::IsMetadataEqual() 2021-10-30 19:06:46 +02:00
Jonas Kvinge
5eae3ddd8a Use float for rating 2021-10-30 18:53:14 +02:00
Jonas Kvinge
3d0b6e6ea1 TagReader: Check for valid file times
Fixes #815
2021-10-30 17:57:13 +02:00
Jonas Kvinge
c6c53548ac GstEnginePipeline: Return true from BusCallback 2021-10-30 17:55:18 +02:00
Jonas Kvinge
3efe2d1e05 Initialize to 0 2021-10-30 17:10:05 +02:00
buckmelanoma
7c87b53517 PlaylistView: Use SmoothPixmapTransform when drawing play icon
Fixes #794
2021-10-30 03:36:17 +02:00
Jonas Kvinge
a461c97bcf IconLoader: Log when icon fails to load 2021-10-30 03:34:52 +02:00
Jonas Kvinge
67a6d6c1e3 LyricsFetcher: Change request ID to quint64 2021-10-30 03:15:03 +02:00
Jonas Kvinge
8b8e427a2b DeleteFiles: Fix compile with older Qt versions 2021-10-30 03:13:55 +02:00
Jonas Kvinge
7d5c263ab2 FHT: Change to static_cast double 2021-10-30 03:13:12 +02:00
Jonas Kvinge
79ac53b2d9 Fix narrowing conversions 2021-10-30 02:21:29 +02:00
Jonas Kvinge
a704412dee ContextAlbumsModel: Use const_iterator 2021-10-30 02:05:55 +02:00
Jonas Kvinge
24e2338769 ScrobblingAPI20: Add clazy:exclude=range-loop-detach 2021-10-30 02:02:37 +02:00
Jonas Kvinge
bc240f82ef SomaFMService: Add clazy:exclude=function-args-by-ref 2021-10-30 02:01:48 +02:00
Jonas Kvinge
a2ad68406d RadioView: Add clazy:exclude=reserve-candidates 2021-10-30 02:01:14 +02:00
Jonas Kvinge
3d807d2331 FilterParser: Add iter_ and end_ to initialization list 2021-10-30 02:00:26 +02:00
Jonas Kvinge
e6a7b484ba OSDBase: Remove redundant initialization 2021-10-30 01:59:51 +02:00
Jonas Kvinge
a62371829f Add ui_ to initialization list 2021-10-30 01:58:47 +02:00
Jonas Kvinge
fb98336713 Udisks2Lister: Remove redundant initialization 2021-10-30 01:58:01 +02:00
Jonas Kvinge
ebadb4db0a Use const_iterator 2021-10-30 01:57:12 +02:00
Jonas Kvinge
9a480eb710 SqlQuery: Make QSqlDatabase parameter const reference 2021-10-30 01:33:16 +02:00
Jonas Kvinge
03ecde8b83 Add default to switch 2021-10-30 01:08:35 +02:00
Strawbs Bot
0291b78bfa Update translations 2021-10-30 01:02:38 +02:00
Jonas Kvinge
377e6fad25 gstmoodbar: Formatting 2021-10-30 00:48:58 +02:00
Jonas Kvinge
e75c955a68 mac_startup: Remove unused functions 2021-10-30 00:48:58 +02:00
buckmelanoma
6df356327d Add 2px of padding for edit-clear icon
edit-clear icon tends to sit on left side of frame.  adding 2 px makes it look nicer.
2021-10-29 17:52:43 +02:00
buckmelanoma
7a57586f90 Map edit-clear-list system icons
Use system edit-clear-list icons when enabled instead of defaulting to edit-clear-locationbar-ltr.png
2021-10-29 17:52:43 +02:00
Strawbs Bot
1e2bad270d Update translations 2021-10-29 01:02:58 +02:00
Strawbs Bot
688d983b25 Update translations 2021-10-28 01:02:41 +02:00
Jonas Kvinge
fa834a76ef ContextView: Avoid unnecessary album resize 2021-10-27 22:16:48 +02:00
Jonas Kvinge
5cb88efc38 Remove debug line 2021-10-26 23:54:27 +02:00
Jonas Kvinge
6b64c43851 TagReaderTagLib: Read FMPS_Rating for MPEG ID3v2 tag 2021-10-26 01:10:01 +02:00
Jonas Kvinge
704e6c5448 TagReaderTagLib: Use double for ratings to match Song 2021-10-26 00:24:03 +02:00
Jonas Kvinge
072d7379df TagReaderTagLib: Add checks for nullptr 2021-10-26 00:02:36 +02:00
Strawbs Bot
df1b756a43 Update translations 2021-10-25 01:13:11 +02:00
Jonas Kvinge
30269d9d95 Organize: Remove unused "mark as listened" option 2021-10-24 23:19:01 +02:00
Jonas Kvinge
d29a1de980 Allow users to select native notifications on Windows
Setting will fallback to system tray notifications which are
native notifications on Windows.
2021-10-24 23:09:20 +02:00
Jonas Kvinge
57f5ccff81 InternetSearchView: Change tool button popup mode back to InstantPopup 2021-10-24 22:05:10 +02:00
buckmelanoma
69374bfa11 Revert collection options QToolButton mode
Changing this button type fixed the arrow problem but has a side effect of making the button only clickable on the arrow portion.   #795 fixed the issue with styling on QToolButtons and ends up fixing this issue too.  Therefore the change to MenuButtonPopup should be reverted in order to restore the original functionality.
2021-10-24 20:11:27 +02:00
Jonas Kvinge
d9fd330216 Update Changelog 2021-10-24 18:32:14 +02:00
Jonas Kvinge
312f62d98f Fix stop after this track button with Qt 6
Fixes #795
2021-10-24 18:15:42 +02:00
buckmelanoma
0d408055b2 Add settings to enable/disable playlist toolbar 2021-10-24 17:04:35 +02:00
Jonas Kvinge
496ae42d72 EditTagDialog: Change return to continue in EditTagDialog::SongRated 2021-10-24 16:11:53 +02:00
Jonas Kvinge
3ab86543ad Add support for saving playcounts and ratings to tags 2021-10-24 16:08:17 +02:00
Jonas Kvinge
ce7926cfa4 Update libffi in nsi 2021-10-23 11:35:52 +02:00
Strawbs Bot
48c81b188c Update translations 2021-10-23 01:02:17 +02:00
Luna
21b241bbbe add release info 2021-10-22 23:25:19 +02:00
Luna
934d6fc267 add oars content rating 2021-10-22 23:25:19 +02:00
Luna
a0ce2daa2e add type to appdata.xml 2021-10-22 23:25:19 +02:00
Jonas Kvinge
006d77239b Update protobuf in nsi 2021-10-21 18:40:04 +02:00
Jonas Kvinge
303d31bde7 Update Windows dependencies 2021-10-19 23:56:24 +02:00
Strawbs Bot
737fbeccde Update translations 2021-10-19 01:02:20 +02:00
Jonas Kvinge
ec17dc9830 TagReaderClient: Sort functions 2021-10-18 23:15:50 +02:00
Strawbs Bot
0f710ea3be Update translations 2021-10-18 01:01:58 +02:00
Jonas Kvinge
bd4eec4527 Song: Only merge user set playcount and rating if set
Fixes #790
2021-10-18 00:39:26 +02:00
Jonas Kvinge
c78252e0d5 Add dates to Changelog
Fixes #803
2021-10-17 20:30:57 +02:00
Jonas Kvinge
b1f70982bf Make playlist column text elided
Fixes #801
2021-10-17 02:33:26 +02:00
Strawbs Bot
6128fb4f19 Update translations 2021-10-17 01:10:41 +02:00
Jonas Kvinge
ee915254e7 Song: Sort code in Song::ToProtobuf() 2021-10-17 00:34:33 +02:00
Jonas Kvinge
d835d4aae6 Add rating to Song::InitFromProtobuf and Song::ToProtobuf 2021-10-17 00:27:31 +02:00
Jonas Kvinge
5945d0ebee Read rating from tags
Fixes #790
2021-10-16 23:33:03 +02:00
Jonas Kvinge
3d3aacdcb1 InternetSearchView: Change popupmode for toolbutton to QToolButton::MenuButtonPopup 2021-10-16 22:41:14 +02:00
Jonas Kvinge
c3ce6cff72 GstEngine: Move CreateElement() to GstEnginePipeline 2021-10-16 21:28:56 +02:00
Jonas Kvinge
6d7a01fb4e Logging: Remove custom debug level for gst pipeline 2021-10-16 21:25:36 +02:00
Jonas Kvinge
8582e09e73 CI: Remove Ubuntu Groovy and add Impish 2021-10-16 14:30:45 +02:00
buckmelanoma
90744f2965 Fix filter menu button arrow overlap
Change collection filter menu button's popupMode to MenuButtonPopup to prevent arrow overlapping with button icon
2021-10-16 13:21:15 +02:00
Jonas Kvinge
03d0776fdc Turn on git revision 2021-10-15 00:02:06 +02:00
255 changed files with 30340 additions and 10520 deletions

View File

@@ -238,63 +238,68 @@ jobs:
CXX: g++-10
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_fedora_33:
name: Build Fedora 33
build_opensuse_tumbleweed_qt5:
name: Build openSUSE Tumbleweed Qt 5
runs-on: ubuntu-latest
container:
image: fedora:33
env:
RPM_BUILD_NCPUS: "2"
image: opensuse/tumbleweed
steps:
- uses: actions/checkout@v1.2.0
- name: Add tagparser repo
run: zypper -n ar -c -f -n 'repo-tagparser' https://download.opensuse.org/repositories/home:/mkittler/openSUSE_Tumbleweed/ repo-tagparser
- name: Update packages
run: yum update --assumeyes
run: zypper --non-interactive --gpg-auto-import-keys ref
- name: Upgrade packages
run: yum upgrade --assumeyes
- name: Install Fedora dependencies
run: zypper --non-interactive --gpg-auto-import-keys dup
- name: Install openSUSE dependencies
run: >
dnf install --assumeyes
@development-tools
redhat-lsb-core
zypper --non-interactive --gpg-auto-import-keys install
lsb-release
rpm-build
git
glibc
gcc-c++
rpmdevtools
tar
make
cmake
pkgconfig
glib
man
tar
gettext
openssh
gcc
gcc-c++
gettext-tools
glibc-devel
libboost_headers-devel
boost-devel
dbus-devel
protobuf-devel
protobuf-compiler
sqlite-devel
alsa-lib-devel
pulseaudio-libs-devel
glib2-devel
glib2-tools
dbus-1-devel
alsa-devel
libnotify-devel
gnutls-devel
qt5-qtbase-devel
qt5-qtbase-private-devel
qt5-qttools-devel
qt5-qtx11extras-devel
gstreamer1-devel
gstreamer1-plugins-base-devel
libgnutls-devel
protobuf-devel
sqlite3-devel
libpulse-devel
gstreamer-devel
gstreamer-plugins-base-devel
vlc-devel
taglib-devel
tagparser-devel
libQt5Core-devel
libQt5Gui-devel
libQt5Gui-private-headers-devel
libQt5Widgets-devel
libQt5Concurrent-devel
libQt5Network-devel
libQt5Sql-devel
libQt5DBus-devel
libQt5Test-devel
libqt5-qtbase-common-devel
libQt5Sql5-sqlite
libqt5-linguist-devel
libqt5-qtx11extras-devel
libcdio-devel
libgpod-devel
libmtp-devel
libchromaprint-devel
fftw-devel
desktop-file-utils
libappstream-glib
update-desktop-files
appstream-glib
hicolor-icon-theme
- name: Create Build Environment
shell: bash
@@ -302,16 +307,97 @@ jobs:
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON -DBUILD_WITH_QT5=ON -DUSE_TAGLIB=ON -DUSE_TAGPARSER=ON
- name: Create source tarball
working-directory: build
run: ../dist/scripts/maketarball.sh
- name: Create RPM build sources directories
working-directory: build
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
- name: Copy source tarball
working-directory: build
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
run: cp strawberry-*.tar.xz /usr/src/packages/SOURCES/
- name: Build RPM
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_opensuse_tumbleweed_qt6:
name: Build openSUSE Tumbleweed Qt 6
runs-on: ubuntu-latest
container:
image: opensuse/tumbleweed
steps:
- uses: actions/checkout@v1.2.0
- name: Add tagparser repo
run: zypper -n ar -c -f -n 'repo-tagparser' https://download.opensuse.org/repositories/home:/mkittler/openSUSE_Tumbleweed/ repo-tagparser
- name: Update packages
run: zypper --non-interactive --gpg-auto-import-keys ref
- name: Upgrade packages
run: zypper --non-interactive --gpg-auto-import-keys dup
- name: Install openSUSE dependencies
run: >
zypper --non-interactive --gpg-auto-import-keys install
lsb-release
rpm-build
git
tar
make
cmake
gcc
gcc-c++
gettext-tools
glibc-devel
libboost_headers-devel
boost-devel
glib2-devel
glib2-tools
dbus-1-devel
alsa-devel
libnotify-devel
libgnutls-devel
protobuf-devel
sqlite3-devel
libpulse-devel
gstreamer-devel
gstreamer-plugins-base-devel
vlc-devel
taglib-devel
tagparser-devel
qt6-core-devel
qt6-gui-devel
qt6-gui-private-devel
qt6-widgets-devel
qt6-concurrent-devel
qt6-network-devel
qt6-sql-devel
qt6-dbus-devel
qt6-test-devel
qt6-base-common-devel
qt6-sql-sqlite
qt6-linguist-devel
libcdio-devel
libgpod-devel
libmtp-devel
libchromaprint-devel
desktop-file-utils
update-desktop-files
appstream-glib
hicolor-icon-theme
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON -DBUILD_WITH_QT6=ON -DUSE_TAGLIB=ON -DUSE_TAGPARSER=ON
- name: Create source tarball
working-directory: build
run: ../dist/scripts/maketarball.sh
- name: Create RPM build sources directories
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
- name: Copy source tarball
working-directory: build
run: cp strawberry-*.tar.xz /usr/src/packages/SOURCES/
- name: Build RPM
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
@@ -394,6 +480,83 @@ jobs:
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_fedora_35:
name: Build Fedora 35
runs-on: ubuntu-latest
container:
image: fedora:35
env:
RPM_BUILD_NCPUS: "2"
steps:
- uses: actions/checkout@v1.2.0
- name: Update packages
run: yum update --assumeyes
- name: Upgrade packages
run: yum upgrade --assumeyes
- name: Install Fedora dependencies
run: >
dnf install --assumeyes
@development-tools
redhat-lsb-core
git
glibc
gcc-c++
rpmdevtools
make
cmake
pkgconfig
glib
man
tar
gettext
openssh
boost-devel
dbus-devel
protobuf-devel
protobuf-compiler
sqlite-devel
alsa-lib-devel
pulseaudio-libs-devel
libnotify-devel
gnutls-devel
qt6-qtbase-devel
qt6-qtbase-private-devel
qt6-qttools-devel
gstreamer1-devel
gstreamer1-plugins-base-devel
taglib-devel
libcdio-devel
libgpod-devel
libmtp-devel
libchromaprint-devel
fftw-devel
desktop-file-utils
libappstream-glib
hicolor-icon-theme
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: build
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_WERROR=ON
- name: Create source tarball
working-directory: build
run: ../dist/scripts/maketarball.sh
- name: Create RPM build sources directories
working-directory: build
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
- name: Copy source tarball
working-directory: build
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
- name: Build RPM
working-directory: build
run: rpmbuild -ba ../dist/unix/strawberry.spec
build_debian_buster:
name: Build Debian Buster
runs-on: ubuntu-latest
@@ -636,11 +799,11 @@ jobs:
run: dpkg-buildpackage -b -d -uc -us -nc -j2
build_ubuntu_groovy:
name: Build Ubuntu Groovy
build_ubuntu_hirsute:
name: Build Ubuntu Hirsute
runs-on: ubuntu-latest
container:
image: ubuntu:groovy
image: ubuntu:hirsute
steps:
- uses: actions/checkout@v1.2.0
- name: Install Ubuntu dependencies
@@ -699,11 +862,11 @@ jobs:
run: dpkg-buildpackage -b -d -uc -us -nc -j2
build_ubuntu_hirsute:
name: Build Ubuntu Hirsute
build_ubuntu_impish:
name: Build Ubuntu Impish
runs-on: ubuntu-latest
container:
image: ubuntu:hirsute
image: ubuntu:impish
steps:
- uses: actions/checkout@v1.2.0
- name: Install Ubuntu dependencies
@@ -974,157 +1137,8 @@ jobs:
path: build/strawberry-*.dmg
build-windows-mingw-qt5:
name: Build Windows MinGW Qt 5
runs-on: ubuntu-latest
container:
image: jonaski/strawberry-mxe-x86_64
steps:
- uses: actions/checkout@v1.2.0
- name: Create Build Environment
shell: bash
run: cmake -E make_directory build
- name: Link MXE directory
shell: bash
run: ln -s /usr/src/strawberry-mxe ~/mxe-shared
- name: Run CMake
shell: bash
env:
PKG_CONFIG_PATH: /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/lib/pkgconfig
working-directory: build
run: >
cmake ..
-DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-x86_64-w64-mingw32-shared.cmake
-DCMAKE_BUILD_TYPE=Release
-DBUILD_WITH_QT5=ON
-DBUILD_WERROR=OFF
-DARCH=x86_64
-DENABLE_WIN32_CONSOLE=OFF
-DENABLE_DBUS=OFF
-DENABLE_LIBGPOD=OFF
-DENABLE_LIBMTP=OFF
-DProtobuf_PROTOC_EXECUTABLE=/usr/src/strawberry-mxe/usr/x86_64-pc-linux-gnu/bin/protoc
- name: Run Make
working-directory: build
run: cmake --build . --config Release --parallel $(nproc)
- name: Create directories
working-directory: build
run: mkdir -p gio-modules platforms sqldrivers imageformats styles gstreamer-plugins nsisplugins
- name: Copy GIO modules
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/lib/gio/modules/libgiognutls.dll ${GITHUB_WORKSPACE}/build/gio-modules/
- name: Copy Qt platform plugins
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/qt5/plugins/platforms/qwindows.dll ${GITHUB_WORKSPACE}/build/platforms/
- name: Copy Qt styles
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/qt5/plugins/styles/qwindowsvistastyle.dll ${GITHUB_WORKSPACE}/build/styles/
- name: Copy Qt SQL drivers
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/qt5/plugins/sqldrivers/qsqlite.dll ${GITHUB_WORKSPACE}/build/sqldrivers/
- name: Copy Qt imageformats
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/qt5/plugins/imageformats/{qgif.dll,qico.dll,qjpeg.dll} ${GITHUB_WORKSPACE}/build/imageformats/
- name: Copy gstreamer plugins
working-directory: build
run: >
cp
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstapp.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstcoreelements.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstaudioconvert.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstaudiofx.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstaudiomixer.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstaudioparsers.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstaudiorate.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstaudioresample.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstaudiotestsrc.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstautodetect.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstplayback.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstvolume.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstspectrum.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstequalizer.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstreplaygain.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgsttypefindfunctions.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstgio.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstdirectsound.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstwasapi.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstpbtypes.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstapetag.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgsticydemux.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstid3demux.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgsttaglib.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgsttcp.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstudp.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstsoup.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstcdio.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstrtp.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstrtsp.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstflac.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstwavparse.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstwavpack.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstogg.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstvorbis.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstopus.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstopusparse.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstspeex.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstlame.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstaiff.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstfaac.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstfaad.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstisomp4.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstasf.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstasfmux.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstlibav.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstopenmpt.dll
${GITHUB_WORKSPACE}/build/gstreamer-plugins/
- name: Copy extra binaries
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/{sqlite3.exe,killproc.exe,gst-launch-1.0.exe,gst-discoverer-1.0.exe} .
- name: Copy dependencies
working-directory: build
run: >
/usr/src/strawberry-mxe/tools/copydlldeps.sh
-c
-d .
-F .
-F ./platforms
-F ./styles
-F ./sqldrivers
-F ./imageformats
-F ./gstreamer-plugins
-R /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared
- name: Strip binaries
working-directory: build
run: find . -type f \( -iname \*.dll -o -iname \*.exe \) -exec /usr/src/strawberry-mxe/usr/bin/x86_64-w64-mingw32.shared-strip {} \;
- name: Copy nsis files
working-directory: build
run: cp ${GITHUB_WORKSPACE}/dist/windows/*.nsi ${GITHUB_WORKSPACE}/dist/windows/*.nsh ${GITHUB_WORKSPACE}/dist/windows/*.ico .
- name: Copy COPYING license file
working-directory: build
run: cp ${GITHUB_WORKSPACE}/COPYING .
- name: Build Windows installer
working-directory: build
run: makensis strawberry.nsi
build-windows-mingw-qt6:
name: Build Windows MinGW Qt 6
build-windows-mingw:
name: Build Windows MinGW
runs-on: ubuntu-latest
container:
image: jonaski/strawberry-mxe-x86_64
@@ -1241,11 +1255,13 @@ jobs:
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstasfmux.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstlibav.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstopenmpt.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstdash.dll
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstxingmux.dll
${GITHUB_WORKSPACE}/build/gstreamer-plugins/
- name: Copy extra binaries
working-directory: build
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/{sqlite3.exe,killproc.exe,gst-launch-1.0.exe,gst-discoverer-1.0.exe} .
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/{sqlite3.exe,gst-launch-1.0.exe,gst-discoverer-1.0.exe} .
- name: Copy dependencies
working-directory: build

View File

@@ -87,7 +87,7 @@ int main(int argc, char **argv)
appBundlePath = QDir::cleanPath(appBundlePath);
if (QDir().exists(appBundlePath) == false) {
if (!QDir(appBundlePath).exists()) {
qDebug() << "Error: Could not find app bundle" << appBundlePath;
return 1;
}

View File

@@ -104,7 +104,7 @@ inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
bool copyFilePrintStatus(const QString &from, const QString &to)
{
if (QFile(to).exists()) {
if (QFile::exists(to)) {
if (alwaysOwerwriteEnabled) {
QFile(to).remove();
} else {
@@ -141,7 +141,7 @@ bool copyFilePrintStatus(const QString &from, const QString &to)
bool linkFilePrintStatus(const QString &file, const QString &link)
{
if (QFile(link).exists()) {
if (QFile::exists(link)) {
if (QFile(link).symLinkTarget().isEmpty())
LogError() << link << "exists but it's a file.";
else
@@ -754,7 +754,7 @@ QString copyDylib(const FrameworkInfo &framework, const QString path)
}
// Return if the dylib has already been deployed
if (QFileInfo(dylibDestinationBinaryPath).exists() && !alwaysOwerwriteEnabled)
if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
return dylibDestinationBinaryPath;
// Copy dylib binary
@@ -821,7 +821,7 @@ QString copyFramework(const FrameworkInfo &framework, const QString path)
// Contents/Info.plist should be Versions/5/Resources/Info.plist
const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
if (QFile(legacyInfoPlistPath).exists()) {
if (QFile::exists(legacyInfoPlistPath)) {
copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
patch_debugInInfoPlist(correctInfoPlistPath);
}
@@ -1443,11 +1443,11 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
#endif
// Fallback: Look relative to the macdeployqt binary
if (!QFile(qmlImportScannerPath).exists())
if (!QFile::exists(qmlImportScannerPath))
qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
// Verify that we found a qmlimportscanner binary
if (!QFile(qmlImportScannerPath).exists()) {
if (!QFile::exists(qmlImportScannerPath)) {
LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
return false;

View File

@@ -3,10 +3,8 @@ cmake_minimum_required(VERSION 3.0)
include(CheckIncludeFiles)
include(CheckFunctionExists)
if(CMAKE_VERSION VERSION_GREATER 3.0)
check_function_exists(geteuid HAVE_GETEUID)
check_function_exists(getpwuid HAVE_GETPWUID)
endif()
check_function_exists(geteuid HAVE_GETEUID)
check_function_exists(getpwuid HAVE_GETPWUID)
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)

View File

@@ -396,7 +396,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
}
// Read the message body
QByteArray msgBytes = sock->read(info.msgLen);
QByteArray msgBytes = sock->read(static_cast<qint64>(info.msgLen));
QDataStream readStream(msgBytes);
readStream.setVersion(QDataStream::Qt_5_8);

View File

@@ -396,7 +396,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
}
// Read the message body
QByteArray msgBytes = sock->read(info.msgLen);
QByteArray msgBytes = sock->read(static_cast<qint64>(info.msgLen));
QDataStream readStream(msgBytes);
readStream.setVersion(QDataStream::Qt_5_8);

View File

@@ -55,7 +55,7 @@ struct InstancesInfo {
struct ConnectionInfo {
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
qint64 msgLen;
quint64 msgLen;
quint32 instanceId;
quint8 stage;
};

View File

@@ -523,6 +523,10 @@ elseif(NOT HAVE_GSTREAMER)
message(WARNING "GStreamer is the only engine that is fully implemented. Using other engines is possible but not recommended.")
endif()
if(QT_VERSION_MAJOR EQUAL 5)
message(WARNING "It is detected that Strawberry is being built with Qt 5. There are no bugfix releases for the latest minor LTS version of Qt 5 available to open-source users, only commercial users. Therefore Strawberry should be built with Qt 6 when possible. Building with Qt 6 will also take advantage of improvements and new features not available in Qt 5. To build with Qt 6 specify -DBUILD_WITH_QT6=ON to automatically detect Qt 6, or for example -DCMAKE_PREFIX_PATH=/usr/local/lib64/cmake to manually specify the Qt 6 directory.")
endif()
if(NOT CMAKE_CROSSCOMPILING)
if(QT_SQLITE_TEST)
if(NOT SQLITE_FTS5_TEST)

159
Changelog
View File

@@ -2,7 +2,45 @@ Strawberry Music Player
=======================
ChangeLog
1.0.0:
Version 1.0.1 (2022.01.08)
Bugfixes:
* Fixed collection and internet search filter tool button menu arrow overlap (#796).
* Fixed stop after this track button with Qt 6 (#795).
* Fixed not updating the URL when songs were moved on disk when the fingerprinting feature is enabled.
* Fixed SQL query error for songs with an invalid modification time (#815).
* Fixed blocky rendering of the currently playing track with high resolution screens (#794).
* Fixed incorrect playlist column filesize for radio streams.
* Fixed deleting embedded album cover from Ogg songs.
* Fixed parsing of Cue tracks with 1-digit minutes (#836).
* Fixed updating of playlist summary after reloading items when adding songs from files outside of the collection (#848).
* Fixed always saving metadata when saving playlists for Tidal, Qobuz and Subsonic songs independent of playlist setting (#851).
* Fixed setting media shortcuts when using kglobalaccel (#849).
* Fixed parsing of Genius lyrics when they are sometimes received in a different HTML format.
* Fixed saving MP4 specific tags as UTF-8 (#830).
* Fixed clearing "manually set" cover when saving album covers embedded from outside of the tag editor (#858).
* Fixed aborting collection scan when Strawberry exists to avoid hang on exit.
* Fixed resuming collection scan when adding a new directory after collection scan was aborted.
* Fixed excluding hidden songs from the collection.
* Disabled moodbar for CUE songs since they can not be supported properly (#865).
* (Windows) Added gstreamer gstxingmux plugin to fix transcoding to MP3 (#856).
Enhancements:
* Made playlist header column text elided (#801).
* Added support for reading and writing playcounts and ratings from/to tags.
* Added support for setting rating using the edit tag dialog.
* Added setting to enable/disable playlist toolbar (#809).
* Added component type, content_rating type and releases to AppStream data file (#806).
* Removed unused "mark as listened" option in organize dialog.
* Fixed some clazy warnings and narrowing conversions in the source code.
* Replaced uses of macros in the source code.
* Added a more user-friendly error message when receiving encrypted streams from Tidal (#824).
* Added support for port-pattern entered in the device textbox when using Jack as output (#828).
* Added Spanish (Spain) translation.
* Added support for more CUE filenames (#835).
* (Windows) Add gstreamer dash plugin.
Version 1.0.0 (2021.10.14)
Bugfixes:
* Fix updating temporary metadata when reloading songs outside of the collection.
@@ -56,7 +94,8 @@ ChangeLog
* Add support for native global shortcuts on MATE.
* Add radios view with channels from Radio Paradise and SomaFM.
0.9.3:
Version 0.9.3 (2021.04.18)
Bugfixes:
* Fix "Show in file browser" to work with thunar.
@@ -79,7 +118,8 @@ ChangeLog
* (macOS) Make macdeployqt work with Qt 5 too.
* (macOS) Show keep running option in behaviour settings.
0.9.2:
Version 0.9.2 (2021.03.25)
Bugfixes:
* Fix marking songs available.
@@ -90,7 +130,8 @@ ChangeLog
* (macOS) Fix crash when opening cover manager.
* (macOS) Fix broken Qt plugins resulting in album covers not showing.
0.9.1:
Version 0.9.1 (2021.03.13)
Bugfixes:
* Fix duplicating songs in the DB when organizing songs between 2 different collection directories.
@@ -125,7 +166,8 @@ ChangeLog
New features:
* Add option and support for saving embedded covers for FLAC, Ogg Vorbis, MP3 and MP4/AAC.
0.8.5:
Version 0.8.5 (2020.12.19)
Bugfixes:
* Fix return type of SmartPlaylistQueryWizardPlugin::type().
@@ -144,7 +186,8 @@ ChangeLog
* Add command line option to play a playlist based on name.
* Change double-click behaviour in cover manager to open fullsize cover.
0.8.4:
Version 0.8.4 (2020.11.15)
Bugfixes:
* Fix preventing session logout when window is maxmimized.
@@ -170,7 +213,8 @@ ChangeLog
* Remove remaining uses of QTextCodec.
* Remove Core5Compat dependency.
0.8.3:
Version 0.8.3 (2020.10.24)
Bugfixes:
* Fixed updating playing widget song details in small cover mode.
@@ -185,7 +229,8 @@ ChangeLog
Enhancements:
* (Windows) Added WASAPI plugin.
0.8.2:
Version 0.8.2 (2020.10.13)
Bugfixes:
* Fixed broken transition to next song for CUE files with certain audio formats (regression since version 0.6.13).
@@ -197,7 +242,8 @@ ChangeLog
* Removed use of HTML in system tray icon tooltip for all desktop environments instead of just KDE and Cinnamon.
* (Windows) Ignore "IDirectSoundBuffer_GetStatus The operation completed successfully" false error when switching device while playing.
0.8.1:
Version 0.8.1 (2020.10.09)
Bugfixes:
* Fixed engine selection in backend settings with Qt 6.
@@ -244,14 +290,15 @@ ChangeLog
* Added Subsonic server side scrobbling support.
* Load thumbnails from iPods to show under device collection.
0.7.2:
Version 0.7.2 (2020.08.15)
Bugfixes:
* Fixed installation directory for translations.
* Fixed collection sorting for non-ASCII characters.
* Fixed closing connected devices on exit.
0.7.1:
Version 0.7.1 (2020.08.15)
Bugfixes:
* Fixed incorrectly mapped global shortcuts keys "2" and "3".
@@ -290,7 +337,8 @@ ChangeLog
* Removed Xine engine support.
* Removed broken imobiledevice (iPhone) support.
0.6.13:
Version 0.6.13 (2020.07.13)
Bugfixes:
* Fixed cut-off text in about dialog.
@@ -316,7 +364,8 @@ ChangeLog
* Fixed unit test for testing playlist model.
* Added new unit tests for tagreader.
0.6.12:
Version 0.6.12 (2020.06.07)
Bugfixes:
* Fixed height of about dialog.
@@ -329,7 +378,8 @@ ChangeLog
* Sort folders added from file view.
* Changed default collection grouping to album - disc.
0.6.11:
Version 0.6.11 (2020.05.16)
Bugfixes:
* Fixed MPRIS missing art url when playing albums with embedded cover.
@@ -355,7 +405,8 @@ ChangeLog
* Added album covers from Musixmatch and Spotify.
* Added lyrics from Genius, Musixmatch and ChartLyrics.
0.6.10:
Version 0.6.10 (2020.05.01)
Bugfixes:
* Fixed Subsonic album covers not working for albums with non ASCII characters.
@@ -395,7 +446,8 @@ ChangeLog
Removed features:
* Removed Phonon engine support.
Version 0.6.9:
Version 0.6.9 (2020.03.09)
BugFixes:
* Fixed playlist metadata updating interfering with manual tag editing.
@@ -427,7 +479,8 @@ Version 0.6.9:
* Tidal support (No agreement).
* QObuz support (No agreement).
Version 0.6.8:
Version 0.6.8 (2020.01.05)
* Fixed stuck tabbar and collection GUI with some themes.
* Fixed possible crashes related to QProxyStyle.
@@ -444,7 +497,8 @@ Version 0.6.8:
* (macOS) Fixed filesystem watcher to correctly pick up changed collection directories.
* (Windows) Fixed translations not being included.
Version 0.6.7:
Version 0.6.7 (2019.11.27)
* Fixed crash when cancelling scrobbler authentication
* Fixed "Double clicking a song in the playlist" behaviour setting
@@ -461,7 +515,8 @@ Version 0.6.7:
* Removed left click on analyzer to popup menu
* (Windows) Added killproc executable to terminate running process before uninstalling
Version 0.6.6:
Version 0.6.6 (2019.11.09)
* Fixed lowercased album artist in playlist column
* Fixed compiling with different optional features turned off
@@ -479,7 +534,8 @@ Version 0.6.6:
* Added option to automatically select current playing track
* (Windows) Added support for WASAPI
Version 0.6.5:
Version 0.6.5 (2019.09.30)
* Fixed scrobbler not to send scrobbles multiple times when metadata is updated
* Fixed Listenbrainz scrobbler not don't send "various artists" as album artist
@@ -488,7 +544,8 @@ Version 0.6.5:
* Fixed OSD pretty positioning on Windows on screens with negative geometry
* Fixed appdata file to pass full validation
Version 0.6.4:
Version 0.6.4 (2019.09.25)
* Added setting for fancy tabbar background color
* Added setting to make marking songs unavailable optional
@@ -515,18 +572,21 @@ Version 0.6.4:
* Fixed restoring to original window size when restoring from system tray
* Updated 3rdparty taglib
Version 0.6.3:
Version 0.6.3 (2019.08.05)
* Fixed crash when using internet services.
* Fixed musicbrainz tagfetcher only showing 1 result per song.
* Fixed collection watcher to unwatch deleted directories.
* Added "album - disc" grouping.
Version 0.6.2:
Version 0.6.2 (2019.08.03)
* Disabled fatal error for FTS5 cmake test.
Version 0.6.1:
Version 0.6.1 (2019.08.03)
* Compare artist and album case-insensitive when generating score for album covers.
* Fixed broken return value of sendMessage() in SingleApplication causing application to be started twice.
@@ -572,11 +632,13 @@ Version 0.6.1:
* Fixed certain cases where the playing widget gets stuck when switching fast between context and other widgets.
* Removed ChartLyrics provider (service have been down for a long time).
Version 0.5.5:
Version 0.5.5 (2019.05.05)
* Fixed Tidal API url
Version 0.5.4:
Version 0.5.4 (2019.05.05)
* Changed description for offline mode scrobbling for less confusion
* Fixed scrobbler to not send "playing now" when in offline mode
@@ -607,7 +669,8 @@ Version 0.5.4:
* Fixed and improved snap including upgrading to core18 and adding proper alsa support
* Fixed resume playback on startup not working for other than the first playlist
Version 0.5.3:
Version 0.5.3 (2019.03.02)
* Changed default tagging to albumartist in organise dialog
* Removed support for older taglib in tagreader
@@ -647,7 +710,8 @@ Version 0.5.3:
* Added group by format
* Fixed gstreamer leaks
Version 0.5.2:
Version 0.5.2 (2019.01.26)
* Added error handling and message for URL handler
* Added SingleCoreApplication secondary check
@@ -664,7 +728,8 @@ Version 0.5.2:
* Added option to copy album cover in organise dialog (filesystem and libgpod devices)
* Added raise() to make sure window is on top when strawberry is started twice
Version 0.5.1:
Version 0.5.1 (2019.01.12)
* Added scrobbler with support for Last.fm, Libre.fm and ListenBrainz
* Fixed key up causing playback to reset
@@ -695,7 +760,8 @@ Version 0.5.1:
* Added debian copyright file
* Fixed some compile errors
Version 0.4.2:
Version 0.4.2 (2018.11.28)
* Updated AppStream data file to newer specifications
* Fixed Deezer engine to use quality setting
@@ -709,7 +775,8 @@ Version 0.4.2:
* (Windows) Corrected uninstalled files on x64 installer
* (macOS) Fixed poor performance
Version 0.4.1:
Version 0.4.1 (2018.11.01)
* Fixed crash in analyzer
* Fixed trying to use systray even if the desktop had no systray
@@ -727,11 +794,13 @@ Version 0.4.1:
* Added AppStream data file
* Fixed compiling with Qt 5 versions of system QtSingleApplication and Qxt library
Version 0.3.3:
Version 0.3.3 (2018.09.24)
* Fixed Tidal login
Version 0.3.2:
Version 0.3.2 (2018.09.24)
* Fixed search error not shown in Tidal search
* Added URL handler for Tidal, now retrieving URL's when playing instead of when searching
@@ -743,7 +812,8 @@ Version 0.3.2:
* Added encoding of Tidal token in the source code
* Added encoding of Tidal password in the configuration
Version 0.3.1:
Version 0.3.1 (2018.09.15)
* Added new lyrics provider with lyrics from AudD and API Seeds
* New improved context widget with albums and lyrics
@@ -765,7 +835,8 @@ Version 0.3.1:
* Added support for reading/writing lyrics to tags
* Fixed saving tags (APE) for WavPack files
Version 0.2.1:
Version 0.2.1 (2018.07.05)
* Fixed crash with newer Qt
* Fixed setting output/device for Xine and VLC backend
@@ -775,19 +846,23 @@ Version 0.2.1:
* Fixed device selection on macOS
* Added xine on to windows build
Version 0.1.6:
Version 0.1.6 (2018.06.07)
* Fixed crash on exit caused by NVIDIA driver
* Fixed PulseAudio device selection
* Improvements to device selection
Version 0.1.5:
Version 0.1.5 (2018.05.16)
* Makefile fixes for building
Version 0.1.4:
Version 0.1.4 (2018.05.14)
* Fixed compliation with clang compiler
* This release is mainly to get it working on openbsd and freebsd.
Version 0.1.3:
Version 0.1.3 (2018.05.12)
* Audio file detection by content
* Added builtin taglib to 3rdparty to support detecting audio by content instead of just file extension
* Removed unneeded qsqlite from 3rdparty
@@ -795,7 +870,8 @@ Version 0.1.3:
* Replaced incorrect DLL libgstdirectsoundsink.dll (from gst 1.12.4) instead of libgstdirectsound.dll (from gst 1.14.0) for windows build
* Fixed git versioning
Version 0.1.2:
Version 0.1.2 (2018.05.02)
* Fixed playback of WavPack files
* Fixed musicbrainz tagfetcher
* Use common regex (Song::kCoverRemoveDisc) for removing Disc/CD from album
@@ -805,5 +881,6 @@ Version 0.1.2:
* Fixed problems with windows build missing some DLL's, only supplying required gstreamer-plugins now
* Removed redundant code
Version 0.1.1:
Version 0.1.1 (2018.04.07)
* Initial release

View File

@@ -24,7 +24,7 @@ macro(summary_show)
list(SORT summary_willbuild)
list(SORT summary_willnotbuild)
message("")
message("Building strawberry version: ${STRAWBERRY_VERSION_DISPLAY}")
message("Building strawberry version: ${STRAWBERRY_VERSION_DISPLAY}, Qt version ${Qt${QT_VERSION_MAJOR}Core_VERSION}")
summary_show_part(summary_willbuild "The following components will be built:")
summary_show_part(summary_willnotbuild "The following components WILL NOT be built:")
message("")

View File

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

View File

@@ -95,6 +95,7 @@
<file>icons/128x128/radio.png</file>
<file>icons/128x128/somafm.png</file>
<file>icons/128x128/radioparadise.png</file>
<file>icons/128x128/musicbrainz.png</file>
<file>icons/64x64/albums.png</file>
<file>icons/64x64/alsa.png</file>
<file>icons/64x64/application-exit.png</file>
@@ -191,6 +192,7 @@
<file>icons/64x64/radio.png</file>
<file>icons/64x64/somafm.png</file>
<file>icons/64x64/radioparadise.png</file>
<file>icons/64x64/musicbrainz.png</file>
<file>icons/48x48/albums.png</file>
<file>icons/48x48/alsa.png</file>
<file>icons/48x48/application-exit.png</file>
@@ -291,6 +293,7 @@
<file>icons/48x48/radio.png</file>
<file>icons/48x48/somafm.png</file>
<file>icons/48x48/radioparadise.png</file>
<file>icons/48x48/musicbrainz.png</file>
<file>icons/32x32/albums.png</file>
<file>icons/32x32/alsa.png</file>
<file>icons/32x32/application-exit.png</file>
@@ -391,6 +394,7 @@
<file>icons/32x32/radio.png</file>
<file>icons/32x32/somafm.png</file>
<file>icons/32x32/radioparadise.png</file>
<file>icons/32x32/musicbrainz.png</file>
<file>icons/22x22/albums.png</file>
<file>icons/22x22/alsa.png</file>
<file>icons/22x22/application-exit.png</file>
@@ -491,5 +495,6 @@
<file>icons/22x22/radio.png</file>
<file>icons/22x22/somafm.png</file>
<file>icons/22x22/radioparadise.png</file>
<file>icons/22x22/musicbrainz.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -19,6 +19,18 @@
background-clip: content;
}
#context-layout-container {
background-color: %palette-base;
}
#context-widget-scrollarea {
background-color: %palette-base;
}
#context-layout-scrollarea {
background-color: %palette-base;
}
QToolButton {
border: 2px solid transparent;
border-radius: 3px;
@@ -60,14 +72,3 @@ macos QMenu {
font-size: 13pt;
}
#context-layout-container {
background-color: %palette-base;
}
#context-widget-scrollarea {
background-color: %palette-base;
}
#context-layout-scrollarea {
background-color: %palette-base;
}

179
dist/scripts/import-from-clementine.sh vendored Executable file
View File

@@ -0,0 +1,179 @@
#!/usr/bin/env bash
# Strawberry Music Player
# Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
# 2021 Alexey Vazhnov
#
# 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/>.
# SPDX-License-Identifier: GPL-3.0-only
# Based on https://github.com/strawberrymusicplayer/strawberry/wiki/Import-collection-library-and-playlists-data-from-Clementine
set -o nounset
set -o errexit
set -o pipefail
shopt -s dotglob
# Use hardcoded path if no parameters. No need in quotes here! See `man bash`, "Parameter Expansion":
FILE_SRC=${1:-~/.config/Clementine/clementine.db}
FILE_DST=${2:-~/.local/share/strawberry/strawberry/strawberry.db}
test -f "$FILE_SRC" || { echo "No such file: $FILE_SRC"; exit 1; }
test -f "$FILE_DST" || { echo "No such file: $FILE_DST"; exit 1; }
echo "Will try to copy information from $FILE_SRC to $FILE_DST."
echo
echo 'This script will **delete all information** from Strawberry database!'
read -r -p 'Do you want to continue? (the only YES is accepted) ' answer
if [ "$answer" != "YES" ]; then exit 1; fi
# 'heredoc' with substitution of variables, see `man bash`, "Here Documents":
sqlite3 -batch << EOF
.echo on
ATTACH '$FILE_DST' AS strawberry;
ATTACH '$FILE_SRC' AS clementine;
.bail on
.databases
/* This must be done when importing all data from Clementine because playlists are based on ROWIDs */
DELETE FROM strawberry.directories;
DELETE FROM strawberry.subdirectories;
DELETE FROM strawberry.songs;
DELETE FROM strawberry.playlists;
DELETE FROM strawberry.playlist_items;
/* Import all data from the collection library songs */
INSERT INTO strawberry.directories (path, subdirs) SELECT path, subdirs FROM clementine.directories;
INSERT INTO strawberry.subdirectories (directory_id, path, mtime) SELECT directory, path, mtime FROM clementine.subdirectories;
INSERT INTO strawberry.songs (ROWID, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, beginning, length, bitrate, samplerate, directory_id, url, filetype, filesize, mtime, ctime, unavailable, playcount, skipcount, lastplayed, compilation_detected, compilation_on, compilation_off, compilation_effective, art_automatic, art_manual, effective_albumartist, effective_originalyear, cue_path, rating)
SELECT ROWID, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, beginning, length, bitrate, samplerate, directory, filename, filetype, filesize, mtime, ctime, unavailable, playcount, skipcount, lastplayed, sampler, forced_compilation_on, forced_compilation_off, effective_compilation, art_automatic, art_manual, effective_albumartist, effective_originalyear, cue_path, rating FROM clementine.songs WHERE unavailable = 0;
UPDATE strawberry.songs SET source = 2;
UPDATE strawberry.songs SET artist_id = "";
UPDATE strawberry.songs SET album_id = "";
UPDATE strawberry.songs SET song_id = "";
/* Import playlists */
INSERT INTO strawberry.playlists (ROWID, name, last_played, special_type, ui_path, is_favorite, dynamic_playlist_type, dynamic_playlist_data, dynamic_playlist_backend)
SELECT ROWID, name, last_played, special_type, ui_path, is_favorite, dynamic_playlist_type, dynamic_playlist_data, dynamic_playlist_backend FROM clementine.playlists WHERE dynamic_playlist_type ISNULL;
/* Import playlist items */
INSERT INTO strawberry.playlist_items
(ROWID,
playlist,
collection_id,
title,
album,
artist,
albumartist,
track,
disc,
year,
originalyear,
genre,
compilation,
composer,
performer,
grouping,
comment,
lyrics,
beginning,
length,
bitrate,
samplerate,
directory_id,
url,
filetype,
filesize,
mtime,
ctime,
unavailable,
playcount,
skipcount,
lastplayed,
compilation_detected,
compilation_on,
compilation_off,
compilation_effective,
art_automatic,
art_manual,
effective_albumartist,
effective_originalyear,
cue_path,
rating
)
SELECT ROWID,
playlist,
library_id,
title,
album,
artist,
albumartist,
track,
disc,
year,
originalyear,
genre,
compilation,
composer,
performer,
grouping,
comment,
lyrics,
beginning,
length,
bitrate,
samplerate,
directory,
filename,
filetype,
filesize,
mtime,
ctime,
unavailable,
playcount,
skipcount,
lastplayed,
sampler,
forced_compilation_on,
forced_compilation_off,
effective_compilation,
art_automatic,
art_manual,
effective_albumartist,
effective_originalyear,
cue_path,
rating FROM clementine.playlist_items WHERE type = 'Library';
UPDATE strawberry.playlist_items SET source = 2;
UPDATE strawberry.playlist_items SET type = 2;
UPDATE strawberry.playlist_items SET artist_id = "";
UPDATE strawberry.playlist_items SET album_id = "";
UPDATE strawberry.playlist_items SET song_id = "";
/* Recreate the FTS tables */
DELETE FROM strawberry.songs_fts;
INSERT INTO strawberry.songs_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment)
SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment
FROM strawberry.songs;
EOF
# To be sure script didn't exit because of any error (because we use `set -o errexit`):
echo "Script finished"

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<component>
<component type="desktop-application">
<id>org.strawberrymusicplayer.strawberry</id>
<launchable type="desktop-id">org.strawberrymusicplayer.strawberry.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license>
@@ -13,6 +13,7 @@
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
<translation type="qt">strawberry</translation>
<content_rating type="oars-1.1" />
<description>
<p>
Strawberry is a music player and music collection organizer. It is aimed at music collectors and audiophiles. With Strawberry you can play and manage your digital music collection, or stream your favorite radios. It also has unofficial streaming support for Tidal and Qobuz. Strawberry is free software released under GPL. The source code is available on GitHub. It's written in C++ using the Qt toolkit and GStreamer. Strawberry is compatible with both Qt version 5 and 6.
@@ -48,4 +49,7 @@
</screenshot>
</screenshots>
<update_contact>eclipseo@fedoraproject.org</update_contact>
<releases>
<release version="1.0.0" date="2021-10-16"/>
</releases>
</component>

View File

@@ -137,7 +137,7 @@ Features:
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
%endif
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7)
%make_build
%else

View File

@@ -18,10 +18,6 @@
!define debug
!endif
!if "@BUILD_WITH_QT6@" == "ON"
!define with_qt6
!endif
!ifdef debug
!define PRODUCT_NAME "Strawberry Music Player Debug"
!define PRODUCT_NAME_SHORT "Strawberry"
@@ -103,33 +99,17 @@ UninstPage custom un.LockedListPageShow
Name "${PRODUCT_NAME}"
!ifdef arch_x86
!ifdef debug
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x86.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-Debug-x86.exe"
!endif
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x86.exe"
!else
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x86.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-x86.exe"
!endif
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x86.exe"
!endif
!endif
!ifdef arch_x64
!ifdef debug
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x64.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-Debug-x64.exe"
!endif
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x64.exe"
!else
!ifdef with_qt6
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x64.exe"
!else
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-x64.exe"
!endif
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x64.exe"
!endif
!endif
@@ -215,23 +195,25 @@ Section "Strawberry" Strawberry
File "libcdio-19.dll"
File "libchromaprint.dll"
File "libdl.dll"
File "libfaac-0.dll"
File "libfaad-2.dll"
File "libffi-7.dll"
File "libffi-8.dll"
File "libfftw3-3.dll"
File "libFLAC-8.dll"
File "libfreetype-6.dll"
File "libfaac-0.dll"
File "libfaad-2.dll"
File "libgio-2.0-0.dll"
File "libglib-2.0-0.dll"
File "libgmodule-2.0-0.dll"
File "libgmp-10.dll"
File "libgnutls-30.dll"
File "libgobject-2.0-0.dll"
File "libgstadaptivedemux-1.0-0.dll"
File "libgstapp-1.0-0.dll"
File "libgstaudio-1.0-0.dll"
File "libgstbadaudio-1.0-0.dll"
File "libgstbase-1.0-0.dll"
File "libgstfft-1.0-0.dll"
File "libgstisoff-1.0-0.dll"
File "libgstnet-1.0-0.dll"
File "libgstpbutils-1.0-0.dll"
File "libgstreamer-1.0-0.dll"
@@ -240,6 +222,7 @@ Section "Strawberry" Strawberry
File "libgstrtsp-1.0-0.dll"
File "libgstsdp-1.0-0.dll"
File "libgsttag-1.0-0.dll"
File "libgsturidownloader-1.0-0.dll"
File "libgstvideo-1.0-0.dll"
File "libharfbuzz-0.dll"
File "libhogweed-6.dll"
@@ -257,13 +240,14 @@ Section "Strawberry" Strawberry
File "libpcre-1.dll"
File "libpcre2-16-0.dll"
File "libpng16-16.dll"
File "libprotobuf-29.dll"
File "libpsl-5.dll"
File "libsoup-2.4-1.dll"
File "libprotobuf-30.dll"
File "libqtsparkle-qt6.dll"
File "libspeex-1.dll"
File "libsqlite3-0.dll"
File "libssp-0.dll"
File "libstdc++-6.dll"
File "libsoup-2.4-1.dll"
File "libtag.dll"
File "libtasn1-6.dll"
File "libunistring-2.dll"
@@ -275,27 +259,15 @@ Section "Strawberry" Strawberry
File "libxml2-2.dll"
File "libzstd.dll"
File "postproc-55.dll"
File "swresample-3.dll"
File "swscale-5.dll"
File "zlib1.dll"
!ifdef with_qt6
File "Qt6Concurrent.dll"
File "Qt6Core.dll"
File "Qt6Gui.dll"
File "Qt6Network.dll"
File "Qt6Sql.dll"
File "Qt6Widgets.dll"
File "libqtsparkle-qt6.dll"
!else
File "Qt5Concurrent.dll"
File "Qt5Core.dll"
File "Qt5Gui.dll"
File "Qt5Network.dll"
File "Qt5Sql.dll"
File "Qt5Widgets.dll"
File "libqtsparkle-qt5.dll"
!endif
File "swresample-3.dll"
File "swscale-5.dll"
File "zlib1.dll"
!ifdef debug
File "gdb.exe"
@@ -353,6 +325,16 @@ Section "Qt styles" styles
File "/oname=qwindowsvistastyle.dll" "styles\qwindowsvistastyle.dll"
SectionEnd
Section "Qt TLS plugins" tls
SetOutPath "$INSTDIR\tls"
File "/oname=qopensslbackend.dll" "tls\qopensslbackend.dll"
SectionEnd
Section "Qt SQL Drivers" sqldrivers
SetOutPath "$INSTDIR\sqldrivers"
File "/oname=qsqlite.dll" "sqldrivers\qsqlite.dll"
SectionEnd
Section "Qt imageformats" imageformats
SetOutPath "$INSTDIR\imageformats"
File "/oname=qgif.dll" "imageformats\qgif.dll"
@@ -360,23 +342,14 @@ Section "Qt imageformats" imageformats
File "/oname=qjpeg.dll" "imageformats\qjpeg.dll"
SectionEnd
!ifdef with_qt6
Section "Qt TLS plugins" tls
SetOutPath "$INSTDIR\tls"
File "/oname=qopensslbackend.dll" "tls\qopensslbackend.dll"
SectionEnd
!endif
Section "Qt SQL Drivers" sqldrivers
SetOutPath "$INSTDIR\sqldrivers"
File "/oname=qsqlite.dll" "sqldrivers\qsqlite.dll"
SectionEnd
Section "Gstreamer plugins" gstreamer-plugins
SetOutPath "$INSTDIR\gstreamer-plugins"
File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll"
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
File "/oname=libgstapp.dll" "gstreamer-plugins\libgstapp.dll"
File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll"
File "/oname=libgstasf.dll" "gstreamer-plugins\libgstasf.dll"
File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll"
File "/oname=libgstaudioconvert.dll" "gstreamer-plugins\libgstaudioconvert.dll"
File "/oname=libgstaudiofx.dll" "gstreamer-plugins\libgstaudiofx.dll"
File "/oname=libgstaudiomixer.dll" "gstreamer-plugins\libgstaudiomixer.dll"
@@ -385,43 +358,42 @@ Section "Gstreamer plugins" gstreamer-plugins
File "/oname=libgstaudioresample.dll" "gstreamer-plugins\libgstaudioresample.dll"
File "/oname=libgstaudiotestsrc.dll" "gstreamer-plugins\libgstaudiotestsrc.dll"
File "/oname=libgstautodetect.dll" "gstreamer-plugins\libgstautodetect.dll"
File "/oname=libgstplayback.dll" "gstreamer-plugins\libgstplayback.dll"
File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll"
File "/oname=libgstspectrum.dll" "gstreamer-plugins\libgstspectrum.dll"
File "/oname=libgstequalizer.dll" "gstreamer-plugins\libgstequalizer.dll"
File "/oname=libgstreplaygain.dll" "gstreamer-plugins\libgstreplaygain.dll"
File "/oname=libgsttypefindfunctions.dll" "gstreamer-plugins\libgsttypefindfunctions.dll"
File "/oname=libgstgio.dll" "gstreamer-plugins\libgstgio.dll"
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
File "/oname=libgsticydemux.dll" "gstreamer-plugins\libgsticydemux.dll"
File "/oname=libgstid3demux.dll" "gstreamer-plugins\libgstid3demux.dll"
File "/oname=libgsttaglib.dll" "gstreamer-plugins\libgsttaglib.dll"
File "/oname=libgsttcp.dll" "gstreamer-plugins\libgsttcp.dll"
File "/oname=libgstudp.dll" "gstreamer-plugins\libgstudp.dll"
File "/oname=libgstsoup.dll" "gstreamer-plugins\libgstsoup.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"
File "/oname=libgstequalizer.dll" "gstreamer-plugins\libgstequalizer.dll"
File "/oname=libgstflac.dll" "gstreamer-plugins\libgstflac.dll"
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll"
File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll"
File "/oname=libgstopus.dll" "gstreamer-plugins\libgstopus.dll"
File "/oname=libgstopusparse.dll" "gstreamer-plugins\libgstopusparse.dll"
File "/oname=libgstspeex.dll" "gstreamer-plugins\libgstspeex.dll"
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll"
File "/oname=libgstfaac.dll" "gstreamer-plugins\libgstfaac.dll"
File "/oname=libgstfaad.dll" "gstreamer-plugins\libgstfaad.dll"
File "/oname=libgstgio.dll" "gstreamer-plugins\libgstgio.dll"
File "/oname=libgsticydemux.dll" "gstreamer-plugins\libgsticydemux.dll"
File "/oname=libgstid3demux.dll" "gstreamer-plugins\libgstid3demux.dll"
File "/oname=libgstisomp4.dll" "gstreamer-plugins\libgstisomp4.dll"
File "/oname=libgstasf.dll" "gstreamer-plugins\libgstasf.dll"
File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll"
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
File "/oname=libgstlibav.dll" "gstreamer-plugins\libgstlibav.dll"
File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll"
File "/oname=libgstopenmpt.dll" "gstreamer-plugins\libgstopenmpt.dll"
File "/oname=libgstopus.dll" "gstreamer-plugins\libgstopus.dll"
File "/oname=libgstopusparse.dll" "gstreamer-plugins\libgstopusparse.dll"
File "/oname=libgstpbtypes.dll" "gstreamer-plugins\libgstpbtypes.dll"
File "/oname=libgstplayback.dll" "gstreamer-plugins\libgstplayback.dll"
File "/oname=libgstreplaygain.dll" "gstreamer-plugins\libgstreplaygain.dll"
File "/oname=libgstrtp.dll" "gstreamer-plugins\libgstrtp.dll"
File "/oname=libgstrtsp.dll" "gstreamer-plugins\libgstrtsp.dll"
File "/oname=libgstopenmpt.dll" "gstreamer-plugins\libgstopenmpt.dll"
File "/oname=libgstsoup.dll" "gstreamer-plugins\libgstsoup.dll"
File "/oname=libgstspectrum.dll" "gstreamer-plugins\libgstspectrum.dll"
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=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=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
File "/oname=libgstxingmux.dll" "gstreamer-plugins\libgstxingmux.dll"
SectionEnd
@@ -460,9 +432,9 @@ Section "Uninstall"
; Delete all the files
Delete "$INSTDIR\strawberry.ico"
Delete "$INSTDIR\strawberry.exe"
Delete "$INSTDIR\strawberry-tagreader.exe"
Delete "$INSTDIR\strawberry.ico"
Delete "$INSTDIR\sqlite3.exe"
Delete "$INSTDIR\gst-launch-1.0.exe"
Delete "$INSTDIR\gst-discoverer-1.0.exe"
@@ -489,23 +461,25 @@ Section "Uninstall"
Delete "$INSTDIR\libcdio-19.dll"
Delete "$INSTDIR\libchromaprint.dll"
Delete "$INSTDIR\libdl.dll"
Delete "$INSTDIR\libfaac-0.dll"
Delete "$INSTDIR\libfaad-2.dll"
Delete "$INSTDIR\libffi-7.dll"
Delete "$INSTDIR\libffi-8.dll"
Delete "$INSTDIR\libfftw3-3.dll"
Delete "$INSTDIR\libFLAC-8.dll"
Delete "$INSTDIR\libfreetype-6.dll"
Delete "$INSTDIR\libfaac-0.dll"
Delete "$INSTDIR\libfaad-2.dll"
Delete "$INSTDIR\libgio-2.0-0.dll"
Delete "$INSTDIR\libglib-2.0-0.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\libgstadaptivedemux-1.0-0.dll"
Delete "$INSTDIR\libgstapp-1.0-0.dll"
Delete "$INSTDIR\libgstaudio-1.0-0.dll"
Delete "$INSTDIR\libgstbadaudio-1.0-0.dll"
Delete "$INSTDIR\libgstbase-1.0-0.dll"
Delete "$INSTDIR\libgstfft-1.0-0.dll"
Delete "$INSTDIR\libgstisoff-1.0-0.dll"
Delete "$INSTDIR\libgstnet-1.0-0.dll"
Delete "$INSTDIR\libgstpbutils-1.0-0.dll"
Delete "$INSTDIR\libgstreamer-1.0-0.dll"
@@ -514,6 +488,7 @@ Section "Uninstall"
Delete "$INSTDIR\libgstrtsp-1.0-0.dll"
Delete "$INSTDIR\libgstsdp-1.0-0.dll"
Delete "$INSTDIR\libgsttag-1.0-0.dll"
Delete "$INSTDIR\libgsturidownloader-1.0-0.dll"
Delete "$INSTDIR\libgstvideo-1.0-0.dll"
Delete "$INSTDIR\libharfbuzz-0.dll"
Delete "$INSTDIR\libhogweed-6.dll"
@@ -531,15 +506,14 @@ Section "Uninstall"
Delete "$INSTDIR\libpcre-1.dll"
Delete "$INSTDIR\libpcre2-16-0.dll"
Delete "$INSTDIR\libpng16-16.dll"
Delete "$INSTDIR\libprotobuf-29.dll"
Delete "$INSTDIR\libpsl-5.dll"
Delete "$INSTDIR\libqtsparkle-qt5.dll"
Delete "$INSTDIR\libprotobuf-30.dll"
Delete "$INSTDIR\libqtsparkle-qt6.dll"
Delete "$INSTDIR\libsoup-2.4-1.dll"
Delete "$INSTDIR\libspeex-1.dll"
Delete "$INSTDIR\libsqlite3-0.dll"
Delete "$INSTDIR\libssp-0.dll"
Delete "$INSTDIR\libstdc++-6.dll"
Delete "$INSTDIR\libsoup-2.4-1.dll"
Delete "$INSTDIR\libtag.dll"
Delete "$INSTDIR\libtasn1-6.dll"
Delete "$INSTDIR\libunistring-2.dll"
@@ -551,12 +525,6 @@ Section "Uninstall"
Delete "$INSTDIR\libxml2-2.dll"
Delete "$INSTDIR\libzstd.dll"
Delete "$INSTDIR\postproc-55.dll"
Delete "$INSTDIR\Qt5Concurrent.dll"
Delete "$INSTDIR\Qt5Core.dll"
Delete "$INSTDIR\Qt5Gui.dll"
Delete "$INSTDIR\Qt5Network.dll"
Delete "$INSTDIR\Qt5Sql.dll"
Delete "$INSTDIR\Qt5Widgets.dll"
Delete "$INSTDIR\Qt6Concurrent.dll"
Delete "$INSTDIR\Qt6Core.dll"
Delete "$INSTDIR\Qt6Gui.dll"
@@ -579,64 +547,64 @@ Section "Uninstall"
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
Delete "$INSTDIR\platforms\qwindows.dll"
Delete "$INSTDIR\styles\qwindowsvistastyle.dll"
!ifdef with_qt6
Delete "$INSTDIR\tls\qopensslbackend.dll"
!endif
Delete "$INSTDIR\sqldrivers\qsqlite.dll"
Delete "$INSTDIR\imageformats\qgif.dll"
Delete "$INSTDIR\imageformats\qico.dll"
Delete "$INSTDIR\imageformats\qjpeg.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstapp.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioconvert.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiofx.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiomixer.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioparsers.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiorate.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudioresample.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaudiotestsrc.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstautodetect.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstplayback.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstspectrum.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstequalizer.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstreplaygain.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsttypefindfunctions.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstgio.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstapetag.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsticydemux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstid3demux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsttaglib.dll"
Delete "$INSTDIR\gstreamer-plugins\libgsttcp.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstudp.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstsoup.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstcdio.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstflac.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstogg.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstopus.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstopusparse.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstspeex.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstaiff.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstfaac.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstfaad.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstisomp4.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstasf.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstasfmux.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstlibav.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstpbtypes.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstrtp.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstrtsp.dll"
Delete "$INSTDIR\gstreamer-plugins\libgstopenmpt.dll"
Delete $INSTDIR\gstreamer-plugins\libgstaiff.dll"
Delete $INSTDIR\gstreamer-plugins\libgstapetag.dll"
Delete $INSTDIR\gstreamer-plugins\libgstapp.dll"
Delete $INSTDIR\gstreamer-plugins\libgstasf.dll"
Delete $INSTDIR\gstreamer-plugins\libgstasfmux.dll"
Delete $INSTDIR\gstreamer-plugins\libgstaudioconvert.dll"
Delete $INSTDIR\gstreamer-plugins\libgstaudiofx.dll"
Delete $INSTDIR\gstreamer-plugins\libgstaudiomixer.dll"
Delete $INSTDIR\gstreamer-plugins\libgstaudioparsers.dll"
Delete $INSTDIR\gstreamer-plugins\libgstaudiorate.dll"
Delete $INSTDIR\gstreamer-plugins\libgstaudioresample.dll"
Delete $INSTDIR\gstreamer-plugins\libgstaudiotestsrc.dll"
Delete $INSTDIR\gstreamer-plugins\libgstautodetect.dll"
Delete $INSTDIR\gstreamer-plugins\libgstcdio.dll"
Delete $INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
Delete $INSTDIR\gstreamer-plugins\libgstdash.dll"
Delete $INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
Delete $INSTDIR\gstreamer-plugins\libgstequalizer.dll"
Delete $INSTDIR\gstreamer-plugins\libgstflac.dll"
Delete $INSTDIR\gstreamer-plugins\libgstfaac.dll"
Delete $INSTDIR\gstreamer-plugins\libgstfaad.dll"
Delete $INSTDIR\gstreamer-plugins\libgstgio.dll"
Delete $INSTDIR\gstreamer-plugins\libgsticydemux.dll"
Delete $INSTDIR\gstreamer-plugins\libgstid3demux.dll"
Delete $INSTDIR\gstreamer-plugins\libgstisomp4.dll"
Delete $INSTDIR\gstreamer-plugins\libgstlame.dll"
Delete $INSTDIR\gstreamer-plugins\libgstlibav.dll"
Delete $INSTDIR\gstreamer-plugins\libgstogg.dll"
Delete $INSTDIR\gstreamer-plugins\libgstopenmpt.dll"
Delete $INSTDIR\gstreamer-plugins\libgstopus.dll"
Delete $INSTDIR\gstreamer-plugins\libgstopusparse.dll"
Delete $INSTDIR\gstreamer-plugins\libgstpbtypes.dll"
Delete $INSTDIR\gstreamer-plugins\libgstplayback.dll"
Delete $INSTDIR\gstreamer-plugins\libgstreplaygain.dll"
Delete $INSTDIR\gstreamer-plugins\libgstrtp.dll"
Delete $INSTDIR\gstreamer-plugins\libgstrtsp.dll"
Delete $INSTDIR\gstreamer-plugins\libgstsoup.dll"
Delete $INSTDIR\gstreamer-plugins\libgstspectrum.dll"
Delete $INSTDIR\gstreamer-plugins\libgstspeex.dll"
Delete $INSTDIR\gstreamer-plugins\libgsttaglib.dll"
Delete $INSTDIR\gstreamer-plugins\libgsttcp.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\libgstwavpack.dll"
Delete $INSTDIR\gstreamer-plugins\libgstwavparse.dll"
Delete $INSTDIR\gstreamer-plugins\libgstxingmux.dll"
Delete "$INSTDIR\Uninstall.exe"
Delete $INSTDIR\Uninstall.exe"
; Remove the installation folders.
RMDir "$INSTDIR\platforms"

View File

@@ -33,24 +33,13 @@
#include "gstfastspectrum.h"
GST_DEBUG_CATEGORY_STATIC (gst_fastspectrum_debug);
#define GST_CAT_DEFAULT gst_fastspectrum_debug
GST_DEBUG_CATEGORY_STATIC(gst_fastspectrum_debug);
/* elementfactory information */
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
# define FORMATS "{ S16LE, S24LE, S32LE, F32LE, F64LE }"
#else
# define FORMATS "{ S16BE, S24BE, S32BE, F32BE, F64BE }"
#endif
namespace {
#define ALLOWED_CAPS \
GST_AUDIO_CAPS_MAKE (FORMATS) ", " \
"layout = (string) interleaved, " \
"channels = 1"
/* Spectrum properties */
#define DEFAULT_INTERVAL (GST_SECOND / 10)
#define DEFAULT_BANDS 128
// Spectrum properties
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
constexpr auto DEFAULT_BANDS = 128;
enum {
PROP_0,
@@ -58,93 +47,97 @@ enum {
PROP_BANDS
};
} // namespace
#define gst_fastspectrum_parent_class parent_class
G_DEFINE_TYPE (GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
static void gst_fastspectrum_finalize (GObject * object);
static void gst_fastspectrum_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_fastspectrum_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
static gboolean gst_fastspectrum_start (GstBaseTransform * trans);
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans);
static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, GstBuffer *buffer);
static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInfo * info);
static void gst_fastspectrum_finalize(GObject *object);
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
static gboolean gst_fastspectrum_start(GstBaseTransform *trans);
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans);
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer);
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info);
static void gst_fastspectrum_class_init (GstFastSpectrumClass * klass) {
static void gst_fastspectrum_class_init(GstFastSpectrumClass *klass) {
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS(klass);
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
GstCaps *caps = nullptr;
gobject_class->set_property = gst_fastspectrum_set_property;
gobject_class->get_property = gst_fastspectrum_get_property;
gobject_class->finalize = gst_fastspectrum_finalize;
trans_class->start = GST_DEBUG_FUNCPTR (gst_fastspectrum_start);
trans_class->stop = GST_DEBUG_FUNCPTR (gst_fastspectrum_stop);
trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_fastspectrum_transform_ip);
trans_class->start = GST_DEBUG_FUNCPTR(gst_fastspectrum_start);
trans_class->stop = GST_DEBUG_FUNCPTR(gst_fastspectrum_stop);
trans_class->transform_ip = GST_DEBUG_FUNCPTR(gst_fastspectrum_transform_ip);
trans_class->passthrough_on_same_caps = TRUE;
filter_class->setup = GST_DEBUG_FUNCPTR (gst_fastspectrum_setup);
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
g_object_class_install_property (gobject_class, PROP_INTERVAL,
g_param_spec_uint64 ("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (gobject_class, PROP_BANDS, g_param_spec_uint ("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
GST_DEBUG_CATEGORY_INIT (gst_fastspectrum_debug, "spectrum", 0,
"audio spectrum analyser element");
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
gst_element_class_set_static_metadata (element_class, "Spectrum analyzer",
"Filter/Analyzer/Audio",
"Run an FFT on the audio signal, output spectrum data",
"Erik Walthinsen <omega@cse.ogi.edu>, "
"Stefan Kost <ensonic@users.sf.net>, "
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
gst_element_class_set_static_metadata(element_class, "Spectrum analyzer",
"Filter/Analyzer/Audio",
"Run an FFT on the audio signal, output spectrum data",
"Erik Walthinsen <omega@cse.ogi.edu>, "
"Stefan Kost <ensonic@users.sf.net>, "
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
caps = gst_caps_from_string (ALLOWED_CAPS);
gst_audio_filter_class_add_pad_templates (filter_class, caps);
gst_caps_unref (caps);
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
#else
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16BE, S24BE, S32BE, F32BE, F64BE }") ", layout = (string) interleaved, channels = 1");
#endif
gst_audio_filter_class_add_pad_templates(filter_class, caps);
gst_caps_unref(caps);
klass->fftw_lock = new QMutex;
}
static void gst_fastspectrum_init (GstFastSpectrum * spectrum) {
static void gst_fastspectrum_init(GstFastSpectrum *spectrum) {
spectrum->interval = DEFAULT_INTERVAL;
spectrum->bands = DEFAULT_BANDS;
spectrum->channel_data_initialized = false;
g_mutex_init (&spectrum->lock);
g_mutex_init(&spectrum->lock);
}
static void gst_fastspectrum_alloc_channel_data (GstFastSpectrum * spectrum) {
static void gst_fastspectrum_alloc_channel_data(GstFastSpectrum *spectrum) {
guint bands = spectrum->bands;
guint nfft = 2 * bands - 2;
spectrum->input_ring_buffer = new double[nfft];
spectrum->fft_input = reinterpret_cast<double*>( fftw_malloc(sizeof(double) * nfft));
spectrum->fft_output =reinterpret_cast<fftw_complex*>( fftw_malloc(sizeof(fftw_complex) * (nfft/2+1)));
spectrum->fft_input = reinterpret_cast<double*>(fftw_malloc(sizeof(double) * nfft));
spectrum->fft_output = reinterpret_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)));
spectrum->spect_magnitude = new double[bands]{};
spectrum->spect_magnitude = new double[bands] {};
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
{
QMutexLocker l(klass->fftw_lock);
spectrum->plan = fftw_plan_dft_r2c_1d(nfft, spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
spectrum->plan = fftw_plan_dft_r2c_1d(static_cast<int>(nfft), spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
}
spectrum->channel_data_initialized = true;
}
static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
static void gst_fastspectrum_free_channel_data(GstFastSpectrum *spectrum) {
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
if (spectrum->channel_data_initialized) {
{
QMutexLocker l(klass->fftw_lock);
@@ -160,7 +153,7 @@ static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
}
static void gst_fastspectrum_flush (GstFastSpectrum * spectrum) {
static void gst_fastspectrum_flush(GstFastSpectrum *spectrum) {
spectrum->num_frames = 0;
spectrum->num_fft = 0;
@@ -169,147 +162,145 @@ static void gst_fastspectrum_flush (GstFastSpectrum * spectrum) {
}
static void gst_fastspectrum_reset_state (GstFastSpectrum * spectrum) {
static void gst_fastspectrum_reset_state(GstFastSpectrum *spectrum) {
GST_DEBUG_OBJECT (spectrum, "resetting state");
GST_DEBUG_OBJECT(spectrum, "resetting state");
gst_fastspectrum_free_channel_data (spectrum);
gst_fastspectrum_flush (spectrum);
gst_fastspectrum_free_channel_data(spectrum);
gst_fastspectrum_flush(spectrum);
}
static void gst_fastspectrum_finalize (GObject * object) {
static void gst_fastspectrum_finalize(GObject *object) {
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(object);
gst_fastspectrum_reset_state (spectrum);
g_mutex_clear (&spectrum->lock);
gst_fastspectrum_reset_state(spectrum);
g_mutex_clear(&spectrum->lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
G_OBJECT_CLASS(parent_class)->finalize(object);
}
static void gst_fastspectrum_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) {
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
switch (prop_id) {
case PROP_INTERVAL:{
guint64 interval = g_value_get_uint64 (value);
g_mutex_lock (&filter->lock);
case PROP_INTERVAL: {
guint64 interval = g_value_get_uint64(value);
g_mutex_lock(&filter->lock);
if (filter->interval != interval) {
filter->interval = interval;
gst_fastspectrum_reset_state (filter);
gst_fastspectrum_reset_state(filter);
}
g_mutex_unlock (&filter->lock);
g_mutex_unlock(&filter->lock);
break;
}
case PROP_BANDS:{
guint bands = g_value_get_uint (value);
g_mutex_lock (&filter->lock);
case PROP_BANDS: {
guint bands = g_value_get_uint(value);
g_mutex_lock(&filter->lock);
if (filter->bands != bands) {
filter->bands = bands;
gst_fastspectrum_reset_state (filter);
gst_fastspectrum_reset_state(filter);
}
g_mutex_unlock (&filter->lock);
g_mutex_unlock(&filter->lock);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void gst_fastspectrum_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) {
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
switch (prop_id) {
case PROP_INTERVAL:
g_value_set_uint64 (value, filter->interval);
g_value_set_uint64(value, filter->interval);
break;
case PROP_BANDS:
g_value_set_uint (value, filter->bands);
g_value_set_uint(value, filter->bands);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static gboolean gst_fastspectrum_start (GstBaseTransform * trans) {
static gboolean gst_fastspectrum_start(GstBaseTransform *trans) {
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
gst_fastspectrum_reset_state (spectrum);
gst_fastspectrum_reset_state(spectrum);
return TRUE;
}
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans) {
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans) {
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
gst_fastspectrum_reset_state (spectrum);
gst_fastspectrum_reset_state(spectrum);
return TRUE;
}
/* mixing data readers */
// Mixing data readers
static void input_data_mixed_float(const guint8* _in, double* out, guint len, double max_value, guint op, guint nfft) {
static void input_data_mixed_float(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
Q_UNUSED(max_value);
guint j = 0, ip = 0;
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
guint ip = 0;
for (j = 0; j < len; j++) {
for (guint j = 0; j < len; j++) {
out[op] = in[ip++];
op = (op + 1) % nfft;
}
}
static void input_data_mixed_double (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
static void input_data_mixed_double(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
Q_UNUSED(max_value);
guint j = 0, ip = 0;
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
guint ip = 0;
for (j = 0; j < len; j++) {
for (guint j = 0; j < len; j++) {
out[op] = in[ip++];
op = (op + 1) % nfft;
}
}
static void input_data_mixed_int32_max (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
static void input_data_mixed_int32_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
guint j = 0, ip = 0;
const gint32 *in = reinterpret_cast<const gint32*>(_in);
guint ip = 0;
for (j = 0; j < len; j++) {
for (guint j = 0; j < len; j++) {
out[op] = in[ip++] / max_value;
op = (op + 1) % nfft;
}
}
static void input_data_mixed_int24_max (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
static void input_data_mixed_int24_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
guint j = 0;
for (j = 0; j < len; j++) {
for (guint j = 0; j < len; j++) {
#if G_BYTE_ORDER == G_BIG_ENDIAN
gint32 value = GST_READ_UINT24_BE (_in);
guint32 value = GST_READ_UINT24_BE(_in);
#else
gint32 value = GST_READ_UINT24_LE (_in);
guint32 value = GST_READ_UINT24_LE(_in);
#endif
if (value & 0x00800000) {
value |= 0xff000000;
@@ -322,25 +313,25 @@ static void input_data_mixed_int24_max (const guint8 * _in, double* out, guint l
}
static void input_data_mixed_int16_max (const guint8 * _in, double * out, guint len, double max_value, guint op, guint nfft) {
static void input_data_mixed_int16_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
guint j = 0, ip = 0;
const gint16 *in = reinterpret_cast<const gint16*>(_in);
guint ip = 0;
for (j = 0; j < len; j++) {
for (guint j = 0; j < len; j++) {
out[op] = in[ip++] / max_value;
op = (op + 1) % nfft;
}
}
static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInfo * info) {
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info) {
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(base);
GstFastSpectrumInputData input_data = nullptr;
g_mutex_lock (&spectrum->lock);
switch (GST_AUDIO_INFO_FORMAT (info)) {
g_mutex_lock(&spectrum->lock);
switch (GST_AUDIO_INFO_FORMAT(info)) {
case GST_AUDIO_FORMAT_S16:
input_data = input_data_mixed_int16_max;
break;
@@ -357,35 +348,33 @@ static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInf
input_data = input_data_mixed_double;
break;
default:
g_assert_not_reached ();
g_assert_not_reached();
break;
}
spectrum->input_data = input_data;
gst_fastspectrum_reset_state (spectrum);
g_mutex_unlock (&spectrum->lock);
gst_fastspectrum_reset_state(spectrum);
g_mutex_unlock(&spectrum->lock);
return TRUE;
}
static void gst_fastspectrum_run_fft (GstFastSpectrum * spectrum, guint input_pos) {
static void gst_fastspectrum_run_fft(GstFastSpectrum *spectrum, guint input_pos) {
guint i = 0;
guint bands = spectrum->bands;
guint nfft = 2 * bands - 2;
for (i = 0; i < nfft; i++) {
for (guint i = 0; i < nfft; i++) {
spectrum->fft_input[i] = spectrum->input_ring_buffer[(input_pos + i) % nfft];
}
// Should be safe to execute the same plan multiple times in parallel.
fftw_execute(spectrum->plan);
gdouble val = 0.0;
/* Calculate magnitude in db */
for (i = 0; i < bands; i++) {
val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
// Calculate magnitude in db
for (guint i = 0; i < bands; i++) {
gdouble val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
val += spectrum->fft_output[i][1] * spectrum->fft_output[i][1];
val /= nfft * nfft;
spectrum->spect_magnitude[i] += val;
@@ -393,79 +382,68 @@ static void gst_fastspectrum_run_fft (GstFastSpectrum * spectrum, guint input_po
}
static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, GstBuffer *buffer) {
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer) {
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
guint rate = GST_AUDIO_FILTER_RATE (spectrum);
guint bps = GST_AUDIO_FILTER_BPS (spectrum);
guint bpf = GST_AUDIO_FILTER_BPF (spectrum);
double max_value = (1UL << ((bps << 3) - 1)) - 1;
guint rate = GST_AUDIO_FILTER_RATE(spectrum);
guint bps = GST_AUDIO_FILTER_BPS(spectrum);
guint bpf = GST_AUDIO_FILTER_BPF(spectrum);
double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
guint bands = spectrum->bands;
guint nfft = 2 * bands - 2;
guint input_pos = 0;
GstMapInfo map;
const guint8 *data = nullptr;
gsize size = 0;
guint fft_todo = 0, msg_todo = 0, block_size = 0;
gboolean have_full_interval = false;
GstFastSpectrumInputData input_data = nullptr;
g_mutex_lock (&spectrum->lock);
gst_buffer_map (buffer, &map, GST_MAP_READ);
g_mutex_lock(&spectrum->lock);
gst_buffer_map(buffer, &map, GST_MAP_READ);
data = map.data;
size = map.size;
GST_LOG_OBJECT (spectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
GST_LOG_OBJECT(spectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
if (GST_BUFFER_IS_DISCONT (buffer)) {
GST_DEBUG_OBJECT (spectrum, "Discontinuity detected -- flushing");
gst_fastspectrum_flush (spectrum);
if (GST_BUFFER_IS_DISCONT(buffer)) {
GST_DEBUG_OBJECT(spectrum, "Discontinuity detected -- flushing");
gst_fastspectrum_flush(spectrum);
}
/* If we don't have a FFT context yet (or it was reset due to parameter
* changes) get one and allocate memory for everything
*/
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
if (!spectrum->channel_data_initialized) {
GST_DEBUG_OBJECT (spectrum, "allocating for bands %u", bands);
GST_DEBUG_OBJECT(spectrum, "allocating for bands %u", bands);
gst_fastspectrum_alloc_channel_data (spectrum);
gst_fastspectrum_alloc_channel_data(spectrum);
/* number of sample frames we process before posting a message
* interval is in ns */
spectrum->frames_per_interval = gst_util_uint64_scale (spectrum->interval, rate, GST_SECOND);
// Number of sample frames we process before posting a message interval is in ns
spectrum->frames_per_interval = gst_util_uint64_scale(spectrum->interval, rate, GST_SECOND);
spectrum->frames_todo = spectrum->frames_per_interval;
/* rounding error for frames_per_interval in ns,
* aggregated it in accumulated_error */
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
spectrum->error_per_interval = (spectrum->interval * rate) % GST_SECOND;
if (spectrum->frames_per_interval == 0) {
spectrum->frames_per_interval = 1;
}
GST_INFO_OBJECT (spectrum, "interval %" GST_TIME_FORMAT ", fpi %"
G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT,
GST_TIME_ARGS (spectrum->interval), spectrum->frames_per_interval,
GST_TIME_ARGS (spectrum->error_per_interval));
GST_INFO_OBJECT(spectrum, "interval %" GST_TIME_FORMAT ", fpi %" G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT, GST_TIME_ARGS(spectrum->interval), spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->error_per_interval));
spectrum->input_pos = 0;
gst_fastspectrum_flush (spectrum);
gst_fastspectrum_flush(spectrum);
}
if (spectrum->num_frames == 0) {
spectrum->message_ts = GST_BUFFER_TIMESTAMP (buffer);
spectrum->message_ts = GST_BUFFER_TIMESTAMP(buffer);
}
input_pos = spectrum->input_pos;
input_data = spectrum->input_data;
while (size >= bpf) {
/* run input_data for a chunk of data */
fft_todo = nfft - (spectrum->num_frames % nfft);
msg_todo = spectrum->frames_todo - spectrum->num_frames;
GST_LOG_OBJECT (spectrum,
"message frames todo: %u, fft frames todo: %u, input frames %"
G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
block_size = msg_todo;
// Run input_data for a chunk of data
guint fft_todo = nfft - (spectrum->num_frames % nfft);
guint msg_todo = spectrum->frames_todo - spectrum->num_frames;
GST_LOG_OBJECT(spectrum, "message frames todo: %u, fft frames todo: %u, input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
guint block_size = msg_todo;
if (block_size > (size / bpf)) {
block_size = (size / bpf);
}
@@ -473,7 +451,7 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
block_size = fft_todo;
}
/* Move the current frames into our ringbuffers */
// Move the current frames into our ringbuffers
input_data(data, spectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
data += block_size * bpf;
@@ -481,25 +459,19 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
input_pos = (input_pos + block_size) % nfft;
spectrum->num_frames += block_size;
have_full_interval = (spectrum->num_frames == spectrum->frames_todo);
gboolean have_full_interval = (spectrum->num_frames == spectrum->frames_todo);
GST_LOG_OBJECT (spectrum,
"size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size,
(spectrum->num_frames % nfft == 0), have_full_interval);
GST_LOG_OBJECT(spectrum, "size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size, (spectrum->num_frames % nfft == 0), have_full_interval);
/* If we have enough frames for an FFT or we have all frames required for
* the interval and we haven't run a FFT, then run an FFT */
// If we have enough frames for an FFT or we have all frames required for the interval and we haven't run a FFT, then run an FFT
if ((spectrum->num_frames % nfft == 0) || (have_full_interval && !spectrum->num_fft)) {
gst_fastspectrum_run_fft (spectrum, input_pos);
gst_fastspectrum_run_fft(spectrum, input_pos);
spectrum->num_fft++;
}
/* Do we have the FFTs for one interval? */
// Do we have the FFTs for one interval?
if (have_full_interval) {
GST_DEBUG_OBJECT (spectrum, "nfft: %u frames: %" G_GUINT64_FORMAT
" fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft,
spectrum->num_frames, spectrum->frames_per_interval,
GST_TIME_ARGS (spectrum->accumulated_error));
GST_DEBUG_OBJECT(spectrum, "nfft: %u frames: %" G_GUINT64_FORMAT " fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft, spectrum->num_frames, spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->accumulated_error));
spectrum->frames_todo = spectrum->frames_per_interval;
if (spectrum->accumulated_error >= GST_SECOND) {
@@ -511,17 +483,17 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
if (spectrum->output_callback) {
// Calculate average
for (guint i = 0; i < spectrum->bands; i++) {
spectrum->spect_magnitude[i] /= spectrum->num_fft;
spectrum->spect_magnitude[i] /= static_cast<double>(spectrum->num_fft);
}
spectrum->output_callback(spectrum->spect_magnitude, spectrum->bands);
spectrum->output_callback(spectrum->spect_magnitude, static_cast<int>(spectrum->bands));
// Reset spectrum accumulators
memset(spectrum->spect_magnitude, 0, spectrum->bands * sizeof(double));
}
if (GST_CLOCK_TIME_IS_VALID (spectrum->message_ts)) {
spectrum->message_ts += gst_util_uint64_scale (spectrum->num_frames, GST_SECOND, rate);
if (GST_CLOCK_TIME_IS_VALID(spectrum->message_ts)) {
spectrum->message_ts += gst_util_uint64_scale(spectrum->num_frames, GST_SECOND, rate);
}
spectrum->num_frames = 0;
@@ -531,10 +503,10 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
spectrum->input_pos = input_pos;
gst_buffer_unmap (buffer, &map);
g_mutex_unlock (&spectrum->lock);
gst_buffer_unmap(buffer, &map);
g_mutex_unlock(&spectrum->lock);
g_assert (size == 0);
g_assert(size == 0);
return GST_FLOW_OK;

View File

@@ -38,38 +38,37 @@
G_BEGIN_DECLS
#define GST_TYPE_FASTSPECTRUM (gst_fastspectrum_get_type())
#define GST_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FASTSPECTRUM,GstFastSpectrum))
#define GST_IS_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FASTSPECTRUM))
#define GST_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM,GstFastSpectrumClass))
#define GST_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstFastSpectrum))
#define GST_IS_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
#define GST_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstFastSpectrumClass))
#define GST_IS_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
class QMutex;
typedef void (*GstFastSpectrumInputData)(const guint8* in, double* out, guint len, double max_value, guint op, guint nfft);
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;
typedef std::function<void(double *magnitudes, int size)> OutputCallback;
struct GstFastSpectrum {
GstAudioFilter parent;
/* properties */
guint64 interval; /* how many nanoseconds between emits */
guint64 frames_per_interval; /* how many frames per interval */
// Properties
guint64 interval; // How many nanoseconds between emits
guint64 frames_per_interval; // How many frames per interval
guint64 frames_todo;
guint bands; /* number of spectrum bands */
gboolean multi_channel; /* send separate channel results */
guint bands; // Number of spectrum bands
gboolean multi_channel; // Send separate channel results
guint64 num_frames; /* frame count (1 sample per channel)
* since last emit */
guint64 num_fft; /* number of FFTs since last emit */
GstClockTime message_ts; /* starttime for next message */
guint64 num_frames; // Frame count (1 sample per channel) since last emit
guint64 num_fft; // Number of FFTs since last emit
GstClockTime message_ts; // Starttime for next message
/* <private> */
// <private>
bool channel_data_initialized;
double* input_ring_buffer;
double* fft_input;
fftw_complex* fft_output;
double* spect_magnitude;
double *input_ring_buffer;
double *fft_input;
fftw_complex *fft_output;
double *spect_magnitude;
fftw_plan plan;
guint input_pos;
@@ -87,11 +86,11 @@ struct GstFastSpectrumClass {
GstAudioFilterClass parent_class;
// Static lock for creating & destroying FFTW plans.
QMutex* fftw_lock;
QMutex *fftw_lock;
};
GType gst_fastspectrum_get_type (void);
GType gst_fastspectrum_get_type(void);
G_END_DECLS
#endif // GST_MOODBAR_FASTSPECTRUM_H
#endif // GST_MOODBAR_FASTSPECTRUM_H

View File

@@ -30,7 +30,6 @@ static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
}
return TRUE;
}
} // namespace

View File

@@ -19,7 +19,7 @@
#define GST_MOODBAR_PLUGIN_H
extern "C" {
int gstfastspectrum_register_static();
int gstfastspectrum_register_static();
}
#endif // GST_MOODBAR_PLUGIN_H

View File

@@ -52,18 +52,17 @@
namespace logging {
static Level sDefaultLevel = Level_Debug;
static QMap<QString, Level>* sClassLevels = nullptr;
static QMap<QString, Level> *sClassLevels = nullptr;
static QIODevice *sNullDevice = nullptr;
//const char* kDefaultLogLevels = "*:3";
const char *kDefaultLogLevels = "GstEnginePipeline:2,*:3";
const char *kDefaultLogLevels = "*:3";
static const char *kMessageHandlerMagic = "__logging_message__";
static const size_t kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
static QtMessageHandler sOriginalMessageHandler = nullptr;
template <class T>
static T CreateLogger(Level level, const QString& class_name, int line, const char* category);
static T CreateLogger(Level level, const QString &class_name, int line, const char *category);
void GLog(const char *domain, int level, const char *message, void*) {
@@ -94,8 +93,8 @@ class DebugBase : public QDebug {
public:
DebugBase() : QDebug(sNullDevice) {}
explicit DebugBase(QtMsgType t) : QDebug(t) {}
T& space() { return static_cast<T&>(QDebug::space()); }
T& noSpace() { return static_cast<T&>(QDebug::nospace()); }
T &space() { return static_cast<T&>(QDebug::space()); }
T &nospace() { return static_cast<T&>(QDebug::nospace()); }
};
// Debug message will be stored in a buffer.
@@ -112,7 +111,7 @@ class BufferedDebug : public DebugBase<BufferedDebug> {
// Delete function for the buffer. Since a base class is holding a reference to the raw pointer,
// it shouldn't be deleted until after the deletion of this object is complete.
static void later_deleter(QBuffer* b) { b->deleteLater(); }
static void later_deleter(QBuffer *b) { b->deleteLater(); }
std::shared_ptr<QBuffer> buf_;
};
@@ -126,8 +125,9 @@ class LoggedDebug : public DebugBase<LoggedDebug> {
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
if (strncmp(kMessageHandlerMagic, message.toLocal8Bit().data(), kMessageHandlerMagicLength) == 0) {
fprintf(stderr, "%s\n", message.toLocal8Bit().data() + kMessageHandlerMagicLength);
if (message.startsWith(kMessageHandlerMagic)) {
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message.toUtf8().data() + kMessageHandlerMagicLength);
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
return;
}
@@ -146,12 +146,13 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
break;
}
for (const QString& line : message.split('\n')) {
for (const QString &line : message.split('\n')) {
BufferedDebug d = CreateLogger<BufferedDebug>(level, "unknown", -1, nullptr);
d << line.toLocal8Bit().constData();
if (d.buf_) {
d.buf_->close();
fprintf(stderr, "%s\n", d.buf_->buffer().data());
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().data());
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
}
}
@@ -174,6 +175,7 @@ void Init() {
if (!sOriginalMessageHandler) {
sOriginalMessageHandler = qInstallMessageHandler(MessageHandler);
}
}
void SetLevels(const QString &levels) {
@@ -213,9 +215,9 @@ static QString ParsePrettyFunction(const char *pretty_function) {
// Get the class name out of the function name.
QString class_name = pretty_function;
const int paren = class_name.indexOf('(');
const qint64 paren = class_name.indexOf('(');
if (paren != -1) {
const int colons = class_name.lastIndexOf("::", paren);
const qint64 colons = class_name.lastIndexOf("::", paren);
if (colons != -1) {
class_name = class_name.left(colons);
}
@@ -224,16 +226,17 @@ static QString ParsePrettyFunction(const char *pretty_function) {
}
}
const int space = class_name.lastIndexOf(' ');
const qint64 space = class_name.lastIndexOf(' ');
if (space != -1) {
class_name = class_name.mid(space + 1);
}
return class_name;
}
template <class T>
static T CreateLogger(Level level, const QString &class_name, int line, const char* category) {
static T CreateLogger(Level level, const QString &class_name, int line, const char *category) {
// Map the level to a string
const char *level_name = nullptr;
@@ -270,11 +273,10 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
}
T ret(type);
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData()
<< level_name
<< function_line.leftJustified(32).toLatin1().constData();
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
return ret.space();
}
#ifdef Q_OS_UNIX
@@ -304,6 +306,7 @@ QString LinuxDemangle(const QString &symbol) {
}
QString mangled_function = match.captured(1);
return CXXDemangle(mangled_function);
}
#endif // Q_OS_LINUX
@@ -324,6 +327,7 @@ QString DarwinDemangle(const QString &symbol) {
QString DemangleSymbol(const QString &symbol);
QString DemangleSymbol(const QString &symbol) {
#ifdef Q_OS_MACOS
return DarwinDemangle(symbol);
#elif defined(Q_OS_LINUX)
@@ -331,9 +335,11 @@ QString DemangleSymbol(const QString &symbol) {
#else
return symbol;
#endif
}
void DumpStackTrace() {
#ifdef HAVE_BACKTRACE
void *callstack[128];
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
@@ -346,11 +352,11 @@ void DumpStackTrace() {
#else
qLog(Debug) << "FIXME: Implement printing stack traces on this platform";
#endif
}
// These are the functions that create loggers for the rest of Clementine.
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It
// doesn't override any behavior that should be needed after return.
// These are the functions that create loggers for the rest of Strawberry.
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It doesn't override any behavior that should be needed after return.
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
@@ -360,13 +366,13 @@ QDebug CreateLoggerError(int line, const char *pretty_function, const char *cate
#ifdef QT_NO_WARNING_OUTPUT
QNoDebug CreateLoggerWarning(int, const char*, const char*) { return QNoDebug(); }
#else
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Warning); }
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
#endif // QT_NO_WARNING_OUTPUT
#ifdef QT_NO_DEBUG_OUTPUT
QNoDebug CreateLoggerDebug(int, const char*, const char*) { return QNoDebug(); }
#else
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Debug); }
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
#endif // QT_NO_DEBUG_OUTPUT
} // namespace logging
@@ -384,4 +390,3 @@ QDebug operator<<(QDebug dbg, std::chrono::seconds secs) {
dbg.nospace() << print_duration(secs, "s");
return dbg.space();
}

View File

@@ -68,28 +68,27 @@ enum Level {
};
void Init();
void SetLevels(const QString& levels);
void SetLevels(const QString &levels);
void DumpStackTrace();
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char* category);
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char* category);
QDebug CreateLoggerError(int line, const char *pretty_function, const char* category);
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category);
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char *category);
QDebug CreateLoggerError(int line, const char *pretty_function, const char *category);
#ifdef QT_NO_WARNING_OUTPUT
QNoDebug CreateLoggerWarning(int, const char*, const char*);
#else
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char* category);
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category);
#endif // QT_NO_WARNING_OUTPUT
#ifdef QT_NO_DEBUG_OUTPUT
QNoDebug CreateLoggerDebug(int, const char*, const char*);
#else
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char* category);
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category);
#endif // QT_NO_DEBUG_OUTPUT
void GLog(const char* domain, int level, const char* message, void* user_data);
void GLog(const char *domain, int level, const char *message, void *user_data);
extern const char *kDefaultLogLevels;

View File

@@ -99,7 +99,7 @@ void _MessageHandlerBase::WriteMessage(const QByteArray &data) {
QDataStream s(device_);
s << quint32(data.length());
s.writeRawData(data.data(), data.length());
s.writeRawData(data.data(), static_cast<int>(data.length()));
// Sorry.
if (flush_abstract_socket_) {

View File

@@ -19,6 +19,9 @@
#ifndef WORKERPOOL_H
#define WORKERPOOL_H
#include "config.h"
#include <cstdio>
#include <cstddef>
#include <QtGlobal>
@@ -56,6 +59,8 @@ class _WorkerPoolBase : public QObject {
protected slots:
virtual void DoStart() {}
virtual void NewConnection() {}
virtual void ProcessReadyReadStandardOutput() {}
virtual void ProcessReadyReadStandardError() {}
virtual void ProcessError(QProcess::ProcessError) {}
virtual void SendQueuedMessages() {}
};
@@ -98,6 +103,8 @@ class WorkerPool : public _WorkerPoolBase {
// These are all reimplemented slots, they are called on the WorkerPool's thread.
void DoStart() override;
void NewConnection() override;
void ProcessReadyReadStandardOutput() override;
void ProcessReadyReadStandardError() override;
void ProcessError(QProcess::ProcessError error) override;
void SendQueuedMessages() override;
@@ -176,6 +183,8 @@ WorkerPool<HandlerType>::~WorkerPool() {
for (const Worker &worker : workers_) {
if (worker.local_socket_ && worker.process_) {
QObject::disconnect(worker.process_, &QProcess::errorOccurred, this, &WorkerPool::ProcessError);
QObject::disconnect(worker.process_, &QProcess::readyReadStandardOutput, this, &WorkerPool::ProcessReadyReadStandardOutput);
QObject::disconnect(worker.process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
// The worker is connected. Close his socket and wait for him to exit.
qLog(Debug) << "Closing worker socket";
@@ -276,6 +285,8 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
QObject::connect(worker->local_server_, &QLocalServer::newConnection, this, &WorkerPool::NewConnection);
QObject::connect(worker->process_, &QProcess::errorOccurred, this, &WorkerPool::ProcessError);
QObject::connect(worker->process_, &QProcess::readyReadStandardOutput, this, &WorkerPool::ProcessReadyReadStandardOutput);
QObject::connect(worker->process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
// Create a server, find an unused name and start listening
forever {
@@ -293,12 +304,12 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
qLog(Debug) << "Starting worker" << worker << executable_path_ << worker->local_server_->fullServerName();
// Start the process
#if defined(Q_OS_WIN32) && defined(QT_NO_DEBUG_OUTPUT) && !defined(ENABLE_WIN32_CONSOLE)
#ifdef Q_OS_WIN32
worker->process_->setProcessChannelMode(QProcess::SeparateChannels);
#else
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
#endif
worker->process_->start(executable_path_, QStringList() << worker->local_server_->fullServerName());
}
@@ -328,6 +339,7 @@ void WorkerPool<HandlerType>::NewConnection() {
worker->handler_ = new HandlerType(worker->local_socket_, this);
SendQueuedMessages();
}
template <typename HandlerType>
@@ -358,6 +370,32 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
}
template <typename HandlerType>
void WorkerPool<HandlerType>::ProcessReadyReadStandardOutput() {
Q_ASSERT(QThread::currentThread() == thread());
QProcess *process = qobject_cast<QProcess*>(sender());
QByteArray data = process->readAllStandardOutput();
fprintf(stdout, "%s", data.data());
fflush(stdout);
}
template <typename HandlerType>
void WorkerPool<HandlerType>::ProcessReadyReadStandardError() {
Q_ASSERT(QThread::currentThread() == thread());
QProcess *process = qobject_cast<QProcess*>(sender());
QByteArray data = process->readAllStandardError();
fprintf(stderr, "%s", data.data());
fflush(stderr);
}
template <typename HandlerType>
typename WorkerPool<HandlerType>::ReplyType*
WorkerPool<HandlerType>::NewReply(MessageType *message) {

View File

@@ -44,6 +44,9 @@ class TagReaderBase {
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0;
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;
protected:
static const std::string kEmbeddedCover;

View File

@@ -58,17 +58,20 @@ message SongMetadata {
optional string url = 21;
optional string basefilename = 22;
optional FileType filetype = 23;
optional int32 filesize = 24;
optional int64 filesize = 24;
optional int64 mtime = 25;
optional int64 ctime = 26;
optional int32 playcount = 27;
optional int32 skipcount = 28;
optional uint32 playcount = 27;
optional uint32 skipcount = 28;
optional int64 lastplayed = 29;
optional int64 lastseen = 30;
optional bool suspicious_tags = 31;
optional string art_automatic = 32;
optional string art_automatic = 31;
optional float rating = 32;
optional bool suspicious_tags = 40;
}
@@ -114,6 +117,24 @@ message SaveEmbeddedArtResponse {
optional bool success = 1;
}
message SaveSongPlaycountToFileRequest {
optional string filename = 1;
optional SongMetadata metadata = 2;
}
message SaveSongPlaycountToFileResponse {
optional bool success = 1;
}
message SaveSongRatingToFileRequest {
optional string filename = 1;
optional SongMetadata metadata = 2;
}
message SaveSongRatingToFileResponse {
optional bool success = 1;
}
message Message {
optional int32 id = 1;
@@ -132,4 +153,10 @@ message Message {
optional SaveEmbeddedArtRequest save_embedded_art_request = 10;
optional SaveEmbeddedArtResponse save_embedded_art_response = 11;
optional SaveSongPlaycountToFileRequest save_song_playcount_to_file_request = 12;
optional SaveSongPlaycountToFileResponse save_song_playcount_to_file_response = 13;
optional SaveSongRatingToFileRequest save_song_rating_to_file_request = 14;
optional SaveSongRatingToFileResponse save_song_rating_to_file_response = 15;
}

View File

@@ -35,6 +35,7 @@
#include <taglib/attachedpictureframe.h>
#include <taglib/textidentificationframe.h>
#include <taglib/unsynchronizedlyricsframe.h>
#include <taglib/popularimeterframe.h>
#include <taglib/xiphcomment.h>
#include <taglib/commentsframe.h>
#include <taglib/tag.h>
@@ -129,10 +130,11 @@ TagLib::String QStringToTaglibString(const QString &s) {
} // namespace
namespace {
// Tags containing the year the album was originally released (in contrast to other tags that contain the release year of the current edition)
const char *kMP4_OriginalYear_ID = "----:com.apple.iTunes:ORIGINAL YEAR";
const char *kASF_OriginalDate_ID = "WM/OriginalReleaseTime";
const char *kASF_OriginalYear_ID = "WM/OriginalReleaseYear";
const char *kMP4_OriginalYear_ID = "----:com.apple.iTunes:ORIGINAL YEAR";
const char *kMP4_FMPS_Playcount_ID = "----:com.apple.iTunes:FMPS_Playcount";
const char *kMP4_FMPS_Rating_ID = "----:com.apple.iTunes:FMPS_Rating";
const char *kASF_OriginalDate_ID = "WM/OriginalReleaseTime";
const char *kASF_OriginalYear_ID = "WM/OriginalReleaseYear";
} // namespace
@@ -185,18 +187,18 @@ spb::tagreader::SongMetadata_FileType TagReaderTagLib::GuessFileType(TagLib::Fil
void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
const QFileInfo info(filename);
const QFileInfo fileinfo(filename);
qLog(Debug) << "Reading tags from" << filename;
song->set_basefilename(DataCommaSizeFromQString(info.fileName()));
song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName()));
song->set_url(url.constData(), url.size());
song->set_filesize(info.size());
song->set_mtime(info.lastModified().toSecsSinceEpoch());
song->set_filesize(fileinfo.size());
song->set_mtime(fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
song->set_ctime(info.birthTime().isValid() ? info.birthTime().toSecsSinceEpoch() : info.lastModified().toSecsSinceEpoch());
song->set_ctime(fileinfo.birthTime().isValid() ? fileinfo.birthTime().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
#else
song->set_ctime(info.created().toSecsSinceEpoch());
song->set_ctime(fileinfo.created().isValid() ? fileinfo.created().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
#endif
song->set_lastseen(QDateTime::currentDateTime().toSecsSinceEpoch());
@@ -220,8 +222,8 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
Decode(tag->artist(), song->mutable_artist()); // TPE1
Decode(tag->album(), song->mutable_album());
Decode(tag->genre(), song->mutable_genre());
song->set_year(tag->year());
song->set_track(tag->track());
song->set_year(static_cast<int>(tag->year()));
song->set_track(static_cast<int>(tag->track()));
song->set_valid(true);
}
@@ -231,10 +233,16 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
// apart, so we keep specific behavior for some formats by adding another "else if" block below.
if (TagLib::Ogg::XiphComment *tag_ogg = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
ParseOggTag(tag_ogg->fieldListMap(), &disc, &compilation, song);
if (!tag_ogg->pictureList().isEmpty()) {
song->set_art_automatic(kEmbeddedCover);
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
ParseOggTag(xiph_comment->fieldListMap(), &disc, &compilation, song);
TagLib::List<TagLib::FLAC::Picture*> pictures = xiph_comment->pictureList();
if (!pictures.isEmpty()) {
for (TagLib::FLAC::Picture *picture : pictures) {
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
song->set_art_automatic(kEmbeddedCover);
break;
}
}
}
}
@@ -244,8 +252,14 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (file_flac->xiphComment()) {
ParseOggTag(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
if (!file_flac->pictureList().isEmpty()) {
song->set_art_automatic(kEmbeddedCover);
TagLib::List<TagLib::FLAC::Picture*> pictures = file_flac->pictureList();
if (!pictures.isEmpty()) {
for (TagLib::FLAC::Picture *picture : pictures) {
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
song->set_art_automatic(kEmbeddedCover);
break;
}
}
}
}
if (tag) Decode(tag->comment(), song->mutable_comment());
@@ -316,6 +330,28 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
}
}
if (TagLib::ID3v2::UserTextIdentificationFrame *frame_fmps_rating = TagLib::ID3v2::UserTextIdentificationFrame::find(file_mpeg->ID3v2Tag(), "FMPS_Rating")) {
TagLib::StringList frame_field_list = frame_fmps_rating->fieldList();
if (frame_field_list.size() > 1) {
float rating = TStringToQString(frame_field_list[1]).toFloat();
if (song->rating() <= 0 && rating > 0 && rating <= 1.0) {
song->set_rating(rating);
}
}
}
if (!map["POPM"].isEmpty()) {
const TagLib::ID3v2::PopularimeterFrame *frame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
if (frame) {
if (song->playcount() <= 0 && frame->counter() > 0) {
song->set_playcount(frame->counter());
}
if (song->rating() <= 0 && frame->rating() > 0) {
song->set_rating(ConvertPOPMRating(frame->rating()));
}
}
}
}
}
@@ -357,6 +393,26 @@ 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());
}
{
TagLib::MP4::Item item = mp4_tag->item(kMP4_FMPS_Playcount_ID);
if (item.isValid()) {
const int playcount = TStringToQString(item.toStringList().toString('\n')).toInt();
if (song->playcount() <= 0 && playcount > 0) {
song->set_playcount(static_cast<uint>(playcount));
}
}
}
{
TagLib::MP4::Item item = mp4_tag->item(kMP4_FMPS_Rating_ID);
if (item.isValid()) {
const float rating = TStringToQString(item.toStringList().toString('\n')).toFloat();
if (song->rating() <= 0 && rating > 0) {
song->set_rating(rating);
}
}
}
Decode(mp4_tag->comment(), song->mutable_comment());
}
}
@@ -367,22 +423,44 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (file_asf->tag()) {
Decode(file_asf->tag()->comment(), song->mutable_comment());
const TagLib::ASF::AttributeListMap &attributes_map = file_asf->tag()->attributeListMap();
if (attributes_map.contains(kASF_OriginalDate_ID)) {
const TagLib::ASF::AttributeList &attributes = attributes_map[kASF_OriginalDate_ID];
if (!attributes.isEmpty()) {
song->set_originalyear(TStringToQString(attributes.front().toString()).left(4).toInt());
}
}
else if (attributes_map.contains(kASF_OriginalYear_ID)) {
const TagLib::ASF::AttributeList &attributes = attributes_map[kASF_OriginalYear_ID];
if (!attributes.isEmpty()) {
song->set_originalyear(TStringToQString(attributes.front().toString()).left(4).toInt());
}
}
if (attributes_map.contains("FMPS/Playcount")) {
const TagLib::ASF::AttributeList &attributes = attributes_map["FMPS/Playcount"];
if (!attributes.isEmpty()) {
int playcount = TStringToQString(attributes.front().toString()).toInt();
if (song->playcount() <= 0 && playcount > 0) {
song->set_playcount(static_cast<uint>(playcount));
}
}
}
if (attributes_map.contains("FMPS/Rating")) {
const TagLib::ASF::AttributeList& attributes = attributes_map["FMPS/Rating"];
if (!attributes.isEmpty()) {
float rating = TStringToQString(attributes.front().toString()).toFloat();
if (song->rating() <= 0 && rating > 0) {
song->set_rating(rating);
}
}
}
}
const TagLib::ASF::AttributeListMap &attributes_map = file_asf->tag()->attributeListMap();
if (attributes_map.contains(kASF_OriginalDate_ID)) {
const TagLib::ASF::AttributeList &attributes = attributes_map[kASF_OriginalDate_ID];
if (!attributes.isEmpty()) {
song->set_originalyear(TStringToQString(attributes.front().toString()).left(4).toInt());
}
}
else if (attributes_map.contains(kASF_OriginalYear_ID)) {
const TagLib::ASF::AttributeList &attributes = attributes_map[kASF_OriginalYear_ID];
if (!attributes.isEmpty()) {
song->set_originalyear(TStringToQString(attributes.front().toString()).left(4).toInt());
}
}
}
else if (TagLib::MPC::File *file_mpc = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
@@ -397,7 +475,7 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
}
if (!disc.isEmpty()) {
const int i = disc.indexOf('/');
const qint64 i = disc.indexOf('/');
if (i != -1) {
// disc.right( i ).toInt() is total number of discs, we don't use this at the moment
song->set_disc(disc.left(i).toInt());
@@ -420,16 +498,15 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (!lyrics.isEmpty()) song->set_lyrics(lyrics.toStdString());
// Set integer fields to -1 if they're not valid
#define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); }
SetDefault(track);
SetDefault(disc);
SetDefault(year);
SetDefault(originalyear);
SetDefault(bitrate);
SetDefault(samplerate);
SetDefault(bitdepth);
SetDefault(lastplayed);
#undef SetDefault
if (song->track() <= 0) { song->set_track(-1); }
if (song->disc() <= 0) { song->set_disc(-1); }
if (song->year() <= 0) { song->set_year(-1); }
if (song->originalyear() <= 0) { song->set_originalyear(-1); }
if (song->samplerate() <= 0) { song->set_samplerate(-1); }
if (song->bitdepth() <= 0) { song->set_bitdepth(-1); }
if (song->bitrate() <= 0) { song->set_bitrate(-1); }
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
}
@@ -464,7 +541,11 @@ void TagReaderTagLib::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString
if (!map["COVERART"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
if (!map["METADATA_BLOCK_PICTURE"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat());
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) {
const int playcount = TStringToQString(map["FMPS_PLAYCOUNT"].front()).trimmed().toInt();
song->set_playcount(static_cast<uint>(playcount));
}
if (!map["FMPS_RATING"].isEmpty() && song->rating() <= 0) song->set_rating(TStringToQString(map["FMPS_RATING"].front()).trimmed().toFloat());
if (!map["LYRICS"].isEmpty()) Decode(map["LYRICS"].front(), song->mutable_lyrics());
else if (!map["UNSYNCEDLYRICS"].isEmpty()) Decode(map["UNSYNCEDLYRICS"].front(), song->mutable_lyrics());
@@ -507,9 +588,16 @@ void TagReaderTagLib::ParseAPETag(const TagLib::APE::ItemListMap &map, QString *
}
if (map.contains("FMPS_PLAYCOUNT")) {
int playcount = TStringToQString(map["FMPS_PLAYCOUNT"].toString()).toFloat();
const int playcount = TStringToQString(map["FMPS_PLAYCOUNT"].toString()).toInt();
if (song->playcount() <= 0 && playcount > 0) {
song->set_playcount(playcount);
song->set_playcount(static_cast<uint>(playcount));
}
}
if (map.contains("FMPS_RATING")) {
const float rating = TStringToQString(map["FMPS_RATING"].toString()).toFloat();
if (song->rating() <= 0 && rating > 0) {
song->set_rating(rating);
}
}
@@ -552,7 +640,8 @@ bool TagReaderTagLib::SaveFile(const QString &filename, const spb::tagreader::So
bool result = false;
if (TagLib::FLAC::File *file_flac = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment *tag = file_flac->xiphComment();
TagLib::Ogg::XiphComment *tag = file_flac->xiphComment(true);
if (!tag) return false;
SetVorbisComments(tag, song);
}
@@ -589,18 +678,19 @@ bool TagReaderTagLib::SaveFile(const QString &filename, const spb::tagreader::So
else if (TagLib::MP4::File *file_mp4 = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
TagLib::MP4::Tag *tag = file_mp4->tag();
if (!tag) return false;
tag->setItem("disk", TagLib::MP4::Item(song.disc() <= 0 -1 ? 0 : song.disc(), 0));
tag->setItem("\251wrt", TagLib::StringList(song.composer().c_str()));
tag->setItem("\251grp", TagLib::StringList(song.grouping().c_str()));
tag->setItem("\251lyr", TagLib::StringList(song.lyrics().c_str()));
tag->setItem("aART", TagLib::StringList(song.albumartist().c_str()));
tag->setItem("\251wrt", TagLib::StringList(TagLib::String(song.composer(), TagLib::String::UTF8)));
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"));
}
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
// apart, so we keep specific behavior for some formats by adding another "else if" block above.
if (TagLib::Ogg::XiphComment *tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
SetVorbisComments(tag, song);
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
SetVorbisComments(xiph_comment, song);
}
result = fileref->save();
@@ -664,6 +754,30 @@ void TagReaderTagLib::SetTextFrame(const char *id, const std::string &value, Tag
}
void TagReaderTagLib::SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const {
const QByteArray descr_utf8(description.toUtf8());
const QByteArray value_utf8(value.toUtf8());
SetUserTextFrame(std::string(descr_utf8.constData(), descr_utf8.length()), std::string(value_utf8.constData(), value_utf8.length()), tag);
}
void TagReaderTagLib::SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const {
const TagLib::String t_description = StdStringToTaglibString(description);
TagLib::ID3v2::UserTextIdentificationFrame *frame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, t_description);
if (frame) {
tag->removeFrame(frame);
}
// Create and add a new frame
frame = new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8);
frame->setDescription(t_description);
frame->setText(StdStringToTaglibString(value));
tag->addFrame(frame);
}
void TagReaderTagLib::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const {
TagLib::ByteVector id_vector("USLT");
@@ -703,86 +817,93 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const {
qLog(Debug) << "Loading art from" << filename;
#ifdef Q_OS_WIN32
TagLib::FileRef ref(filename.toStdWString().c_str());
TagLib::FileRef fileref(filename.toStdWString().c_str());
#else
TagLib::FileRef ref(QFile::encodeName(filename).constData());
TagLib::FileRef fileref(QFile::encodeName(filename).constData());
#endif
if (ref.isNull() || !ref.file()) return QByteArray();
if (fileref.isNull() || !fileref.file()) return QByteArray();
// FLAC
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(ref.file())) {
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref.file())) {
if (flac_file->xiphComment()) {
TagLib::List<TagLib::FLAC::Picture*> pics = flac_file->pictureList();
if (!pics.isEmpty()) {
TagLib::FLAC::Picture *picture = nullptr;
for (std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin() ; it != pics.end() ; ++it) {
picture = *it;
if (picture->type() == TagLib::FLAC::Picture::FrontCover) {
break;
TagLib::List<TagLib::FLAC::Picture*> pictures = flac_file->pictureList();
if (!pictures.isEmpty()) {
for (TagLib::FLAC::Picture *picture : pictures) {
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
QByteArray data(picture->data().data(), picture->data().size());
if (!data.isEmpty()) {
return data;
}
}
}
if (picture) return QByteArray(picture->data().data(), picture->data().size());
}
}
}
// WavPack
if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(ref.file())) {
return LoadEmbeddedAPEArt(wavpack_file->APETag()->itemListMap());
if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(fileref.file())) {
if (wavpack_file->APETag()) {
return LoadEmbeddedAPEArt(wavpack_file->APETag()->itemListMap());
}
}
// APE
if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(ref.file())) {
return LoadEmbeddedAPEArt(ape_file->APETag()->itemListMap());
if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(fileref.file())) {
if (ape_file->APETag()) {
return LoadEmbeddedAPEArt(ape_file->APETag()->itemListMap());
}
}
// MPC
if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(ref.file())) {
return LoadEmbeddedAPEArt(mpc_file->APETag()->itemListMap());
if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(fileref.file())) {
if (mpc_file->APETag()) {
return LoadEmbeddedAPEArt(mpc_file->APETag()->itemListMap());
}
}
// Ogg Vorbis / Speex
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(ref.file()->tag())) {
// Ogg Vorbis / Opus / Speex
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref.file()->tag())) {
TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap();
TagLib::List<TagLib::FLAC::Picture*> pics = xiph_comment->pictureList();
if (!pics.isEmpty()) {
for (auto p : pics) {
if (p->type() == TagLib::FLAC::Picture::FrontCover)
return QByteArray(p->data().data(), p->data().size());
TagLib::List<TagLib::FLAC::Picture*> pictures = xiph_comment->pictureList();
if (!pictures.isEmpty()) {
for (TagLib::FLAC::Picture *picture : pictures) {
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
QByteArray data(picture->data().data(), picture->data().size());
if (!data.isEmpty()) {
return data;
}
}
}
// If there was no specific front cover, just take the first picture
std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin();
TagLib::FLAC::Picture *picture = *it;
return QByteArray(picture->data().data(), picture->data().size());
}
// Ogg lacks a definitive standard for embedding cover art, but it seems b64 encoding a field called COVERART is the general convention
if (map.contains("COVERART"))
if (map.contains("COVERART")) {
return QByteArray::fromBase64(map["COVERART"].toString().toCString());
}
return QByteArray();
}
// MP3
if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(ref.file())) {
if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(fileref.file())) {
if (file_mp3->ID3v2Tag()) {
TagLib::ID3v2::FrameList apic_frames = file_mp3->ID3v2Tag()->frameListMap()["APIC"];
if (apic_frames.isEmpty())
if (apic_frames.isEmpty()) {
return QByteArray();
}
TagLib::ID3v2::AttachedPictureFrame *pic = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front());
TagLib::ID3v2::AttachedPictureFrame *picture = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front());
return QByteArray(reinterpret_cast<const char*>(pic->picture().data()), pic->picture().size());
return QByteArray(reinterpret_cast<const char*>(picture->picture().data()), picture->picture().size());
}
}
// MP4/AAC
if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(ref.file())) {
if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(fileref.file())) {
TagLib::MP4::Tag *tag = aac_file->tag();
if (tag->item("covr").isValid()) {
if (tag && tag->item("covr").isValid()) {
const TagLib::MP4::CoverArtList &art_list = tag->item("covr").toCoverArtList();
if (!art_list.isEmpty()) {
@@ -822,78 +943,292 @@ bool TagReaderTagLib::SaveEmbeddedArt(const QString &filename, const QByteArray
qLog(Debug) << "Saving art to" << filename;
#ifdef Q_OS_WIN32
TagLib::FileRef ref(filename.toStdWString().c_str());
TagLib::FileRef fileref(filename.toStdWString().c_str());
#else
TagLib::FileRef ref(QFile::encodeName(filename).constData());
TagLib::FileRef fileref(QFile::encodeName(filename).constData());
#endif
if (ref.isNull() || !ref.file()) return false;
if (fileref.isNull() || !fileref.file()) return false;
// FLAC
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(ref.file())) {
if (flac_file->xiphComment()) {
flac_file->removePictures();
if (!data.isEmpty()) {
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
picture->setType(TagLib::FLAC::Picture::FrontCover);
picture->setMimeType("image/jpeg");
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
flac_file->addPicture(picture);
}
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref.file())) {
TagLib::Ogg::XiphComment *vorbis_comments = flac_file->xiphComment(true);
if (!vorbis_comments) return false;
flac_file->removePictures();
if (!data.isEmpty()) {
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
picture->setType(TagLib::FLAC::Picture::FrontCover);
picture->setMimeType("image/jpeg");
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
flac_file->addPicture(picture);
}
}
// Ogg Vorbis / Speex
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(ref.file()->tag())) {
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
picture->setType(TagLib::FLAC::Picture::FrontCover);
picture->setMimeType("image/jpeg");
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
xiph_comment->addPicture(picture);
// Ogg Vorbis / Opus / Speex
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref.file()->tag())) {
xiph_comment->removeAllPictures();
if (!data.isEmpty()) {
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
picture->setType(TagLib::FLAC::Picture::FrontCover);
picture->setMimeType("image/jpeg");
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
xiph_comment->addPicture(picture);
}
}
// MP3
else if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(ref.file())) {
if (file_mp3->ID3v2Tag()) {
TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag();
else if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(fileref.file())) {
TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag();
if (!tag) return false;
// Remove existing covers
TagLib::ID3v2::FrameList apiclist = tag->frameListMap()["APIC"];
for (TagLib::ID3v2::FrameList::ConstIterator it = apiclist.begin() ; it != apiclist.end() ; ++it ) {
TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
tag->removeFrame(frame, false);
}
// Remove existing covers
TagLib::ID3v2::FrameList apiclist = tag->frameListMap()["APIC"];
for (TagLib::ID3v2::FrameList::ConstIterator it = apiclist.begin() ; it != apiclist.end() ; ++it ) {
TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
tag->removeFrame(frame, false);
}
if (!data.isEmpty()) {
// Add new cover
TagLib::ID3v2::AttachedPictureFrame *frontcover = nullptr;
frontcover = new TagLib::ID3v2::AttachedPictureFrame("APIC");
frontcover->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
frontcover->setMimeType("image/jpeg");
frontcover->setPicture(TagLib::ByteVector(data.constData(), data.count()));
tag->addFrame(frontcover);
}
if (!data.isEmpty()) {
// Add new cover
TagLib::ID3v2::AttachedPictureFrame *frontcover = nullptr;
frontcover = new TagLib::ID3v2::AttachedPictureFrame("APIC");
frontcover->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
frontcover->setMimeType("image/jpeg");
frontcover->setPicture(TagLib::ByteVector(data.constData(), data.count()));
tag->addFrame(frontcover);
}
}
// MP4/AAC
else if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(ref.file())) {
else if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(fileref.file())) {
TagLib::MP4::Tag *tag = aac_file->tag();
if (!tag) return false;
TagLib::MP4::CoverArtList covers;
if (tag) {
if (data.isEmpty()) {
if (tag->contains("covr")) tag->removeItem("covr");
}
else {
covers.append(TagLib::MP4::CoverArt(TagLib::MP4::CoverArt::JPEG, TagLib::ByteVector(data.constData(), data.count())));
tag->setItem("covr", covers);
}
if (data.isEmpty()) {
if (tag->contains("covr")) tag->removeItem("covr");
}
else {
covers.append(TagLib::MP4::CoverArt(TagLib::MP4::CoverArt::JPEG, TagLib::ByteVector(data.constData(), data.count())));
tag->setItem("covr", covers);
}
}
// Not supported.
else return false;
return ref.file()->save();
return fileref.file()->save();
}
TagLib::ID3v2::PopularimeterFrame *TagReaderTagLib::GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag) {
TagLib::ID3v2::PopularimeterFrame *frame = nullptr;
const TagLib::ID3v2::FrameListMap &map = tag->frameListMap();
if (!map["POPM"].isEmpty()) {
frame = dynamic_cast<TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
}
if (!frame) {
frame = new TagLib::ID3v2::PopularimeterFrame();
tag->addFrame(frame);
}
return frame;
}
float TagReaderTagLib::ConvertPOPMRating(const int POPM_rating) {
if (POPM_rating < 0x01) return 0.0;
else if (POPM_rating < 0x40) return 0.20;
else if (POPM_rating < 0x80) return 0.40;
else if (POPM_rating < 0xC0) return 0.60;
else if (POPM_rating < 0xFC) return 0.80;
return 1.0;
}
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;
qLog(Debug) << "Saving song playcount to" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) return false;
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment *vorbis_comments = flac_file->xiphComment(true);
if (!vorbis_comments) return false;
if (song.playcount() > 0) {
vorbis_comments->addField("FMPS_PLAYCOUNT", TagLib::String::number(static_cast<int>(song.playcount())), true);
}
else {
vorbis_comments->removeFields("FMPS_PLAYCOUNT");
}
}
else if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(fileref->file())) {
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()))));
}
}
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()))));
}
}
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
if (song.playcount() > 0) {
xiph_comment->addField("FMPS_PLAYCOUNT", TagLib::String::number(static_cast<int>(song.playcount())), true);
}
else {
xiph_comment->removeFields("FMPS_PLAYCOUNT");
}
}
else if (TagLib::MPEG::File *mpeg_file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true);
if (!tag) return false;
if (song.playcount() > 0) {
SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag);
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
if (frame) {
frame->setCounter(song.playcount());
}
}
}
else if (TagLib::MP4::File *mp4_file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
TagLib::MP4::Tag *tag = mp4_file->tag();
if (!tag) return false;
if (song.playcount() > 0) {
tag->setItem(kMP4_FMPS_Playcount_ID, TagLib::MP4::Item(TagLib::String::number(static_cast<int>(song.playcount()))));
}
}
else if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
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()))));
}
}
else if (TagLib::ASF::File *asf_file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
TagLib::ASF::Tag *tag = asf_file->tag();
if (!tag) return false;
if (song.playcount() > 0) {
tag->addAttribute("FMPS/Playcount", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.playcount()))));
}
}
else {
return true;
}
bool ret = fileref->save();
#ifdef Q_OS_LINUX
if (ret) {
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
}
#endif // Q_OS_LINUX
return ret;
}
bool TagReaderTagLib::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
if (filename.isNull()) return false;
qLog(Debug) << "Saving song rating to" << filename;
if (song.rating() < 0) {
return true;
}
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) return false;
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment *vorbis_comments = flac_file->xiphComment(true);
if (!vorbis_comments) return false;
if (song.rating() > 0) {
vorbis_comments->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())), true);
}
else {
vorbis_comments->removeFields("FMPS_RATING");
}
}
else if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(fileref->file())) {
TagLib::APE::Tag *tag = wavpack_file->APETag(true);
if (!tag) return false;
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
}
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;
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
}
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
if (song.rating() > 0) {
xiph_comment->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())), true);
}
else {
xiph_comment->removeFields("FMPS_RATING");
}
}
else if (TagLib::MPEG::File *mpeg_file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true);
if (!tag) return false;
SetUserTextFrame("FMPS_Rating", QString::number(song.rating()), tag);
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
if (frame) {
frame->setRating(ConvertToPOPMRating(song.rating()));
}
}
else if (TagLib::MP4::File *mp4_file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
TagLib::MP4::Tag *tag = mp4_file->tag();
if (!tag) return false;
tag->setItem(kMP4_FMPS_Rating_ID, TagLib::StringList(QStringToTaglibString(QString::number(song.rating()))));
}
else if (TagLib::ASF::File *asf_file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
TagLib::ASF::Tag *tag = asf_file->tag();
if (!tag) return false;
tag->addAttribute("FMPS/Rating", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.rating()))));
}
else if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
TagLib::APE::Tag *tag = mpc_file->APETag(true);
if (!tag) return false;
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
}
else {
return true;
}
bool ret = fileref->save();
#ifdef Q_OS_LINUX
if (ret) {
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
}
#endif // Q_OS_LINUX
return ret;
}

View File

@@ -32,6 +32,7 @@
#include <taglib/apetag.h>
#include <taglib/apefile.h>
#include <taglib/id3v2tag.h>
#include <taglib/popularimeterframe.h>
#include "tagreaderbase.h"
#include "tagreadermessages.pb.h"
@@ -55,6 +56,9 @@ class TagReaderTagLib : public TagReaderBase {
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;
private:
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
@@ -69,10 +73,16 @@ class TagReaderTagLib : public TagReaderBase {
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const;
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
void SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const;
void SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const;
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:
FileRefFactory *factory_;

View File

@@ -106,11 +106,11 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName()));
song->set_url(url.constData(), url.size());
song->set_filesize(fileinfo.size());
song->set_mtime(fileinfo.lastModified().toSecsSinceEpoch());
song->set_mtime(fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
song->set_ctime(fileinfo.birthTime().isValid() ? fileinfo.birthTime().toSecsSinceEpoch() : fileinfo.lastModified().toSecsSinceEpoch());
song->set_ctime(fileinfo.birthTime().isValid() ? fileinfo.birthTime().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
#else
song->set_ctime(fileinfo.created().toSecsSinceEpoch());
song->set_ctime(fileinfo.created().isValid() ? fileinfo.created().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
#endif
song->set_lastseen(QDateTime::currentDateTime().toSecsSinceEpoch());
@@ -225,20 +225,19 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
}
// Set integer fields to -1 if they're not valid
#define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); }
SetDefault(track);
SetDefault(disc);
SetDefault(year);
SetDefault(originalyear);
SetDefault(bitrate);
SetDefault(samplerate);
SetDefault(bitdepth);
SetDefault(lastplayed);
#undef SetDefault
if (song->track() <= 0) { song->set_track(-1); }
if (song->disc() <= 0) { song->set_disc(-1); }
if (song->year() <= 0) { song->set_year(-1); }
if (song->originalyear() <= 0) { song->set_originalyear(-1); }
if (song->samplerate() <= 0) { song->set_samplerate(-1); }
if (song->bitdepth() <= 0) { song->set_bitdepth(-1); }
if (song->bitrate() <= 0) { song->set_bitrate(-1); }
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
song->set_valid(true);
taginfo.close();
}
catch(...) {}
@@ -422,3 +421,62 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
return false;
}
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; }
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
if (filename.isEmpty()) return false;
qLog(Debug) << "Saving song rating to" << filename;
try {
TagParser::MediaFileInfo taginfo;
TagParser::Diagnostics diag;
TagParser::AbortableProgressFeedback progress;
#ifdef Q_OS_WIN32
taginfo.setPath(filename.toStdWString().toStdString());
#else
taginfo.setPath(QFile::encodeName(filename).toStdString());
#endif
taginfo.open(false);
taginfo.parseContainerFormat(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return false;
}
taginfo.parseTracks(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return false;
}
taginfo.parseTags(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return false;
}
if (taginfo.tags().size() <= 0) {
taginfo.createAppropriateTags();
}
for (const auto tag : taginfo.tags()) {
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(song.rating()));
}
taginfo.applyChanges(diag, progress);
taginfo.close();
for (const TagParser::DiagMessage &msg : diag) {
qLog(Debug) << QString::fromStdString(msg.message());
}
return true;
}
catch(...) {}
return false;
}

View File

@@ -45,6 +45,9 @@ class TagReaderTagParser : public TagReaderBase {
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;
Q_DISABLE_COPY(TagReaderTagParser)
};

View File

@@ -34,22 +34,28 @@ void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
spb::tagreader::Message reply;
if (message.has_read_file_request()) {
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_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_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(), message.save_embedded_art_request().data().size())));
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()));
}
SendReply(message, &reply);

View File

@@ -635,34 +635,6 @@ if(UNIX AND HAVE_DBUS)
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)
# org.freedesktop.Avahi.Server interface
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
dbus/org.freedesktop.Avahi.Server.xml
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver
-i dbus/metatypes.h
DEPENDS dbus/org.freedesktop.Avahi.Server.xml
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp)
# org.freedesktop.Avahi.EntryGroup interface
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
dbus/org.freedesktop.Avahi.EntryGroup.xml
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup
-i dbus/metatypes.h
DEPENDS dbus/org.freedesktop.Avahi.EntryGroup.xml
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp)
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)

View File

@@ -180,7 +180,7 @@ void Analyzer::Base::demo(QPainter &p) {
const double dt = static_cast<double>(t) / 200;
for (uint i = 0; i < s.size(); ++i) {
s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0);
s[i] = static_cast<float>(dt * (sin(M_PI + (i * M_PI) / static_cast<double>(s.size())) + 1.0));
}
analyze(p, s, new_frame_);
@@ -200,7 +200,7 @@ void Analyzer::Base::polishEvent() {
void Analyzer::interpolate(const Scope &inVec, Scope &outVec) {
double pos = 0.0;
const double step = static_cast<double>(inVec.size()) / outVec.size();
const double step = static_cast<double>(inVec.size()) / static_cast<double>(outVec.size());
for (uint i = 0; i < outVec.size(); ++i, pos += step) {
const double error = pos - std::floor(pos);
@@ -218,7 +218,7 @@ void Analyzer::interpolate(const Scope &inVec, Scope &outVec) {
indexRight = inVec.size() - 1;
}
outVec[i] = inVec[indexLeft] * (1.0 - error) + inVec[indexRight] * error;
outVec[i] = inVec[indexLeft] * (1.0F - static_cast<float>(error)) + inVec[indexRight] * static_cast<float>(error);
}
}
@@ -229,7 +229,7 @@ void Analyzer::initSin(Scope &v, const uint size) {
double radian = 0;
for (uint i = 0; i < size; i++) {
v.push_back(sin(radian));
v.push_back(static_cast<float>(sin(radian)));
radian += step;
}

View File

@@ -75,7 +75,7 @@ void BoomAnalyzer::resizeEvent(QResizeEvent *e) {
QWidget::resizeEvent(e);
const uint HEIGHT = height() - 2;
const int HEIGHT = height() - 2;
const double h = 1.2 / HEIGHT;
bands_ = qMin(static_cast<int>(static_cast<double>(width() + 1) / (kColumnWidth + 1)) + 1, kMaxBandCount);
@@ -88,7 +88,7 @@ void BoomAnalyzer::resizeEvent(QResizeEvent *e) {
canvas_.fill(palette().color(QPalette::Window));
QPainter p(&barPixmap_);
for (uint y = 0; y < HEIGHT; ++y) {
for (int y = 0; y < HEIGHT; ++y) {
const double F = static_cast<double>(y) * h;
p.setPen(QColor(qMax(0, 255 - static_cast<int>(229.0 * F)),

View File

@@ -53,8 +53,8 @@ void FHT::makeCasTable(void) {
float *sintab = tab_() + num_ / 2 + 1;
for (int ul = 0; ul < num_; ul++) {
float d = M_PI * ul / (num_ / 2); // NOLINT(bugprone-integer-division)
*costab = *sintab = cos(d);
double d = M_PI * static_cast<double>(ul) / (static_cast<double>(num_) / 2.0);
*costab = *sintab = static_cast<float>(cos(d));
costab += 2;
sintab += 2;
@@ -73,25 +73,25 @@ void FHT::ewma(float *d, float *s, float w) const {
void FHT::logSpectrum(float *out, float *p) {
int n = num_ / 2, i = 0, j = 0, k = 0, *r = nullptr;
int n = num_ / 2, i = 0, k = 0, *r = nullptr;
if (log_vector_.size() < n) {
log_vector_.resize(n);
float f = n / log10(static_cast<double>(n));
float f = static_cast<float>(n) / static_cast<float>(log10(static_cast<double>(n)));
for (i = 0, r = log_(); i < n; i++, r++) {
j = static_cast<int>(rint(log10(i + 1.0) * f));
int j = static_cast<int>(rint(log10(i + 1.0) * f));
*r = j >= n ? n - 1 : j;
}
}
semiLogSpectrum(p);
*out++ = *p = *p / 100;
for (k = i = 1, r = log_(); i < n; i++) {
j = *r++;
int j = *r++;
if (i == j) {
*out++ = p[i];
}
else {
float base = p[k - 1];
float step = (p[j] - base) / (j - (k - 1));
float step = (p[j] - base) / static_cast<float>(j - (k - 1));
for (float corr = 0; k <= j; k++, corr += step) *out++ = base + corr;
}
}
@@ -102,7 +102,7 @@ void FHT::semiLogSpectrum(float *p) {
power2(p);
for (int i = 0; i < (num_ / 2); i++, p++) {
float e = 10.0 * log10(sqrt(*p / 2));
float e = 10.0F * static_cast<float>(log10(sqrt(*p / static_cast<float>(2))));
*p = e < 0 ? 0 : e;
}
@@ -158,8 +158,8 @@ void FHT::transform8(float *p) {
a = *p++, b = *p++, c = *p++, d = *p++;
e = *p++, f = *p++, g = *p++, h = *p;
b_f2 = (b - f) * M_SQRT2;
d_h2 = (d - h) * M_SQRT2;
b_f2 = (b - f) * static_cast<float>(M_SQRT2);
d_h2 = (d - h) * static_cast<float>(M_SQRT2);
a_c_eg = a - c - e + g;
a_ce_g = a - c + e - g;

View File

@@ -76,7 +76,7 @@ Rainbow::RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *par
// pow constants computed so that
// | band_scale(0) | ~= .5 and | band_scale(5) | ~= 32
band_scale_[i] = -std::cos(M_PI * i / (kRainbowBands - 1)) * 0.5 * std::pow(2.3, i);
band_scale_[i] = -static_cast<float>(std::cos(M_PI * i / (kRainbowBands - 1))) * 0.5F * static_cast<float>(std::pow(2.3, i));
}
}
@@ -103,7 +103,7 @@ void Rainbow::RainbowAnalyzer::resizeEvent(QResizeEvent *e) {
buffer_[1] = QPixmap();
available_rainbow_width_ = width() - kWidth[rainbowtype] + kRainbowOverlap[rainbowtype];
px_per_frame_ = static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
px_per_frame_ = available_rainbow_width_ / (kHistorySize - 1) + 1;
x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_;
}
@@ -144,7 +144,7 @@ void Rainbow::RainbowAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bo
const float top_of = static_cast<float>(height()) / 2 - static_cast<float>(kRainbowHeight[rainbowtype]) / 2;
for (int band = 0; band < kRainbowBands; ++band) {
// Calculate the Y position of this band.
const float y = static_cast<float>(kRainbowHeight[rainbowtype]) / (kRainbowBands + 1) * (band + 0.5) + top_of;
const float y = static_cast<float>(kRainbowHeight[rainbowtype]) / static_cast<float>(kRainbowBands + 1) * (static_cast<float>(band) + 0.5F) + top_of;
// Add each point in the line.
for (int x = 0; x < kHistorySize; ++x) {

View File

@@ -25,9 +25,12 @@
#include <QObject>
#include <QThread>
#include <QList>
#include <QSettings>
#include <QtConcurrentRun>
#include <QtDebug>
#include "core/application.h"
#include "core/taskmanager.h"
#include "core/database.h"
#include "core/player.h"
#include "core/tagreaderclient.h"
@@ -41,6 +44,7 @@
#include "collectionmodel.h"
#include "playlist/playlistmanager.h"
#include "scrobbler/lastfmimport.h"
#include "settings/collectionsettingspage.h"
const char *SCollection::kSongsTable = "songs";
const char *SCollection::kFtsTable = "songs_fts";
@@ -54,7 +58,9 @@ SCollection::SCollection(Application *app, QObject *parent)
model_(nullptr),
watcher_(nullptr),
watcher_thread_(nullptr),
original_thread_(nullptr) {
original_thread_(nullptr),
save_playcounts_to_files_(false),
save_ratings_to_files_(false) {
original_thread_ = thread();
@@ -100,6 +106,9 @@ void SCollection::Init() {
QObject::connect(backend_, &CollectionBackend::Error, this, &SCollection::Error);
QObject::connect(backend_, &CollectionBackend::DirectoryDiscovered, watcher_, &CollectionWatcher::AddDirectory);
QObject::connect(backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
QObject::connect(backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged);
QObject::connect(backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged);
QObject::connect(watcher_, &CollectionWatcher::NewOrUpdatedSongs, backend_, &CollectionBackend::AddOrUpdateSongs);
QObject::connect(watcher_, &CollectionWatcher::SongsMTimeUpdated, backend_, &CollectionBackend::UpdateMTimesOnly);
QObject::connect(watcher_, &CollectionWatcher::SongsDeleted, backend_, &CollectionBackend::DeleteSongs);
@@ -128,6 +137,7 @@ void SCollection::Exit() {
QObject::connect(backend_, &CollectionBackend::ExitFinished, this, &SCollection::ExitReceived);
QObject::connect(watcher_, &CollectionWatcher::ExitFinished, this, &SCollection::ExitReceived);
backend_->ExitAsync();
watcher_->Abort();
watcher_->ExitAsync();
}
@@ -164,4 +174,53 @@ void SCollection::ReloadSettings() {
watcher_->ReloadSettingsAsync();
model_->ReloadSettings();
QSettings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
s.endGroup();
}
void SCollection::SyncPlaycountAndRatingToFilesAsync() {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
(void)QtConcurrent::run(&SCollection::SyncPlaycountAndRatingToFiles, this);
#else
(void)QtConcurrent::run(this, &SCollection::SyncPlaycountAndRatingToFiles);
#endif
}
void SCollection::SyncPlaycountAndRatingToFiles() {
const int task_id = app_->task_manager()->StartTask(tr("Saving playcounts and ratings"));
app_->task_manager()->SetTaskBlocksCollectionScans(task_id);
const SongList songs = backend_->GetAllSongs();
const qint64 nb_songs = songs.size();
int i = 0;
for (const Song &song : songs) {
TagReaderClient::Instance()->UpdateSongPlaycountBlocking(song);
TagReaderClient::Instance()->UpdateSongRatingBlocking(song);
app_->task_manager()->SetTaskProgress(task_id, ++i, nb_songs);
}
app_->task_manager()->SetTaskFinished(task_id);
}
void SCollection::SongsPlaycountChanged(const SongList &songs) {
if (save_playcounts_to_files_) {
app_->tag_reader_client()->UpdateSongsPlaycount(songs);
}
}
void SCollection::SongsRatingChanged(const SongList &songs, const bool save_tags) {
if (save_tags || save_ratings_to_files_) {
app_->tag_reader_client()->UpdateSongsRating(songs);
}
}

View File

@@ -59,9 +59,10 @@ class SCollection : public QObject {
QString full_rescan_reason(int schema_version) const { return full_rescan_revisions_.value(schema_version, QString()); }
int Total_Albums = 0;
int total_songs_ = 0;
int Total_Artists = 0;
void SyncPlaycountAndRatingToFilesAsync();
private:
void SyncPlaycountAndRatingToFiles();
public slots:
void ReloadSettings();
@@ -77,6 +78,8 @@ class SCollection : public QObject {
private slots:
void ExitReceived();
void SongsPlaycountChanged(const SongList &songs);
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
signals:
void Error(QString);
@@ -95,6 +98,9 @@ class SCollection : public QObject {
QHash<int, QString> full_rescan_revisions_;
QList<QObject*> wait_for_exit_;
bool save_playcounts_to_files_;
bool save_ratings_to_files_;
};
#endif

View File

@@ -177,7 +177,7 @@ void CollectionBackend::ChangeDirPath(const int id, const QString &old_path, con
const QByteArray old_url = QUrl::fromLocalFile(old_path).toEncoded();
const QByteArray new_url = QUrl::fromLocalFile(new_path).toEncoded();
const int path_len = old_url.length();
const qint64 path_len = old_url.length();
// Do the subdirs table
{
@@ -509,6 +509,28 @@ void CollectionBackend::AddOrUpdateSubdirs(const SubdirectoryList &subdirs) {
}
SongList CollectionBackend::GetAllSongs() {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
SqlQuery q(db);
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1").arg(songs_table_));
if (!q.Exec()) {
db_->ReportErrors(q);
return SongList();
}
SongList songs;
while (q.next()) {
Song song;
song.InitFromQuery(q, true);
songs << song;
}
return songs;
}
void CollectionBackend::AddOrUpdateSongsAsync(const SongList &songs) {
QMetaObject::invokeMethod(this, "AddOrUpdateSongs", Qt::QueuedConnection, Q_ARG(SongList, songs));
}
@@ -687,7 +709,8 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
}
// Add or update songs.
for (const Song &new_song : new_songs) {
QList new_songs_list = new_songs.values();
for (const Song &new_song : new_songs_list) {
if (old_songs.contains(new_song.song_id())) {
Song old_song = old_songs[new_song.song_id()];
@@ -757,7 +780,8 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
}
// Delete songs
for (const Song &old_song : old_songs) {
QList old_songs_list = old_songs.values();
for (const Song &old_song : old_songs_list) {
if (!new_songs.contains(old_song.song_id())) {
{
SqlQuery q(db);
@@ -1087,7 +1111,7 @@ SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QS
QVector<Song> ret(ids.count());
while (q.next()) {
const QString foreign_id = q.value(static_cast<int>(Song::kColumns.count()) + 1).toString();
const int index = ids.indexOf(foreign_id);
const qint64 index = ids.indexOf(foreign_id);
if (index == -1) continue;
ret[index].InitFromQuery(q, true);
@@ -1550,7 +1574,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
}
// Update the songs
QString sql(QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_));
QString sql = QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_);
if (clear_art_automatic) {
sql += ", art_automatic = ''";
}
@@ -1587,13 +1611,13 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
}
void CollectionBackend::UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) {
void CollectionBackend::UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
QMetaObject::invokeMethod(this, "UpdateAutomaticAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url));
QMetaObject::invokeMethod(this, "UpdateAutomaticAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url), Q_ARG(bool, clear_art_manual));
}
void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) {
void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
@@ -1617,7 +1641,11 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
}
// Update the songs
QString sql(QString("UPDATE %1 SET art_automatic = :cover WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
QString sql = QString("UPDATE %1 SET art_automatic = :cover").arg(songs_table_);
if (clear_art_manual) {
sql += ", art_manual = ''";
}
sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
SqlQuery q(db);
q.prepare(sql);
@@ -1924,17 +1952,15 @@ void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &ti
}
void CollectionBackend::UpdateSongRating(const int id, const double rating) {
void CollectionBackend::UpdateSongRating(const int id, const float rating, const bool save_tags) {
if (id == -1) return;
QList<int> id_list;
id_list << id;
UpdateSongsRating(id_list, rating);
UpdateSongsRating(QList<int>() << id, rating, save_tags);
}
void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const double rating) {
void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const float rating, const bool save_tags) {
if (id_list.isEmpty()) return;
@@ -1957,16 +1983,16 @@ void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const doubl
SongList new_song_list = GetSongsById(id_str_list, db);
emit SongsRatingChanged(new_song_list);
emit SongsRatingChanged(new_song_list, save_tags);
}
void CollectionBackend::UpdateSongRatingAsync(const int id, const double rating) {
QMetaObject::invokeMethod(this, "UpdateSongRating", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(double, rating));
void CollectionBackend::UpdateSongRatingAsync(const int id, const float rating, const bool save_tags) {
QMetaObject::invokeMethod(this, "UpdateSongRating", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, rating), Q_ARG(bool, save_tags));
}
void CollectionBackend::UpdateSongsRatingAsync(const QList<int> &ids, const double rating) {
QMetaObject::invokeMethod(this, "UpdateSongsRating", Qt::QueuedConnection, Q_ARG(QList<int>, ids), Q_ARG(double, rating));
void CollectionBackend::UpdateSongsRatingAsync(const QList<int> &ids, const float rating, const bool save_tags) {
QMetaObject::invokeMethod(this, "UpdateSongsRating", Qt::QueuedConnection, Q_ARG(QList<int>, ids), Q_ARG(float, rating), Q_ARG(bool, save_tags));
}
void CollectionBackend::UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days) {
@@ -2014,3 +2040,4 @@ void CollectionBackend::ExpireSongs(const int directory_id, const int expire_una
}

View File

@@ -92,6 +92,8 @@ class CollectionBackendInterface : public QObject {
virtual DirectoryList GetAllDirectories() = 0;
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
virtual SongList GetAllSongs() = 0;
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
virtual SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) = 0;
@@ -105,7 +107,7 @@ class CollectionBackendInterface : public QObject {
virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0;
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) = 0;
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) = 0;
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
virtual Album GetAlbumArt(const QString &effective_albumartist, const QString &album) = 0;
@@ -157,6 +159,8 @@ class CollectionBackend : public CollectionBackendInterface {
DirectoryList GetAllDirectories() override;
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
SongList GetAllSongs() override;
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
@@ -171,7 +175,7 @@ class CollectionBackend : public CollectionBackendInterface {
AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) override;
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) override;
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) override;
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
Album GetAlbumArt(const QString &effective_albumartist, const QString &album) override;
@@ -208,8 +212,8 @@ class CollectionBackend : public CollectionBackendInterface {
void AddOrUpdateSongsAsync(const SongList &songs);
void UpdateSongsBySongIDAsync(const SongMap &new_songs);
void UpdateSongRatingAsync(const int id, const double rating);
void UpdateSongsRatingAsync(const QList<int> &ids, const double rating);
void UpdateSongRatingAsync(const int id, const float rating, const bool save_tags = false);
void UpdateSongsRatingAsync(const QList<int> &ids, const float rating, const bool save_tags = false);
public slots:
void Exit();
@@ -225,7 +229,7 @@ class CollectionBackend : public CollectionBackendInterface {
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
void CompilationsNeedUpdating();
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url);
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
void IncrementPlayCount(const int id);
void IncrementSkipCount(const int id, const float progress);
@@ -236,8 +240,8 @@ class CollectionBackend : public CollectionBackendInterface {
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
void UpdateSongRating(const int id, const double rating);
void UpdateSongsRating(const QList<int> &id_list, const double rating);
void UpdateSongRating(const int id, const float rating, const bool save_tags = false);
void UpdateSongsRating(const QList<int> &id_list, const float rating, const bool save_tags = false);
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
@@ -255,7 +259,7 @@ class CollectionBackend : public CollectionBackendInterface {
void TotalSongCountUpdated(int);
void TotalArtistCountUpdated(int);
void TotalAlbumCountUpdated(int);
void SongsRatingChanged(SongList);
void SongsRatingChanged(SongList, bool);
void ExitFinished();

View File

@@ -38,6 +38,10 @@
</item>
<item>
<widget class="QToolButton" name="options">
<property name="styleSheet">
<string notr="true">padding-right: 16px;
</string>
</property>
<property name="iconSize">
<size>
<width>16</width>
@@ -45,7 +49,10 @@
</size>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
<enum>QToolButton::MenuButtonPopup</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>

View File

@@ -295,7 +295,7 @@ CollectionItem *CollectionModel::CreateCompilationArtistNode(const bool signal,
Q_ASSERT(parent->compilation_artist_node_ == nullptr);
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
if (signal) beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
parent->compilation_artist_node_ = new CollectionItem(CollectionItem::Type_Container, parent);
parent->compilation_artist_node_->compilation_artist_node_ = nullptr;
@@ -808,7 +808,10 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const
case Role_SortText:
return item->SortText();
default:
return QVariant();
}
return QVariant();
}
@@ -1176,7 +1179,7 @@ CollectionItem *CollectionModel::InitItem(const GroupBy type, const bool signal,
CollectionItem::Type item_type = type == GroupBy_None ? CollectionItem::Type_Song : CollectionItem::Type_Container;
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
if (signal) beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
// Initialize the item depending on what type it's meant to be
CollectionItem *item = new CollectionItem(item_type, parent);
@@ -1597,7 +1600,7 @@ void CollectionModel::FinishItem(const GroupBy type, const bool signal, const bo
if (!divider_key.isEmpty() && !divider_nodes_.contains(divider_key)) {
if (signal) {
beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
}
CollectionItem *divider = new CollectionItem(CollectionItem::Type_Divider, root_);
@@ -1676,7 +1679,7 @@ QString CollectionModel::SortTextForArtist(QString artist) {
for (const auto &i : Song::kArticles) {
if (artist.startsWith(i)) {
int ilen = i.length();
qint64 ilen = i.length();
artist = artist.right(artist.length() - ilen) + ", " + i.left(ilen - 1);
break;
}
@@ -1871,6 +1874,7 @@ const CollectionModel::GroupBy &CollectionModel::Grouping::operator[](const int
case 0: return first;
case 1: return second;
case 2: return third;
default: break;
}
qLog(Error) << "CollectionModel::Grouping[] index out of range" << i;
return first;
@@ -1883,6 +1887,7 @@ CollectionModel::GroupBy &CollectionModel::Grouping::operator[](const int i) {
case 0: return first;
case 1: return second;
case 2: return third;
default: break;
}
qLog(Error) << "CollectionModel::Grouping[] index out of range" << i;

View File

@@ -87,7 +87,9 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
song_tracking_(true),
mark_songs_unavailable_(true),
expire_unavailable_songs_days_(60),
overwrite_rating_(false),
stop_requested_(false),
abort_requested_(false),
rescan_in_progress_(false),
rescan_timer_(new QTimer(this)),
periodic_scan_timer_(new QTimer(this)),
@@ -150,6 +152,7 @@ void CollectionWatcher::ReloadSettings() {
song_tracking_ = s.value("song_tracking", false).toBool();
mark_songs_unavailable_ = song_tracking_ ? true : s.value("mark_songs_unavailable", true).toBool();
expire_unavailable_songs_days_ = s.value("expire_unavailable_songs", 60).toInt();
overwrite_rating_ = s.value("overwrite_rating", false).toBool();
s.endGroup();
best_image_filters_.clear();
@@ -210,7 +213,7 @@ CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher,
CollectionWatcher::ScanTransaction::~ScanTransaction() {
// If we're stopping then don't commit the transaction
if (!watcher_->stop_requested_) {
if (!watcher_->stop_requested_ && !watcher_->abort_requested_) {
CommitNewOrUpdatedSongs();
}
@@ -371,6 +374,8 @@ SubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryList &subdirs) {
stop_requested_ = false;
watched_dirs_[dir.id] = dir;
if (subdirs.isEmpty()) {
@@ -390,7 +395,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
transaction.SetKnownSubdirs(subdirs);
transaction.AddToProgressMax(files_count);
for (const Subdirectory &subdir : subdirs) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
@@ -452,16 +457,16 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
}
// First we "quickly" get a list of the files in the directory that we think might be music. While we're here, we also look for new subdirectories and possible album artwork.
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot);
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
while (it.hasNext()) {
if (stop_requested_) return;
if (stop_requested_ || abort_requested_) return;
QString child(it.next());
QFileInfo child_info(child);
if (child_info.isDir()) {
if (!child_info.isHidden() && !t->HasSeenSubdir(child)) {
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.
Subdirectory new_subdir;
new_subdir.directory_id = -1;
@@ -487,7 +492,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
}
}
if (stop_requested_) return;
if (stop_requested_ || abort_requested_) return;
// Ask the database for a list of files in this directory
SongList songs_in_db = t->FindSongsInSubdirectory(path);
@@ -498,10 +503,10 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
QStringList files_on_disk_copy = files_on_disk;
for (const QString &file : files_on_disk_copy) {
if (stop_requested_) return;
if (stop_requested_ || abort_requested_) return;
// Associated CUE
QString new_cue = NoExtensionPart(file) + ".cue";
QString new_cue = CueParser::FindCueFilename(file);
SongList matching_songs;
if (FindSongsByPath(songs_in_db, file, &matching_songs)) { // Found matching song in DB by path.
@@ -510,9 +515,9 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
// The song is in the database and still on disk.
// Check the mtime to see if it's been changed since it was added.
QFileInfo file_info(file);
QFileInfo fileinfo(file);
if (!file_info.exists()) {
if (!fileinfo.exists()) {
// Partially fixes race condition - if file was removed between being added to the list and now.
files_on_disk.removeAll(file);
t->AddToProgress(1);
@@ -520,16 +525,20 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
}
// CUE sheet's path from collection (if any).
qint64 matching_song_cue_mtime = GetMtimeForCue(matching_song.cue_path());
qint64 matching_song_cue_mtime = static_cast<qint64>(GetMtimeForCue(matching_song.cue_path()));
// CUE sheet's path from this file (if any).
qint64 new_cue_mtime = GetMtimeForCue(new_cue);
qint64 new_cue_mtime = 0;
if (!new_cue.isEmpty()) {
new_cue_mtime = static_cast<qint64>(GetMtimeForCue(new_cue));
}
bool cue_added = new_cue_mtime != 0 && !matching_song.has_cue();
bool cue_deleted = matching_song_cue_mtime == 0 && matching_song.has_cue();
const bool cue_added = new_cue_mtime != 0 && !matching_song.has_cue();
const bool cue_changed = new_cue_mtime != 0 && matching_song.has_cue() && new_cue != matching_song.cue_path();
const bool cue_deleted = matching_song.has_cue() && new_cue_mtime == 0;
// Watch out for CUE songs which have their mtime equal to qMax(media_file_mtime, cue_sheet_mtime)
bool changed = (matching_song.mtime() != qMax(file_info.lastModified().toSecsSinceEpoch(), matching_song_cue_mtime)) || cue_deleted || cue_added;
bool changed = (matching_song.mtime() != qMax(fileinfo.lastModified().toSecsSinceEpoch(), matching_song_cue_mtime)) || cue_deleted || cue_added || cue_changed;
// Also want to look to see whether the album art has changed
QUrl image = ImageForSong(file, album_art);
@@ -565,12 +574,13 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
}
#endif
if (!cue_deleted && (matching_song.has_cue() || cue_added)) { // If CUE associated.
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
}
else { // If no CUE or it's about to lose it.
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, cue_deleted, t);
}
else { // If CUE associated.
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
}
}
// Nothing has changed - mark the song available without re-scanning
@@ -594,15 +604,15 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
// The song is in the database and still on disk.
// Check the mtime to see if it's been changed since it was added.
QFileInfo file_info(file);
if (!file_info.exists()) {
QFileInfo fileinfo(file);
if (!fileinfo.exists()) {
// Partially fixes race condition - if file was removed between being added to the list and now.
files_on_disk.removeAll(file);
t->AddToProgress(1);
continue;
}
// Make sure the songs aren't deleted, as they still exist elsewhere with a different fingerprint.
// Make sure the songs aren't deleted, as they still exist elsewhere with a different file path.
bool matching_songs_has_cue = false;
for (const Song &matching_song : matching_songs) {
QString matching_filename = matching_song.url().toLocalFile();
@@ -619,19 +629,19 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
}
// CUE sheet's path from this file (if any).
const qint64 new_cue_mtime = GetMtimeForCue(new_cue);
const bool cue_deleted = new_cue_mtime == 0 && matching_songs_has_cue;
const bool cue_added = new_cue_mtime != 0 && !matching_songs_has_cue;
qint64 new_cue_mtime = 0;
if (!new_cue.isEmpty()) {
new_cue_mtime = static_cast<qint64>(GetMtimeForCue(new_cue));
}
// Get new album art
QUrl image = ImageForSong(file, album_art);
if (!cue_deleted && (matching_songs_has_cue || cue_added)) { // CUE associated.
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, matching_songs_has_cue && new_cue_mtime == 0, t);
}
else { // If no CUE or it's about to lose it.
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, cue_deleted, t);
else { // If CUE associated.
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
}
}
@@ -686,7 +696,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
// Recurse into the new subdirs that we found
for (const Subdirectory &my_new_subdir : my_new_subdirs) {
if (stop_requested_) return;
if (stop_requested_ || abort_requested_) return;
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
}
@@ -726,7 +736,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
const Song matching_cue_song = sections_map[new_cue_song.beginning_nanosec()];
new_cue_song.set_id(matching_cue_song.id());
if (!new_cue_song.has_embedded_cover()) new_cue_song.set_art_automatic(image);
new_cue_song.MergeUserSetData(matching_cue_song);
new_cue_song.MergeUserSetData(matching_cue_song, true);
AddChangedSong(file, matching_cue_song, new_cue_song, t);
used_ids.insert(matching_cue_song.id());
}
@@ -769,7 +779,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
song_on_disk.set_id(matching_song.id());
song_on_disk.set_fingerprint(fingerprint);
if (!song_on_disk.has_embedded_cover()) song_on_disk.set_art_automatic(image);
song_on_disk.MergeUserSetData(matching_song);
song_on_disk.MergeUserSetData(matching_song, !overwrite_rating_);
AddChangedSong(file, matching_song, song_on_disk, t);
}
@@ -827,28 +837,47 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching_song, const Song &new_song, ScanTransaction *t) {
bool notify_new = false;
QStringList changes;
if (matching_song.is_unavailable()) {
qLog(Debug) << file << "unavailable song restored.";
t->new_songs << new_song;
}
else if (!matching_song.IsMetadataEqual(new_song)) {
qLog(Debug) << file << "metadata changed.";
t->new_songs << new_song;
}
else if (matching_song.fingerprint() != new_song.fingerprint()) {
qLog(Debug) << file << "fingerprint changed.";
t->new_songs << new_song;
}
else if (matching_song.art_automatic() != new_song.art_automatic() || matching_song.art_manual() != new_song.art_manual()) {
qLog(Debug) << file << "art changed.";
t->new_songs << new_song;
}
else if (matching_song.mtime() != new_song.mtime()) {
qLog(Debug) << file << "mtime changed.";
t->touched_songs << new_song;
qLog(Debug) << "unavailable song" << file << "restored.";
notify_new = true;
}
else {
if (matching_song.url() != new_song.url()) {
changes << "file path";
notify_new = true;
}
if (matching_song.fingerprint() != new_song.fingerprint()) {
changes << "fingerprint";
notify_new = true;
}
if (!matching_song.IsMetadataEqual(new_song)) {
changes << "metadata";
notify_new = true;
}
if (matching_song.art_automatic() != new_song.art_automatic() || matching_song.art_manual() != new_song.art_manual()) {
changes << "album art";
notify_new = true;
}
if (matching_song.mtime() != new_song.mtime()) {
changes << "mtime";
}
if (changes.isEmpty()) {
qLog(Debug) << "Song" << file << "unchanged.";
}
else {
qLog(Debug) << "Song" << file << changes.join(", ") << "changed.";
}
}
if (notify_new) {
t->new_songs << new_song;
}
else {
qLog(Debug) << file << "unchanged.";
t->touched_songs << new_song;
}
@@ -860,12 +889,12 @@ quint64 CollectionWatcher::GetMtimeForCue(const QString &cue_path) {
return 0;
}
const QFileInfo file_info(cue_path);
if (!file_info.exists()) {
const QFileInfo fileinfo(cue_path);
if (!fileinfo.exists()) {
return 0;
}
const QDateTime cue_last_modified = file_info.lastModified();
const QDateTime cue_last_modified = fileinfo.lastModified();
return cue_last_modified.isValid() ? cue_last_modified.toSecsSinceEpoch() : 0;
}
@@ -971,7 +1000,7 @@ void CollectionWatcher::RescanPathsNow() {
QList<int> dirs = rescan_queue_.keys();
for (const int dir : dirs) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
ScanTransaction transaction(this, dir, false, false, mark_songs_unavailable_);
QMap<QString, quint64> subdir_files_count;
@@ -982,7 +1011,7 @@ void CollectionWatcher::RescanPathsNow() {
}
for (const QString &path : rescan_queue_[dir]) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
Subdirectory subdir;
subdir.directory_id = dir;
subdir.mtime = 0;
@@ -1007,8 +1036,8 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
for (const QString &filter_text : best_image_filters_) {
// The images in the images list are represented by a full path, so we need to isolate just the filename
for (const QString &image : images) {
QFileInfo file_info(image);
QString filename(file_info.fileName());
QFileInfo fileinfo(image);
QString filename(fileinfo.fileName());
if (filename.contains(filter_text, Qt::CaseInsensitive))
filtered << image;
}
@@ -1027,7 +1056,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
QString biggest_path;
for (const QString &path : filtered) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
QImage image(path);
if (image.isNull()) continue;
@@ -1120,7 +1149,7 @@ void CollectionWatcher::RescanTracksNow() {
// Currently we are too stupid to rescan one file at a time, so we'll just scan the full directories
QStringList scanned_dirs; // To avoid double scans
while (!song_rescan_queue_.isEmpty()) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
Song song = song_rescan_queue_.takeFirst();
QString songdir = song.url().toLocalFile().section('/', 0, -2);
if (!scanned_dirs.contains(songdir)) {
@@ -1146,7 +1175,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
for (const Directory &dir : std::as_const(watched_dirs_)) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
SubdirectoryList subdirs(transaction.GetAllSubdirs());
@@ -1164,7 +1193,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
transaction.AddToProgressMax(files_count);
for (const Subdirectory &subdir : subdirs) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
}
@@ -1179,10 +1208,10 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &path) {
quint64 i = 0;
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot);
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
while (it.hasNext()) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
QString child = it.next();
QFileInfo path_info(child);
@@ -1219,7 +1248,7 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Subdir
quint64 i = 0;
for (const Subdirectory &subdir : subdirs) {
if (stop_requested_) break;
if (stop_requested_ || abort_requested_) break;
const quint64 files_count = FilesCountForPath(t, subdir.path);
subdir_files_count[subdir.path] = files_count;
i += files_count;

View File

@@ -62,6 +62,7 @@ class CollectionWatcher : public QObject {
void ReloadSettingsAsync();
void Stop() { stop_requested_ = true; }
void Abort() { abort_requested_ = true; }
void ExitAsync();
@@ -194,6 +195,8 @@ class CollectionWatcher : public QObject {
quint64 FilesCountForPath(ScanTransaction *t, const QString &path);
quint64 FilesCountForSubdirs(ScanTransaction *t, const SubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count);
QString FindCueFilename(const QString &filename);
private:
Song::Source source_;
CollectionBackend *backend_;
@@ -213,8 +216,10 @@ class CollectionWatcher : public QObject {
bool song_tracking_;
bool mark_songs_unavailable_;
int expire_unavailable_songs_days_;
bool overwrite_rating_;
bool stop_requested_;
bool abort_requested_;
bool rescan_in_progress_; // True if RescanTracksNow() has been called and is working.
QMap<int, Directory> watched_dirs_;

View File

@@ -1,7 +1,6 @@
/*
* 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
@@ -21,20 +20,74 @@
#include "config.h"
#include <QVariant>
#include <QString>
#include <QUrl>
#include <QSqlQuery>
#include <QSqlRecord>
#include "sqlrow.h"
#include "collectionquery.h"
SqlRow::SqlRow(const QSqlQuery &query) { Init(query); }
void SqlRow::Init(const QSqlQuery &query) {
int rows = query.record().count();
for (int i = 0; i < rows; ++i) {
columns_ << query.value(i);
const QSqlRecord r = query.record();
for (int i = 0; i < r.count(); ++i) {
columns_by_number_.insert(i, query.value(i));
const QString field_name = r.fieldName(i);
if (!columns_by_name_.contains(field_name) || columns_by_name_[field_name].isNull()) {
columns_by_name_.insert(field_name, query.value(i));
}
}
}
const QVariant SqlRow::value(const int number) const {
if (columns_by_number_.contains(number)) {
return columns_by_number_[number];
}
else {
return QVariant();
}
}
const QVariant SqlRow::value(const QString &name) const {
if (columns_by_name_.contains(name)) {
return columns_by_name_[name];
}
else {
return QVariant();
}
}
QString SqlRow::ValueToString(const QString &n) const {
return value(n).isNull() ? QString() : value(n).toString();
}
QUrl SqlRow::ValueToUrl(const QString &n) const {
return value(n).isNull() ? QUrl() : QUrl(value(n).toString());
}
int SqlRow::ValueToInt(const QString &n) const {
return value(n).isNull() ? -1 : value(n).toInt();
}
uint SqlRow::ValueToUInt(const QString &n) const {
return value(n).isNull() || value(n).toInt() < 0 ? 0 : value(n).toInt();
}
qint64 SqlRow::ValueToLongLong(const QString &n) const {
return value(n).isNull() ? -1 : value(n).toLongLong();
}
float SqlRow::ValueToFloat(const QString &n) const {
return value(n).isNull() ? -1.0F : value(n).toFloat();
}
bool SqlRow::ValueToBool(const QString &n) const {
return !value(n).isNull() && value(n).toInt() == 1;
}

View File

@@ -1,7 +1,6 @@
/*
* 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
@@ -25,24 +24,33 @@
#include <QList>
#include <QVariant>
#include <QUrl>
#include <QSqlQuery>
class CollectionQuery;
class SqlRow {
public:
SqlRow(const QSqlQuery &query);
const QVariant &value(int i) const { return columns_[i]; }
const QVariant value(const int number) const;
const QVariant value(const QString &name) const;
QList<QVariant> columns_;
QString ValueToString(const QString &n) const;
QUrl ValueToUrl(const QString &n) const;
int ValueToInt(const QString &n) const;
uint ValueToUInt(const QString &n) const;
qint64 ValueToLongLong(const QString &n) const;
float ValueToFloat(const QString &n) const;
bool ValueToBool(const QString& n) const;
private:
SqlRow();
void Init(const QSqlQuery &query);
QMap<int, QVariant> columns_by_number_;
QMap<QString, QVariant> columns_by_name_;
};
typedef QList<SqlRow> SqlRowList;

View File

@@ -46,7 +46,6 @@
#include "collection/collectionbackend.h"
#include "collection/collectionmodel.h"
#include "collection/collectionitem.h"
#include "collection/sqlrow.h"
#include "playlist/playlistmanager.h"
#include "playlist/songmimedata.h"
#include "covermanager/albumcoverloader.h"
@@ -236,14 +235,18 @@ QVariant ContextAlbumsModel::data(const CollectionItem *item, int role) const {
case Role_SortText:
return item->SortText();
default:
return QVariant();
}
return QVariant();
}
void ContextAlbumsModel::Reset() {
for (QMap<QString, CollectionItem*>::iterator it = container_nodes_.begin(); it != container_nodes_.end(); ++it) {
for (QMap<QString, CollectionItem*>::const_iterator it = container_nodes_.constBegin(); it != container_nodes_.constEnd(); ++it) {
const QString cache_key = AlbumIconPixmapCacheKey(ItemToIndex(it.value()));
QPixmapCache::remove(cache_key);
}
@@ -263,7 +266,7 @@ void ContextAlbumsModel::Reset() {
CollectionItem *ContextAlbumsModel::ItemFromSong(CollectionItem::Type item_type, const bool signal, CollectionItem *parent, const Song &s, const int container_level) {
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
if (signal) beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
CollectionItem *item = new CollectionItem(item_type, parent);
item->container_level = container_level;

View File

@@ -43,7 +43,6 @@
#include "core/song.h"
#include "collection/collectionquery.h"
#include "collection/collectionitem.h"
#include "collection/sqlrow.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "covermanager/albumcoverloaderresult.h"
@@ -72,11 +71,6 @@ class ContextAlbumsModel : public SimpleTreeModel<CollectionItem> {
LastRole
};
struct QueryResult {
QueryResult() {}
SqlRowList rows;
};
void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
SongList GetChildSongs(const QModelIndex &idx) const;
SongList GetChildSongs(const QModelIndexList &indexes) const;

View File

@@ -294,7 +294,10 @@ ContextView::ContextView(QWidget *parent)
void ContextView::resizeEvent(QResizeEvent*) {
widget_album_->setFixedSize(width() - 15, width());
if (width() != prev_width_) {
widget_album_->setFixedSize(width() - 15, width());
prev_width_ = width();
}
}
@@ -428,7 +431,7 @@ void ContextView::SearchLyrics() {
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
lyrics_fetcher_->Clear();
lyrics_tried_ = true;
lyrics_id_ = lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.album(), song_playing_.title());
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.album(), song_playing_.title()));
}
}

View File

@@ -108,7 +108,7 @@ void DeleteFiles::ProcessSomeFiles() {
// We process files in batches so we can be cancelled part-way through.
const int n = qMin(songs_.count(), progress_ + kBatchSize);
const qint64 n = qMin(static_cast<qint64>(songs_.count()), static_cast<qint64>(progress_ + kBatchSize));
for (; progress_ < n; ++progress_) {
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());

View File

@@ -133,6 +133,10 @@ QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));
}
if (ret.isNull() && !system_icons_ && !custom_icons_) {
qLog(Error) << "Couldn't load icon" << name;
}
return ret;
}

View File

@@ -56,7 +56,7 @@ static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non
{ "dialog-ok-apply", { {}} },
{ "dialog-password", { {}} },
{ "dialog-warning", { {}} },
{ "document-download", { {}} },
{ "document-download", { {"download"}} },
{ "document-new", { {}} },
{ "document-open-folder", { {}} },
{ "document-open", { {}} },
@@ -64,8 +64,8 @@ static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non
{ "document-search", { {}} },
{ "document-open-remote", { {}} },
{ "download", { {"applications-internet", "network-workgroup"}} },
{ "edit-clear-list", { {}} },
{ "edit-clear-locationbar-ltr", { {}, 0, 0, false } },
{ "edit-clear-list", { {"edit-clear-list","edit-clear-all"}} },
{ "edit-clear-locationbar-ltr", { {"edit-clear-locationbar-ltr"}} },
{ "edit-copy", { {}} },
{ "edit-delete", { {}} },
{ "edit-find", { {}} },
@@ -111,7 +111,7 @@ static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non
{ "pulseaudio", { {}} },
{ "realtek", { {}} },
{ "scrobble-disabled", { {}} },
{ "scrobble", { {}} },
{ "scrobble", { {"love"}} },
{ "search", { {}} },
{ "soundcard", { {"audiocard", "audio-card"}} },
{ "speaker", { {}} },

View File

@@ -31,11 +31,6 @@ void SetShortcutHandler(GlobalShortcutsBackendMacOS *handler);
void SetApplicationHandler(PlatformInterface *handler);
void CheckForUpdates();
QString GetBundlePath();
QString GetResourcesPath();
QString GetApplicationSupportPath();
QString GetMusicDirectory();
void EnableFullScreen(const QWidget &main_window);
} // namespace mac

View File

@@ -309,55 +309,6 @@ void CheckForUpdates() {
#endif
}
QString GetBundlePath() {
ScopedCFTypeRef<CFURLRef> app_url(CFBundleCopyBundleURL(CFBundleGetMainBundle()));
ScopedCFTypeRef<CFStringRef> mac_path(CFURLCopyFileSystemPath(app_url.get(), kCFURLPOSIXPathStyle));
const char *path = CFStringGetCStringPtr(mac_path.get(), CFStringGetSystemEncoding());
QString bundle_path = QString::fromUtf8(path);
return bundle_path;
}
QString GetResourcesPath() {
QString bundle_path = GetBundlePath();
return bundle_path + "/Contents/Resources";
}
QString GetApplicationSupportPath() {
ScopedNSAutoreleasePool pool;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
QString ret;
if ([paths count] > 0) {
NSString *user_path = [paths objectAtIndex:0];
ret = QString::fromUtf8([user_path UTF8String]);
}
else {
ret = "~/Library/Application Support";
}
return ret;
}
QString GetMusicDirectory() {
ScopedNSAutoreleasePool pool;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES);
QString ret;
if ([paths count] > 0) {
NSString *user_path = [paths objectAtIndex:0];
ret = QString::fromUtf8([user_path UTF8String]);
}
else {
ret = "~/Music";
}
return ret;
}
static int MapFunctionKey(int keycode) {
switch (keycode) {

View File

@@ -380,7 +380,7 @@ 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 = app_->player()->GetVolume();
int volume = static_cast<int>(app_->player()->GetVolume());
ui_->volume->setValue(volume);
VolumeChanged(volume);
@@ -457,8 +457,12 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
ui_->action_transcoder->setIcon(IconLoader::Load("tools-wizard"));
ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh"));
ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh"));
ui_->action_abort_collection_scan->setIcon(IconLoader::Load("dialog-error"));
ui_->action_settings->setIcon(IconLoader::Load("configure"));
ui_->action_import_data_from_last_fm->setIcon(IconLoader::Load("scrobble"));
ui_->action_console->setIcon(IconLoader::Load("keyboard"));
ui_->action_toggle_show_sidebar->setIcon(IconLoader::Load("view-choose"));
ui_->action_auto_complete_tags->setIcon(IconLoader::Load("musicbrainz"));
// Scrobble
@@ -1375,7 +1379,7 @@ void MainWindow::TrackSkipped(PlaylistItemPtr item) {
const qint64 seconds_left = (length - position) / kNsecPerSec;
const qint64 seconds_total = length / kNsecPerSec;
if (((0.05 * seconds_total > 60 && percentage < 0.98) || percentage < 0.95) && seconds_left > 5) { // Never count the skip if under 5 seconds left
if (((0.05 * static_cast<double>(seconds_total) > 60.0 && percentage < 0.98) || percentage < 0.95) && seconds_left > 5) { // Never count the skip if under 5 seconds left
app_->collection_backend()->IncrementSkipCountAsync(song.id(), percentage);
}
}
@@ -1630,7 +1634,7 @@ void MainWindow::Seeked(const qint64 microseconds) {
const qint64 position = microseconds / kUsecPerSec;
const qint64 length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec;
tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / length * 100));
tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
}
@@ -1641,10 +1645,10 @@ void MainWindow::UpdateTrackPosition() {
const qint64 length = (item->Metadata().length_nanosec() / kNsecPerSec);
if (length <= 0) return;
const int position = std::floor(static_cast<float>(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5);
const int position = std::floor(static_cast<float>(app_->player()->engine()->position_nanosec()) / static_cast<float>(kNsecPerSec) + 0.5);
// Update the tray icon every 10 seconds
if (position % 10 == 0) tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / length * 100));
if (position % 10 == 0) tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
// Send Scrobble
if (app_->scrobbler()->IsEnabled() && item->Metadata().is_metadata_good()) {
@@ -1816,7 +1820,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
// Are any of the selected songs editable or queued?
QModelIndexList selection = ui_->playlist->view()->selectionModel()->selectedRows();
bool cue_selected = false;
int selected = ui_->playlist->view()->selectionModel()->selectedRows().count();
qint64 selected = ui_->playlist->view()->selectionModel()->selectedRows().count();
int editable = 0;
int in_queue = 0;
int not_in_queue = 0;
@@ -2433,7 +2437,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
if (options.set_volume() != -1) app_->player()->SetVolume(options.set_volume());
if (options.volume_modifier() != 0) {
app_->player()->SetVolume(app_->player()->GetVolume() +options.volume_modifier());
app_->player()->SetVolume(app_->player()->GetVolume() + options.volume_modifier());
}
if (options.seek_to() != -1) {
@@ -3183,8 +3187,8 @@ void MainWindow::FocusSearchField() {
qobuz_view_->FocusSearchField();
}
#endif
else if (!ui_->playlist->SearchFieldHasFocus()) {
ui_->playlist->FocusSearchField();
}
else if (!ui_->playlist->SearchFieldHasFocus()) {
ui_->playlist->FocusSearchField();
}
}

View File

@@ -151,6 +151,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">padding-right: 16px;</string>
</property>
<property name="iconSize">
<size>
<width>32</width>
@@ -448,7 +451,7 @@
<x>0</x>
<y>0</y>
<width>1131</width>
<height>24</height>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menu_music">
@@ -792,10 +795,6 @@
</property>
</action>
<action name="action_auto_complete_tags">
<property name="icon">
<iconset resource="../../data/data.qrc">
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
</property>
<property name="text">
<string>Complete tags automatically...</string>
</property>

View File

@@ -113,8 +113,8 @@ void RegisterMetaTypes() {
qRegisterMetaType<GstElement*>("GstElement*");
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
#endif
qRegisterMetaType<PlaylistItemList>("PlaylistItemList");
qRegisterMetaType<PlaylistItemPtr>("PlaylistItemPtr");
qRegisterMetaType<PlaylistItemList>("PlaylistItemList");
qRegisterMetaType<QList<PlaylistItemPtr>>("QList<PlaylistItemPtr>");
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");

View File

@@ -58,12 +58,11 @@ class MusicStorage {
typedef std::function<void(float progress)> ProgressFunction;
struct CopyJob {
CopyJob() : overwrite_(false), mark_as_listened_(false), remove_original_(false), albumcover_(false) {}
CopyJob() : overwrite_(false), remove_original_(false), albumcover_(false) {}
QString source_;
QString destination_;
Song metadata_;
bool overwrite_;
bool mark_as_listened_;
bool remove_original_;
bool albumcover_;
QString cover_source_;

View File

@@ -632,11 +632,11 @@ void Player::EngineStateChanged(const Engine::State state) {
}
void Player::SetVolume(const int value) {
void Player::SetVolume(const uint value) {
int old_volume = engine_->volume();
uint old_volume = engine_->volume();
int volume = qBound(0, value, 100);
uint volume = qBound(0U, value, 100U);
settings_.setValue("volume", volume);
engine_->SetVolume(volume);
@@ -646,9 +646,9 @@ void Player::SetVolume(const int value) {
}
int Player::GetVolume() const { return engine_->volume(); }
uint Player::GetVolume() const { return engine_->volume(); }
void Player::PlayAt(const int index, const qint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) {
void Player::PlayAt(const int index, const quint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) {
pause_time_ = QDateTime();
play_offset_nanosec_ = offset_nanosec;
@@ -696,7 +696,7 @@ void Player::CurrentMetadataChanged(const Song &metadata) {
}
void Player::SeekTo(const qint64 seconds) {
void Player::SeekTo(const quint64 seconds) {
const qint64 length_nanosec = engine_->length_nanosec();
@@ -705,7 +705,7 @@ void Player::SeekTo(const qint64 seconds) {
return;
}
const qint64 nanosec = qBound(0LL, seconds * kNsecPerSec, length_nanosec);
const qint64 nanosec = qBound(0LL, static_cast<qint64>(seconds) * kNsecPerSec, length_nanosec);
engine_->Seek(nanosec);
qLog(Debug) << "Track seeked to" << nanosec << "ns - updating scrobble point";
@@ -766,7 +766,7 @@ void Player::Mute() {
if (!volume_control_) return;
const int current_volume = engine_->volume();
const uint current_volume = engine_->volume();
if (current_volume == 0) {
SetVolume(volume_before_mute_);

View File

@@ -61,7 +61,7 @@ class PlayerInterface : public QObject {
virtual EngineBase *engine() const = 0;
virtual Engine::State GetState() const = 0;
virtual int GetVolume() const = 0;
virtual uint GetVolume() const = 0;
virtual PlaylistItemPtr GetCurrentItem() const = 0;
virtual PlaylistItemPtr GetItemAt(const int pos) const = 0;
@@ -73,7 +73,7 @@ class PlayerInterface : public QObject {
virtual void ReloadSettings() = 0;
// Manual track change to the specified track
virtual void PlayAt(const int index, const qint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0;
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;
// If there's currently a song playing, pause it, otherwise play the track that was playing last, or the first one on the playlist
virtual void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Always) = 0;
@@ -84,10 +84,10 @@ class PlayerInterface : public QObject {
virtual void Next() = 0;
virtual void Previous() = 0;
virtual void PlayPlaylist(const QString &playlist_name) = 0;
virtual void SetVolume(const int value) = 0;
virtual void SetVolume(const uint value) = 0;
virtual void VolumeUp() = 0;
virtual void VolumeDown() = 0;
virtual void SeekTo(const qint64 seconds) = 0;
virtual void SeekTo(const quint64 seconds) = 0;
// Moves the position of the currently playing song five seconds forward.
virtual void SeekForward() = 0;
// Moves the position of the currently playing song five seconds backwards.
@@ -111,7 +111,7 @@ class PlayerInterface : public QObject {
void Error(QString message = QString());
void PlaylistFinished();
void VolumeEnabled(bool);
void VolumeChanged(int volume);
void VolumeChanged(uint volume);
void TrackSkipped(PlaylistItemPtr old_track);
// Emitted when there's a manual change to the current's track position.
void Seeked(qint64 microseconds);
@@ -141,7 +141,7 @@ class Player : public PlayerInterface {
EngineBase *engine() const override { return engine_.get(); }
Engine::State GetState() const override { return last_state_; }
int GetVolume() const override;
uint GetVolume() const override;
PlaylistItemPtr GetCurrentItem() const override { return current_item_; }
PlaylistItemPtr GetItemAt(const int pos) const override;
@@ -159,17 +159,17 @@ class Player : public PlayerInterface {
public slots:
void ReloadSettings() override;
void PlayAt(const int index, const qint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) 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;
void PlayPauseHelper() override { PlayPause(play_offset_nanosec_); }
void RestartOrPrevious() override;
void Next() override;
void Previous() override;
void PlayPlaylist(const QString &playlist_name) override;
void SetVolume(const int value) override;
void SetVolume(const uint value) override;
void VolumeUp() override { SetVolume(GetVolume() + 5); }
void VolumeDown() override { SetVolume(GetVolume() - 5); }
void SeekTo(const qint64 seconds) override;
void SeekTo(const quint64 seconds) override;
void SeekForward() override;
void SeekBackward() override;
@@ -235,7 +235,7 @@ class Player : public PlayerInterface {
QMap<QString, UrlHandler*> url_handlers_;
QList<QUrl> loading_async_;
int volume_before_mute_;
uint volume_before_mute_;
QDateTime last_pressed_previous_;
bool continue_on_error_;

View File

@@ -202,15 +202,15 @@ struct Song::Private : public QSharedData {
QString basefilename_;
QUrl url_;
FileType filetype_;
int filesize_;
qint64 filesize_;
qint64 mtime_;
qint64 ctime_;
bool unavailable_;
QString fingerprint_;
int playcount_;
int skipcount_;
uint playcount_;
uint skipcount_;
qint64 lastplayed_;
qint64 lastseen_;
@@ -224,7 +224,7 @@ struct Song::Private : public QSharedData {
QString cue_path_; // If the song has a CUE, this contains it's path.
double rating_; // Database rating, not read from tags.
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.
@@ -331,14 +331,14 @@ int Song::directory_id() const { return d->directory_id_; }
const QUrl &Song::url() const { return d->url_; }
const QString &Song::basefilename() const { return d->basefilename_; }
Song::FileType Song::filetype() const { return d->filetype_; }
int Song::filesize() const { return d->filesize_; }
qint64 Song::filesize() const { return d->filesize_; }
qint64 Song::mtime() const { return d->mtime_; }
qint64 Song::ctime() const { return d->ctime_; }
QString Song::fingerprint() const { return d->fingerprint_; }
int Song::playcount() const { return d->playcount_; }
int Song::skipcount() const { return d->skipcount_; }
uint Song::playcount() const { return d->playcount_; }
uint Song::skipcount() const { return d->skipcount_; }
qint64 Song::lastplayed() const { return d->lastplayed_; }
qint64 Song::lastseen() const { return d->lastseen_; }
@@ -360,6 +360,7 @@ bool Song::save_embedded_cover_supported(const FileType filetype) {
return filetype == FileType_FLAC ||
filetype == FileType_OggVorbis ||
filetype == FileType_OggOpus ||
filetype == FileType_MPEG ||
filetype == FileType_MP4;
@@ -373,7 +374,7 @@ bool Song::init_from_file() const { return d->init_from_file_; }
const QString &Song::cue_path() const { return d->cue_path_; }
bool Song::has_cue() const { return !d->cue_path_.isEmpty(); }
double Song::rating() const { return d->rating_; }
float Song::rating() const { return d->rating_; }
bool Song::is_collection_song() const { return d->source_ == Source_Collection; }
bool Song::is_metadata_good() const { return !d->url_.isEmpty() && !d->artist_.isEmpty() && !d->title_.isEmpty(); }
@@ -408,8 +409,8 @@ bool Song::art_manual_is_valid() const {
bool Song::has_valid_art() const { return art_automatic_is_valid() || art_manual_is_valid(); }
void Song::set_id(int id) { d->id_ = id; }
void Song::set_valid(bool v) { d->valid_ = v; }
void Song::set_id(const int id) { d->id_ = id; }
void Song::set_valid(const bool v) { d->valid_ = v; }
void Song::set_artist_id(const QString &v) { d->artist_id_ = v; }
void Song::set_album_id(const QString &v) { d->album_id_ = v; }
@@ -421,7 +422,7 @@ QString Song::sortable(const QString &v) {
for (const auto &i : kArticles) {
if (copy.startsWith(i)) {
int ilen = i.length();
qint64 ilen = i.length();
return copy.right(copy.length() - ilen) + ", " + copy.left(ilen - 1);
}
}
@@ -433,10 +434,10 @@ void Song::set_title(const QString &v) { d->title_sortable_ = sortable(v); d->ti
void Song::set_album(const QString &v) { d->album_sortable_ = sortable(v); d->album_ = v; }
void Song::set_artist(const QString &v) { d->artist_sortable_ = sortable(v); d->artist_ = v; }
void Song::set_albumartist(const QString &v) { d->albumartist_sortable_ = sortable(v); d->albumartist_ = v; }
void Song::set_track(int v) { d->track_ = v; }
void Song::set_disc(int v) { d->disc_ = v; }
void Song::set_year(int v) { d->year_ = v; }
void Song::set_originalyear(int v) { d->originalyear_ = v; }
void Song::set_track(const int v) { d->track_ = v; }
void Song::set_disc(const int v) { d->disc_ = v; }
void Song::set_year(const int v) { d->year_ = v; }
void Song::set_originalyear(const int v) { d->originalyear_ = v; }
void Song::set_genre(const QString &v) { d->genre_ = v; }
void Song::set_compilation(bool v) { d->compilation_ = v; }
void Song::set_composer(const QString &v) { d->composer_ = v; }
@@ -445,40 +446,40 @@ void Song::set_grouping(const QString &v) { d->grouping_ = v; }
void Song::set_comment(const QString &v) { d->comment_ = v; }
void Song::set_lyrics(const QString &v) { d->lyrics_ = v; }
void Song::set_beginning_nanosec(qint64 v) { d->beginning_ = qMax(0LL, v); }
void Song::set_end_nanosec(qint64 v) { d->end_ = v; }
void Song::set_length_nanosec(qint64 v) { d->end_ = d->beginning_ + v; }
void Song::set_beginning_nanosec(const qint64 v) { d->beginning_ = qMax(0LL, v); }
void Song::set_end_nanosec(const qint64 v) { d->end_ = v; }
void Song::set_length_nanosec(const qint64 v) { d->end_ = d->beginning_ + v; }
void Song::set_bitrate(int v) { d->bitrate_ = v; }
void Song::set_samplerate(int v) { d->samplerate_ = v; }
void Song::set_bitdepth(int v) { d->bitdepth_ = v; }
void Song::set_bitrate(const int v) { d->bitrate_ = v; }
void Song::set_samplerate(const int v) { d->samplerate_ = v; }
void Song::set_bitdepth(const int v) { d->bitdepth_ = v; }
void Song::set_source(Source v) { d->source_ = v; }
void Song::set_directory_id(int v) { d->directory_id_ = v; }
void Song::set_source(const Source v) { d->source_ = v; }
void Song::set_directory_id(const int v) { d->directory_id_ = v; }
void Song::set_url(const QUrl &v) { d->url_ = v; }
void Song::set_basefilename(const QString &v) { d->basefilename_ = v; }
void Song::set_filetype(FileType v) { d->filetype_ = v; }
void Song::set_filesize(int v) { d->filesize_ = v; }
void Song::set_mtime(qint64 v) { d->mtime_ = v; }
void Song::set_ctime(qint64 v) { d->ctime_ = v; }
void Song::set_unavailable(bool v) { d->unavailable_ = v; }
void Song::set_filetype(const FileType v) { d->filetype_ = v; }
void Song::set_filesize(const qint64 v) { d->filesize_ = v; }
void Song::set_mtime(const qint64 v) { d->mtime_ = v; }
void Song::set_ctime(const qint64 v) { d->ctime_ = v; }
void Song::set_unavailable(const bool v) { d->unavailable_ = v; }
void Song::set_fingerprint(const QString &v) { d->fingerprint_ = v; }
void Song::set_playcount(int v) { d->playcount_ = v; }
void Song::set_skipcount(int v) { d->skipcount_ = v; }
void Song::set_lastplayed(qint64 v) { d->lastplayed_ = v; }
void Song::set_lastseen(qint64 v) { d->lastseen_ = v; }
void Song::set_playcount(const uint v) { d->playcount_ = v; }
void Song::set_skipcount(const uint v) { d->skipcount_ = v; }
void Song::set_lastplayed(const qint64 v) { d->lastplayed_ = v; }
void Song::set_lastseen(const qint64 v) { d->lastseen_ = v; }
void Song::set_compilation_detected(bool v) { d->compilation_detected_ = v; }
void Song::set_compilation_on(bool v) { d->compilation_on_ = v; }
void Song::set_compilation_off(bool v) { d->compilation_off_ = v; }
void Song::set_compilation_detected(const bool v) { d->compilation_detected_ = v; }
void Song::set_compilation_on(const bool v) { d->compilation_on_ = v; }
void Song::set_compilation_off(const bool v) { d->compilation_off_ = v; }
void Song::set_art_automatic(const QUrl &v) { d->art_automatic_ = v; }
void Song::set_art_manual(const QUrl &v) { d->art_manual_ = v; }
void Song::set_cue_path(const QString &v) { d->cue_path_ = v; }
void Song::set_rating(double v) { d->rating_ = 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; }
@@ -846,11 +847,11 @@ void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
d->grouping_ = QStringFromStdString(pb.grouping());
d->comment_ = QStringFromStdString(pb.comment());
d->lyrics_ = QStringFromStdString(pb.lyrics());
set_length_nanosec(pb.length_nanosec());
set_length_nanosec(static_cast<qint64>(pb.length_nanosec()));
d->bitrate_ = pb.bitrate();
d->samplerate_ = pb.samplerate();
d->bitdepth_ = pb.bitdepth();
set_url(QUrl::fromEncoded(QByteArray(pb.url().data(), pb.url().size())));
set_url(QUrl::fromEncoded(QByteArray(pb.url().data(), static_cast<qint64>(pb.url().size()))));
d->basefilename_ = QStringFromStdString(pb.basefilename());
d->filetype_ = static_cast<FileType>(pb.filetype());
d->filesize_ = pb.filesize();
@@ -859,17 +860,21 @@ void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
d->skipcount_ = pb.skipcount();
d->lastplayed_ = pb.lastplayed();
d->lastseen_ = pb.lastseen();
d->suspicious_tags_ = pb.suspicious_tags();
if (pb.has_playcount()) {
d->playcount_ = pb.playcount();
}
if (pb.has_rating()) {
d->rating_ = pb.rating();
}
if (pb.has_art_automatic()) {
QByteArray art_automatic(pb.art_automatic().data(), pb.art_automatic().size());
QByteArray art_automatic(pb.art_automatic().data(), static_cast<qint64>(pb.art_automatic().size()));
if (!art_automatic.isEmpty()) set_art_automatic(QUrl::fromLocalFile(art_automatic));
}
d->suspicious_tags_ = pb.suspicious_tags();
InitArtManual();
}
@@ -884,233 +889,108 @@ void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
pb->set_album(DataCommaSizeFromQString(d->album_));
pb->set_artist(DataCommaSizeFromQString(d->artist_));
pb->set_albumartist(DataCommaSizeFromQString(d->albumartist_));
pb->set_composer(DataCommaSizeFromQString(d->composer_));
pb->set_performer(DataCommaSizeFromQString(d->performer_));
pb->set_grouping(DataCommaSizeFromQString(d->grouping_));
pb->set_lyrics(DataCommaSizeFromQString(d->lyrics_));
pb->set_track(d->track_);
pb->set_disc(d->disc_);
pb->set_year(d->year_);
pb->set_originalyear(d->originalyear_);
pb->set_genre(DataCommaSizeFromQString(d->genre_));
pb->set_comment(DataCommaSizeFromQString(d->comment_));
pb->set_compilation(d->compilation_);
pb->set_playcount(d->playcount_);
pb->set_skipcount(d->skipcount_);
pb->set_lastplayed(d->lastplayed_);
pb->set_lastseen(d->lastseen_);
pb->set_composer(DataCommaSizeFromQString(d->composer_));
pb->set_performer(DataCommaSizeFromQString(d->performer_));
pb->set_grouping(DataCommaSizeFromQString(d->grouping_));
pb->set_comment(DataCommaSizeFromQString(d->comment_));
pb->set_lyrics(DataCommaSizeFromQString(d->lyrics_));
pb->set_length_nanosec(length_nanosec());
pb->set_bitrate(d->bitrate_);
pb->set_samplerate(d->samplerate_);
pb->set_bitdepth(d->bitdepth_);
pb->set_url(url.constData(), url.size());
pb->set_basefilename(DataCommaSizeFromQString(d->basefilename_));
pb->set_filetype(static_cast<spb::tagreader::SongMetadata_FileType>(d->filetype_));
pb->set_filesize(d->filesize_);
pb->set_mtime(d->mtime_);
pb->set_ctime(d->ctime_);
pb->set_filesize(d->filesize_);
pb->set_suspicious_tags(d->suspicious_tags_);
pb->set_playcount(d->playcount_);
pb->set_skipcount(d->skipcount_);
pb->set_lastplayed(d->lastplayed_);
pb->set_lastseen(d->lastseen_);
pb->set_art_automatic(art_automatic.constData(), art_automatic.size());
pb->set_filetype(static_cast<spb::tagreader::SongMetadata_FileType>(d->filetype_));
pb->set_rating(d->rating_);
pb->set_suspicious_tags(d->suspicious_tags_);
}
#define tostr(n) (q.value(n).isNull() ? QString() : q.value(n).toString())
#define toint(n) (q.value(n).isNull() ? -1 : q.value(n).toInt())
#define tolonglong(n) (q.value(n).isNull() ? -1 : q.value(n).toLongLong())
#define todouble(n) (q.value(n).isNull() ? -1 : q.value(n).toDouble())
void Song::InitFromQuery(const SqlRow &q, const bool reliable_metadata) {
void Song::InitFromQuery(const SqlRow &q, bool reliable_metadata, int col) {
d->id_ = q.value("rowid").isNull() ? -1 : q.value("rowid").toInt();
//qLog(Debug) << "Song::kColumns.size():" << Song::kColumns.size() << "q.columns_.size():" << q.columns_.size() << "col:" << col;
int x = col;
d->id_ = toint(col);
for (int i = 0; i < Song::kColumns.size(); i++) {
x++;
if (x >= q.columns_.size()) {
qLog(Error) << "Skipping" << Song::kColumns.value(i);
break;
set_title(q.ValueToString("title"));
set_album(q.ValueToString("album"));
set_artist(q.ValueToString("artist"));
set_albumartist(q.ValueToString("albumartist"));
d->track_ = q.ValueToInt("track");
d->disc_ = q.ValueToInt("disc");
d->year_ = q.ValueToInt("year");
d->originalyear_ = q.ValueToInt("originalyear");
d->genre_ = q.ValueToString("genre");
d->compilation_ = q.value("compilation").toBool();
d->composer_ = q.ValueToString("composer");
d->performer_ = q.ValueToString("performer");
d->grouping_ = q.ValueToString("grouping");
d->comment_ = q.ValueToString("comment");
d->lyrics_ = q.ValueToString("lyrics");
d->artist_id_ = q.ValueToString("artist_id");
d->album_id_ = q.ValueToString("album_id");
d->song_id_ = q.ValueToString("song_id");
d->beginning_ = q.value("beginning").isNull() ? 0 : q.value("beginning").toLongLong();
set_length_nanosec(q.ValueToLongLong("length"));
d->bitrate_ = q.ValueToInt("bitrate");
d->samplerate_ = q.ValueToInt("samplerate");
d->bitdepth_ = q.ValueToInt("bitdepth");
d->source_ = Source(q.value("source").isNull() ? 0 : q.value("source").toInt());
d->directory_id_ = q.ValueToInt("directory_id");
set_url(QUrl::fromEncoded(q.ValueToString("url").toUtf8()));
d->basefilename_ = QFileInfo(d->url_.toLocalFile()).fileName();
d->filetype_ = FileType(q.value("filetype").isNull() ? 0 : q.value("filetype").toInt());
d->filesize_ = q.ValueToLongLong("filesize");
d->mtime_ = q.ValueToLongLong("mtime");
d->ctime_ = q.ValueToLongLong("ctime");
d->unavailable_ = q.value("unavailable").toBool();
d->fingerprint_ = q.ValueToString("fingerprint");
d->playcount_ = q.ValueToUInt("playcount");
d->skipcount_ = q.ValueToUInt("skipcount");
d->lastplayed_ = q.ValueToLongLong("lastplayed");
d->lastseen_ = q.ValueToLongLong("lastseen");
d->compilation_detected_ = q.ValueToBool("compilation_detected");
d->compilation_on_ = q.ValueToBool("compilation_on");
d->compilation_off_ = q.ValueToBool("compilation_off");
QString art_automatic = q.ValueToString("art_automatic");
if (!art_automatic.isEmpty()) {
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
set_art_automatic(QUrl::fromEncoded(art_automatic.toUtf8()));
}
//qLog(Debug) << "Index:" << i << x << Song::kColumns.value(i) << q.value(x).toString();
if (Song::kColumns.value(i) == "title") {
set_title(tostr(x));
}
else if (Song::kColumns.value(i) == "album") {
set_album(tostr(x));
}
else if (Song::kColumns.value(i) == "artist") {
set_artist(tostr(x));
}
else if (Song::kColumns.value(i) == "albumartist") {
set_albumartist(tostr(x));
}
else if (Song::kColumns.value(i) == "track") {
d->track_ = toint(x);
}
else if (Song::kColumns.value(i) == "disc") {
d->disc_ = toint(x);
}
else if (Song::kColumns.value(i) == "year") {
d->year_ = toint(x);
}
else if (Song::kColumns.value(i) == "originalyear") {
d->originalyear_ = toint(x);
}
else if (Song::kColumns.value(i) == "genre") {
d->genre_ = tostr(x);
}
else if (Song::kColumns.value(i) == "compilation") {
d->compilation_ = q.value(x).toBool();
}
else if (Song::kColumns.value(i) == "composer") {
d->composer_ = tostr(x);
}
else if (Song::kColumns.value(i) == "performer") {
d->performer_ = tostr(x);
}
else if (Song::kColumns.value(i) == "grouping") {
d->grouping_ = tostr(x);
}
else if (Song::kColumns.value(i) == "comment") {
d->comment_ = tostr(x);
}
else if (Song::kColumns.value(i) == "lyrics") {
d->lyrics_ = tostr(x);
}
else if (Song::kColumns.value(i) == "artist_id") {
d->artist_id_ = tostr(x);
}
else if (Song::kColumns.value(i) == "album_id") {
d->album_id_ = tostr(x);
}
else if (Song::kColumns.value(i) == "song_id") {
d->song_id_ = tostr(x);
}
else if (Song::kColumns.value(i) == "beginning") {
d->beginning_ = q.value(x).isNull() ? 0 : q.value(x).toLongLong();
}
else if (Song::kColumns.value(i) == "length") {
set_length_nanosec(tolonglong(x));
}
else if (Song::kColumns.value(i) == "bitrate") {
d->bitrate_ = toint(x);
}
else if (Song::kColumns.value(i) == "samplerate") {
d->samplerate_ = toint(x);
}
else if (Song::kColumns.value(i) == "bitdepth") {
d->bitdepth_ = toint(x);
}
else if (Song::kColumns.value(i) == "source") {
d->source_ = Source(q.value(x).toInt());
}
else if (Song::kColumns.value(i) == "directory_id") {
d->directory_id_ = toint(x);
}
else if (Song::kColumns.value(i) == "url") {
set_url(QUrl::fromEncoded(tostr(x).toUtf8()));
d->basefilename_ = QFileInfo(d->url_.toLocalFile()).fileName();
}
else if (Song::kColumns.value(i) == "filetype") {
d->filetype_ = FileType(q.value(x).toInt());
}
else if (Song::kColumns.value(i) == "filesize") {
d->filesize_ = toint(x);
}
else if (Song::kColumns.value(i) == "mtime") {
d->mtime_ = tolonglong(x);
}
else if (Song::kColumns.value(i) == "ctime") {
d->ctime_ = tolonglong(x);
}
else if (Song::kColumns.value(i) == "unavailable") {
d->unavailable_ = q.value(x).toBool();
}
else if (Song::kColumns.value(i) == "fingerprint") {
d->fingerprint_ = tostr(x);
}
else if (Song::kColumns.value(i) == "playcount") {
d->playcount_ = q.value(x).isNull() ? 0 : q.value(x).toInt();
}
else if (Song::kColumns.value(i) == "skipcount") {
d->skipcount_ = q.value(x).isNull() ? 0 : q.value(x).toInt();
}
else if (Song::kColumns.value(i) == "lastplayed") {
d->lastplayed_ = tolonglong(x);
}
else if (Song::kColumns.value(i) == "lastseen") {
d->lastseen_ = tolonglong(x);
}
else if (Song::kColumns.value(i) == "compilation_detected") {
d->compilation_detected_ = q.value(x).toBool();
}
else if (Song::kColumns.value(i) == "compilation_on") {
d->compilation_on_ = q.value(x).toBool();
}
else if (Song::kColumns.value(i) == "compilation_off") {
d->compilation_off_ = q.value(x).toBool();
}
else if (Song::kColumns.value(i) == "compilation_effective") {
}
else if (Song::kColumns.value(i) == "art_automatic") {
QString art_automatic = tostr(x);
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
set_art_automatic(QUrl::fromEncoded(art_automatic.toUtf8()));
}
else {
set_art_automatic(QUrl::fromLocalFile(art_automatic));
}
}
else if (Song::kColumns.value(i) == "art_manual") {
QString art_manual = tostr(x);
if (art_manual.contains(QRegularExpression("..+:.*"))) {
set_art_manual(QUrl::fromEncoded(art_manual.toUtf8()));
}
else {
set_art_manual(QUrl::fromLocalFile(art_manual));
}
}
else if (Song::kColumns.value(i) == "effective_albumartist") {
}
else if (Song::kColumns.value(i) == "effective_originalyear") {
}
else if (Song::kColumns.value(i) == "cue_path") {
d->cue_path_ = tostr(x);
}
else if (Song::kColumns.value(i) == "rating") {
d->rating_ = todouble(x);
}
else {
qLog(Error) << "Forgot to handle" << Song::kColumns.value(i);
set_art_automatic(QUrl::fromLocalFile(art_automatic));
}
}
QString art_manual = q.ValueToString("art_manual");
if (!art_manual.isEmpty()) {
if (art_manual.contains(QRegularExpression("..+:.*"))) {
set_art_manual(QUrl::fromEncoded(art_manual.toUtf8()));
}
else {
set_art_manual(QUrl::fromLocalFile(art_manual));
}
}
d->cue_path_ = q.ValueToString("cue_path");
d->rating_ = q.ValueToFloat("rating");
d->valid_ = true;
d->init_from_file_ = reliable_metadata;
InitArtManual();
#undef tostr
#undef toint
#undef tolonglong
#undef todouble
}
void Song::InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo) {
@@ -1195,8 +1075,8 @@ void Song::InitFromItdb(Itdb_Track *track, const QString &prefix) {
d->mtime_ = track->time_modified;
d->ctime_ = track->time_added;
d->playcount_ = track->playcount;
d->skipcount_ = track->skipcount;
d->playcount_ = static_cast<int>(track->playcount);
d->skipcount_ = static_cast<int>(track->skipcount);
d->lastplayed_ = track->time_played;
if (itdb_track_has_thumbnails(track) && !d->artist_.isEmpty() && !d->title_.isEmpty()) {
@@ -1231,7 +1111,7 @@ void Song::ToItdb(Itdb_Track *track) const {
track->grouping = strdup(d->grouping_.toUtf8().constData());
track->comment = strdup(d->comment_.toUtf8().constData());
track->tracklen = length_nanosec() / kNsecPerMsec;
track->tracklen = static_cast<int>(length_nanosec() / kNsecPerMsec);
track->bitrate = d->bitrate_;
track->samplerate = d->samplerate_;
@@ -1239,7 +1119,7 @@ void Song::ToItdb(Itdb_Track *track) const {
track->type1 = (d->filetype_ == FileType_MPEG ? 1 : 0);
track->type2 = (d->filetype_ == FileType_MPEG ? 1 : 0);
track->mediatype = 1; // Audio
track->size = d->filesize_;
track->size = static_cast<uint>(d->filesize_);
track->time_modified = d->mtime_;
track->time_added = d->ctime_;
@@ -1265,15 +1145,15 @@ void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) {
d->url_ = QUrl(QString("mtp://%1/%2").arg(host, QString::number(track->item_id)));
d->basefilename_ = QString::number(track->item_id);
d->filesize_ = track->filesize;
d->filesize_ = static_cast<qint64>(track->filesize);
d->mtime_ = track->modificationdate;
d->ctime_ = track->modificationdate;
set_length_nanosec(track->duration * kNsecPerMsec);
d->samplerate_ = track->samplerate;
d->samplerate_ = static_cast<int>(track->samplerate);
d->bitdepth_ = 0;
d->bitrate_ = track->bitrate;
d->bitrate_ = static_cast<int>(track->bitrate);
d->playcount_ = track->usecount;
@@ -1314,7 +1194,7 @@ void Song::ToMTP(LIBMTP_track_t *track) const {
track->filename = strdup(d->basefilename_.toUtf8().constData());
track->filesize = d->filesize_;
track->filesize = static_cast<quint64>(d->filesize_);
track->modificationdate = d->mtime_;
track->duration = length_nanosec() / kNsecPerMsec;
@@ -1398,73 +1278,65 @@ bool Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) {
void Song::BindToQuery(SqlQuery *query) const {
#define strval(x) ((x).isNull() ? "" : (x))
#define intval(x) ((x) <= 0 ? -1 : (x))
#define notnullintval(x) ((x) == -1 ? QVariant() : (x))
// Remember to bind these in the same order as kBindSpec
query->BindValue(":title", strval(d->title_));
query->BindValue(":album", strval(d->album_));
query->BindValue(":artist", strval(d->artist_));
query->BindValue(":albumartist", strval(d->albumartist_));
query->BindValue(":track", intval(d->track_));
query->BindValue(":disc", intval(d->disc_));
query->BindValue(":year", intval(d->year_));
query->BindValue(":originalyear", intval(d->originalyear_));
query->BindValue(":genre", strval(d->genre_));
query->BindValue(":compilation", d->compilation_ ? 1 : 0);
query->BindValue(":composer", strval(d->composer_));
query->BindValue(":performer", strval(d->performer_));
query->BindValue(":grouping", strval(d->grouping_));
query->BindValue(":comment", strval(d->comment_));
query->BindValue(":lyrics", strval(d->lyrics_));
query->BindStringValue(":title", d->title_);
query->BindStringValue(":album", d->album_);
query->BindStringValue(":artist", d->artist_);
query->BindStringValue(":albumartist", d->albumartist_);
query->BindIntValue(":track", d->track_);
query->BindIntValue(":disc", d->disc_);
query->BindIntValue(":year", d->year_);
query->BindIntValue(":originalyear", d->originalyear_);
query->BindStringValue(":genre", d->genre_);
query->BindBoolValue(":compilation", d->compilation_);
query->BindStringValue(":composer", d->composer_);
query->BindStringValue(":performer", d->performer_);
query->BindStringValue(":grouping", d->grouping_);
query->BindStringValue(":comment", d->comment_);
query->BindStringValue(":lyrics", d->lyrics_);
query->BindValue(":artist_id", strval(d->artist_id_));
query->BindValue(":album_id", strval(d->album_id_));
query->BindValue(":song_id", strval(d->song_id_));
query->BindStringValue(":artist_id", d->artist_id_);
query->BindStringValue(":album_id", d->album_id_);
query->BindStringValue(":song_id", d->song_id_);
query->BindValue(":beginning", d->beginning_);
query->BindValue(":length", intval(length_nanosec()));
query->BindLongLongValue(":length", length_nanosec());
query->BindValue(":bitrate", intval(d->bitrate_));
query->BindValue(":samplerate", intval(d->samplerate_));
query->BindValue(":bitdepth", intval(d->bitdepth_));
query->BindIntValue(":bitrate", d->bitrate_);
query->BindIntValue(":samplerate", d->samplerate_);
query->BindIntValue(":bitdepth", d->bitdepth_);
query->BindValue(":source", d->source_);
query->BindValue(":directory_id", notnullintval(d->directory_id_));
query->BindValue(":url", d->url_.toString(QUrl::FullyEncoded));
query->BindNotNullIntValue(":directory_id", d->directory_id_);
query->BindUrlValue(":url", d->url_);
query->BindValue(":filetype", d->filetype_);
query->BindValue(":filesize", notnullintval(d->filesize_));
query->BindValue(":mtime", notnullintval(d->mtime_));
query->BindValue(":ctime", notnullintval(d->ctime_));
query->BindValue(":unavailable", d->unavailable_ ? 1 : 0);
query->BindNotNullLongLongValue(":filesize", d->filesize_);
query->BindNotNullLongLongValue(":mtime", d->mtime_);
query->BindNotNullLongLongValue(":ctime", d->ctime_);
query->BindBoolValue(":unavailable", d->unavailable_);
query->BindValue(":fingerprint", strval(d->fingerprint_));
query->BindStringValue(":fingerprint", d->fingerprint_);
query->BindValue(":playcount", d->playcount_);
query->BindValue(":skipcount", d->skipcount_);
query->BindValue(":lastplayed", intval(d->lastplayed_));
query->BindValue(":lastseen", intval(d->lastseen_));
query->BindLongLongValue(":lastplayed", d->lastplayed_);
query->BindLongLongValue(":lastseen", d->lastseen_);
query->BindValue(":compilation_detected", d->compilation_detected_ ? 1 : 0);
query->BindValue(":compilation_on", d->compilation_on_ ? 1 : 0);
query->BindValue(":compilation_off", d->compilation_off_ ? 1 : 0);
query->BindValue(":compilation_effective", is_compilation() ? 1 : 0);
query->BindBoolValue(":compilation_detected", d->compilation_detected_);
query->BindBoolValue(":compilation_on", d->compilation_on_);
query->BindBoolValue(":compilation_off", d->compilation_off_);
query->BindBoolValue(":compilation_effective", is_compilation());
query->BindValue(":art_automatic", d->art_automatic_.isValid() ? d->art_automatic_.toString(QUrl::FullyEncoded) : "");
query->BindValue(":art_manual", d->art_manual_.isValid() ? d->art_manual_.toString(QUrl::FullyEncoded) : "");
query->BindUrlValue(":art_automatic", d->art_automatic_);
query->BindUrlValue(":art_manual", d->art_manual_);
query->BindValue(":effective_albumartist", strval(effective_albumartist()));
query->BindValue(":effective_originalyear", intval(effective_originalyear()));
query->BindStringValue(":effective_albumartist", effective_albumartist());
query->BindIntValue(":effective_originalyear", effective_originalyear());
query->BindValue(":cue_path", d->cue_path_);
query->BindValue(":rating", intval(d->rating_));
#undef intval
#undef notnullintval
#undef strval
query->BindFloatValue(":rating", d->rating_);
}
@@ -1550,7 +1422,7 @@ QString Song::SampleRateBitDepthToText() const {
QString Song::PrettyRating() const {
double rating = d->rating_;
float rating = d->rating_;
if (rating == -1.0F) return "0";
@@ -1583,7 +1455,9 @@ bool Song::IsMetadataEqual(const Song &other) const {
d->bitrate_ == other.d->bitrate_ &&
d->samplerate_ == other.d->samplerate_ &&
d->bitdepth_ == other.d->bitdepth_ &&
d->cue_path_ == other.d->cue_path_;
d->cue_path_ == other.d->cue_path_ &&
d->playcount_ == other.d->playcount_ &&
d->rating_ == other.d->rating_;
}
bool Song::IsMetadataAndMoreEqual(const Song &other) const {
@@ -1596,12 +1470,7 @@ bool Song::IsMetadataAndMoreEqual(const Song &other) const {
}
bool Song::IsEditable() const {
return d->valid_ &&
!d->url_.isEmpty() &&
(d->url_.isLocalFile() || d->source_ == Source_Stream) &&
!has_cue();
return d->valid_ && d->url_.isValid() && (d->url_.isLocalFile() || d->source_ == Source_Stream) && !has_cue();
}
bool Song::operator==(const Song &other) const {
@@ -1661,7 +1530,7 @@ void Song::ToXesam(QVariantMap *map) const {
AddMetadataAsList("xesam:artist", artist(), map);
AddMetadata("xesam:album", album(), map);
AddMetadataAsList("xesam:albumArtist", albumartist(), map);
AddMetadata("mpris:length", length_nanosec() / kNsecPerUsec, map);
AddMetadata("mpris:length", (length_nanosec() / kNsecPerUsec), map);
AddMetadata("xesam:trackNumber", track(), map);
AddMetadataAsList("xesam:genre", genre(), map);
AddMetadata("xesam:discNumber", disc(), map);
@@ -1669,18 +1538,24 @@ void Song::ToXesam(QVariantMap *map) const {
AddMetadata("xesam:contentCreated", AsMPRISDateTimeType(ctime()), map);
AddMetadata("xesam:lastUsed", AsMPRISDateTimeType(lastplayed()), map);
AddMetadataAsList("xesam:composer", composer(), map);
AddMetadata("xesam:useCount", playcount(), map);
AddMetadata("xesam:useCount", static_cast<int>(playcount()), map);
}
void Song::MergeUserSetData(const Song &other) {
void Song::MergeUserSetData(const Song &other, const bool merge_rating) {
if (other.playcount() > 0) {
set_playcount(other.playcount());
}
if (merge_rating && other.rating() > 0.0F) {
set_rating(other.rating());
}
set_playcount(other.playcount());
set_skipcount(other.skipcount());
set_lastplayed(other.lastplayed());
set_art_manual(other.art_manual());
set_compilation_on(other.compilation_on());
set_compilation_off(other.compilation_off());
set_rating(other.rating());
}

View File

@@ -168,7 +168,7 @@ class Song {
void Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec);
void Init(const QString &title, const QString &artist, const QString &album, qint64 beginning, qint64 end);
void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
void InitFromQuery(const SqlRow &query, bool reliable_metadata, int col = 0);
void InitFromQuery(const SqlRow &query, const bool reliable_metadata);
void InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo);
void InitArtManual();
void InitArtAutomatic();
@@ -187,7 +187,7 @@ class Song {
// Copies important statistics from the other song to this one, overwriting any data that already exists.
// Useful when you want updated tags from disk but you want to keep user stats.
void MergeUserSetData(const Song &other);
void MergeUserSetData(const Song &other, const bool merge_rating);
// Save
void BindToQuery(SqlQuery *query) const;
@@ -237,14 +237,14 @@ class Song {
const QUrl &url() const;
const QString &basefilename() const;
FileType filetype() const;
int filesize() const;
qint64 filesize() const;
qint64 mtime() const;
qint64 ctime() const;
QString fingerprint() const;
int playcount() const;
int skipcount() const;
uint playcount() const;
uint skipcount() const;
qint64 lastplayed() const;
qint64 lastseen() const;
@@ -258,7 +258,7 @@ class Song {
const QString &cue_path() const;
bool has_cue() const;
double rating() const;
float rating() const;
const QString &effective_album() const;
int effective_originalyear() const;
@@ -318,16 +318,16 @@ class Song {
// Setters
bool IsEditable() const;
void set_id(int id);
void set_valid(bool v);
void set_id(const int id);
void set_valid(const bool v);
void set_title(const QString &v);
void set_album(const QString &v);
void set_artist(const QString &v);
void set_albumartist(const QString &v);
void set_track(int v);
void set_disc(int v);
void set_year(int v);
void set_track(const int v);
void set_disc(const int v);
void set_year(const int v);
void set_originalyear(int v);
void set_genre(const QString &v);
void set_compilation(bool v);
@@ -345,37 +345,37 @@ class Song {
void set_end_nanosec(qint64 v);
void set_length_nanosec(qint64 v);
void set_bitrate(int v);
void set_samplerate(int v);
void set_bitdepth(int v);
void set_bitrate(const int v);
void set_samplerate(const int v);
void set_bitdepth(const int v);
void set_source(Source v);
void set_directory_id(int v);
void set_source(const Source v);
void set_directory_id(const int v);
void set_url(const QUrl &v);
void set_basefilename(const QString &v);
void set_filetype(FileType v);
void set_filesize(int v);
void set_mtime(qint64 v);
void set_ctime(qint64 v);
void set_unavailable(bool v);
void set_filetype(const FileType v);
void set_filesize(const qint64 v);
void set_mtime(const qint64 v);
void set_ctime(const qint64 v);
void set_unavailable(const bool v);
void set_fingerprint(const QString &v);
void set_playcount(int v);
void set_skipcount(int v);
void set_lastplayed(qint64 v);
void set_lastseen(qint64 v);
void set_playcount(const uint v);
void set_skipcount(const uint v);
void set_lastplayed(const qint64 v);
void set_lastseen(const qint64 v);
void set_compilation_detected(bool v);
void set_compilation_on(bool v);
void set_compilation_off(bool v);
void set_compilation_detected(const bool v);
void set_compilation_on(const bool v);
void set_compilation_off(const bool v);
void set_art_automatic(const QUrl &v);
void set_art_manual(const QUrl &v);
void set_cue_path(const QString &v);
void set_rating(const double v);
void set_rating(const float v);
void set_stream_url(const QUrl &v);
void set_image(const QImage &i);

View File

@@ -300,7 +300,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
}
// Check if it's a CUE file
QString matching_cue = filename.section('.', 0, -2) + ".cue";
QString matching_cue = CueParser::FindCueFilename(filename);
if (QFile::exists(matching_cue)) {
// It's a CUE - create virtual tracks
QFile cue(matching_cue);
@@ -552,7 +552,7 @@ GstPadProbeReturn SongLoader::DataReady(GstPad*, GstPadProbeInfo *info, gpointer
gst_buffer_map(buffer, &map, GST_MAP_READ);
// Append the data to the buffer
instance->buffer_.append(reinterpret_cast<const char*>(map.data), map.size);
instance->buffer_.append(reinterpret_cast<const char*>(map.data), static_cast<qint64>(map.size));
qLog(Debug) << "Received total" << instance->buffer_.size() << "bytes";
gst_buffer_unmap(buffer, &map);

View File

@@ -22,8 +22,9 @@
#include <QSqlQuery>
#include <QMap>
#include <QVariant>
#include <QString>
#include <QVariantList>
#include <QString>
#include <QUrl>
#include "sqlquery.h"
@@ -37,13 +38,61 @@ void SqlQuery::BindValue(const QString &placeholder, const QVariant &value) {
}
void SqlQuery::BindStringValue(const QString &placeholder, const QString &value) {
BindValue(placeholder, value.isNull() ? "" : value);
}
void SqlQuery::BindUrlValue(const QString &placeholder, const QUrl &value) {
BindValue(placeholder, value.isValid() ? value.toString(QUrl::FullyEncoded) : "");
}
void SqlQuery::BindIntValue(const QString &placeholder, const int value) {
BindValue(placeholder, value <= 0 ? -1 : value);
}
void SqlQuery::BindLongLongValue(const QString &placeholder, const qint64 value) {
BindValue(placeholder, value <= 0 ? -1 : value);
}
void SqlQuery::BindFloatValue(const QString &placeholder, const float value) {
BindValue(placeholder, value <= 0 ? -1 : value);
}
void SqlQuery::BindBoolValue(const QString &placeholder, const bool value) {
BindValue(placeholder, value ? 1 : 0);
}
void SqlQuery::BindNotNullIntValue(const QString &placeholder, const int value) {
BindValue(placeholder, value == -1 ? QVariant() : value);
}
void SqlQuery::BindNotNullLongLongValue(const QString &placeholder, const qint64 value) {
BindValue(placeholder, value == -1 ? QVariant() : value);
}
bool SqlQuery::Exec() {
bool success = exec();
last_query_ = executedQuery();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
for (QMap<QString, QVariant>::const_iterator it = bound_values_.begin(); it != bound_values_.end(); ++it) {
for (QMap<QString, QVariant>::const_iterator it = bound_values_.constBegin(); it != bound_values_.constEnd(); ++it) {
last_query_.replace(it.key(), it.value().toString());
}
bound_values_.clear();

View File

@@ -31,9 +31,18 @@
class SqlQuery : public QSqlQuery {
public:
explicit SqlQuery(QSqlDatabase db) : QSqlQuery(db) {}
explicit SqlQuery(const QSqlDatabase &db) : QSqlQuery(db) {}
void BindValue(const QString &placeholder, const QVariant &value);
void BindStringValue(const QString &placeholder, const QString &value);
void BindUrlValue(const QString &placeholder, const QUrl &value);
void BindIntValue(const QString &placeholder, const int value);
void BindLongLongValue(const QString &placeholder, const qint64 value);
void BindFloatValue(const QString &placeholder, const float value);
void BindBoolValue(const QString &placeholder, const bool value);
void BindNotNullIntValue(const QString &placeholder, const int value);
void BindNotNullLongLongValue(const QString &placeholder, const qint64 value);
bool Exec();
QString LastQuery() const;

View File

@@ -91,7 +91,7 @@ QColor StyleHelper::notTooBrightHighlightColor() {
QColor highlightColor = QApplication::palette().highlight().color();
if (0.5 * highlightColor.saturationF() + 0.75 - highlightColor.valueF() < 0) {
highlightColor.setHsvF(highlightColor.hsvHueF(), 0.1 + highlightColor.saturationF() * 2.0, highlightColor.valueF());
highlightColor.setHsvF(highlightColor.hsvHueF(), 0.1F + highlightColor.saturationF() * 2.0F, highlightColor.valueF());
}
return highlightColor;
@@ -133,10 +133,10 @@ QColor StyleHelper::highlightColor(bool lightColored) {
QColor result = baseColor(lightColored);
if (lightColored) {
result.setHsv(result.hue(), clamp(result.saturation()), clamp(result.value() * 1.06));
result.setHsv(result.hue(), clamp(static_cast<float>(result.saturation())), clamp(static_cast<float>(result.value()) * 1.06F));
}
else {
result.setHsv(result.hue(), clamp(result.saturation()), clamp(result.value() * 1.16));
result.setHsv(result.hue(), clamp(static_cast<float>(result.saturation())), clamp(static_cast<float>(result.value()) * 1.16F));
}
return result;
@@ -146,7 +146,7 @@ QColor StyleHelper::highlightColor(bool lightColored) {
QColor StyleHelper::shadowColor(bool lightColored) {
QColor result = baseColor(lightColored);
result.setHsv(result.hue(), clamp(result.saturation() * 1.1), clamp(result.value() * 0.70));
result.setHsv(result.hue(), clamp(static_cast<float>(result.saturation()) * 1.1F), clamp(static_cast<float>(result.value()) * 0.70F));
return result;
}
@@ -162,7 +162,7 @@ QColor StyleHelper::borderColor(bool lightColored) {
QColor StyleHelper::toolBarBorderColor() {
const QColor base = baseColor();
return QColor::fromHsv(base.hue(), base.saturation(), clamp(base.value() * 0.80F));
return QColor::fromHsv(base.hue(), base.saturation(), clamp(static_cast<float>(base.value()) * 0.80F));
}
@@ -172,7 +172,7 @@ void StyleHelper::setBaseColor(const QColor &newcolor) {
m_requestedBaseColor = newcolor;
QColor color;
color.setHsv(newcolor.hue(), newcolor.saturation() * 0.7, 64 + newcolor.value() / 3);
color.setHsv(newcolor.hue(), static_cast<int>(static_cast<float>(newcolor.saturation()) * 0.7F), 64 + newcolor.value() / 3);
if (color.isValid() && color != m_baseColor) {
m_baseColor = color;
@@ -303,7 +303,7 @@ void StyleHelper::drawArrow(QStyle::PrimitiveElement element, QPainter *painter,
QPixmap pixmap;
QString pixmapName = QString::asprintf("StyleHelper::drawArrow-%d-%d-%d-%f", element, size, (enabled ? 1 : 0), devicePixelRatio);
if (!QPixmapCache::find(pixmapName, &pixmap)) {
QImage image(size * devicePixelRatio, size * devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
QImage image(size * static_cast<int>(devicePixelRatio), size * static_cast<int>(devicePixelRatio), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter p(&image);
@@ -325,7 +325,7 @@ void StyleHelper::drawArrow(QStyle::PrimitiveElement element, QPainter *painter,
drawCommonStyleArrow(image.rect(), m_IconsDisabledColor);
}
else {
drawCommonStyleArrow(image.rect().translated(0, devicePixelRatio), toolBarDropShadowColor());
drawCommonStyleArrow(image.rect().translated(0, static_cast<int>(devicePixelRatio)), toolBarDropShadowColor());
drawCommonStyleArrow(image.rect(), m_IconsBaseColor);
}
p.end();
@@ -434,7 +434,7 @@ void StyleHelper::tintImage(QImage &img, const QColor &tintColor) {
if (alpha > 0) {
c.toHsl();
qreal l = c.lightnessF();
float l = c.lightnessF();
QColor newColor = QColor::fromHslF(tintColor.hslHueF(), tintColor.hslSaturationF(), l);
newColor.setAlpha(alpha);
img.setPixel(x, y, newColor.rgba());

View File

@@ -69,6 +69,17 @@ void TagReaderClient::WorkerFailedToStart() {
qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it.";
}
TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) {
spb::tagreader::Message message;
spb::tagreader::IsMediaFileRequest *req = message.mutable_is_media_file_request();
req->set_filename(DataCommaSizeFromQString(filename));
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
spb::tagreader::Message message;
@@ -94,17 +105,6 @@ TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &m
}
TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) {
spb::tagreader::Message message;
spb::tagreader::IsMediaFileRequest *req = message.mutable_is_media_file_request();
req->set_filename(DataCommaSizeFromQString(filename));
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) {
spb::tagreader::Message message;
@@ -128,6 +128,64 @@ TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const
}
TagReaderReply *TagReaderClient::UpdateSongPlaycount(const Song &metadata) {
spb::tagreader::Message message;
spb::tagreader::SaveSongPlaycountToFileRequest *req = message.mutable_save_song_playcount_to_file_request();
req->set_filename(DataCommaSizeFromQString(metadata.url().toLocalFile()));
metadata.ToProtobuf(req->mutable_metadata());
return worker_pool_->SendMessageWithReply(&message);
}
void TagReaderClient::UpdateSongsPlaycount(const SongList &songs) {
for (const Song &song : songs) {
TagReaderReply *reply = UpdateSongPlaycount(song);
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
}
}
TagReaderReply *TagReaderClient::UpdateSongRating(const Song &metadata) {
spb::tagreader::Message message;
spb::tagreader::SaveSongRatingToFileRequest *req = message.mutable_save_song_rating_to_file_request();
req->set_filename(DataCommaSizeFromQString(metadata.url().toLocalFile()));
metadata.ToProtobuf(req->mutable_metadata());
return worker_pool_->SendMessageWithReply(&message);
}
void TagReaderClient::UpdateSongsRating(const SongList &songs) {
for (const Song &song : songs) {
TagReaderReply *reply = UpdateSongRating(song);
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
}
}
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
bool ret = false;
TagReaderReply *reply = IsMediaFile(filename);
if (reply->WaitForFinished()) {
ret = reply->message().is_media_file_response().success();
}
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
return ret;
}
void TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
Q_ASSERT(QThread::currentThread() != thread());
@@ -156,22 +214,6 @@ bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &meta
}
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
bool ret = false;
TagReaderReply *reply = IsMediaFile(filename);
if (reply->WaitForFinished()) {
ret = reply->message().is_media_file_response().success();
}
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
return ret;
}
QByteArray TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
@@ -181,7 +223,7 @@ QByteArray TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) {
TagReaderReply *reply = LoadEmbeddedArt(filename);
if (reply->WaitForFinished()) {
const std::string &data_str = reply->message().load_embedded_art_response().data();
ret = QByteArray(data_str.data(), data_str.size());
ret = QByteArray(data_str.data(), static_cast<qint64>(data_str.size()));
}
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
@@ -198,7 +240,7 @@ QImage TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename)
TagReaderReply *reply = LoadEmbeddedArt(filename);
if (reply->WaitForFinished()) {
const std::string &data_str = reply->message().load_embedded_art_response().data();
ret.loadFromData(QByteArray(data_str.data(), data_str.size()));
ret.loadFromData(QByteArray(data_str.data(), static_cast<qint64>(data_str.size())));
}
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
@@ -210,14 +252,46 @@ bool TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const QBy
Q_ASSERT(QThread::currentThread() != thread());
bool ret = false;
bool success = false;
TagReaderReply *reply = SaveEmbeddedArt(filename, data);
if (reply->WaitForFinished()) {
ret = reply->message().save_embedded_art_response().success();
success = reply->message().save_embedded_art_response().success();
}
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
return ret;
return success;
}
bool TagReaderClient::UpdateSongPlaycountBlocking(const Song &metadata) {
Q_ASSERT(QThread::currentThread() != thread());
bool success = false;
TagReaderReply *reply = UpdateSongPlaycount(metadata);
if (reply->WaitForFinished()) {
success = reply->message().save_song_playcount_to_file_response().success();
}
reply->deleteLater();
return success;
}
bool TagReaderClient::UpdateSongRatingBlocking(const Song &metadata) {
Q_ASSERT(QThread::currentThread() != thread());
bool success = false;
TagReaderReply *reply = UpdateSongRating(metadata);
if (reply->WaitForFinished()) {
success = reply->message().save_song_rating_to_file_response().success();
}
reply->deleteLater();
return success;
}

View File

@@ -58,6 +58,8 @@ class TagReaderClient : public QObject {
ReplyType *IsMediaFile(const QString &filename);
ReplyType *LoadEmbeddedArt(const QString &filename);
ReplyType *SaveEmbeddedArt(const QString &filename, const QByteArray &data);
ReplyType* UpdateSongPlaycount(const Song &metadata);
ReplyType* UpdateSongRating(const Song &metadata);
// Convenience functions that call the above functions and wait for a response.
// These block the calling thread with a semaphore, and must NOT be called from the TagReaderClient's thread.
@@ -67,6 +69,8 @@ class TagReaderClient : public QObject {
QByteArray LoadEmbeddedArtBlocking(const QString &filename);
QImage LoadEmbeddedArtAsImageBlocking(const QString &filename);
bool SaveEmbeddedArtBlocking(const QString &filename, const QByteArray &data);
bool UpdateSongPlaycountBlocking(const Song &metadata);
bool UpdateSongRatingBlocking(const Song &metadata);
// TODO: Make this not a singleton
static TagReaderClient *Instance() { return sInstance; }
@@ -78,6 +82,10 @@ class TagReaderClient : public QObject {
void Exit();
void WorkerFailedToStart();
public slots:
void UpdateSongsPlaycount(const SongList &songs);
void UpdateSongsRating(const SongList &songs);
private:
static TagReaderClient *sInstance;

View File

@@ -79,7 +79,7 @@ void TaskManager::SetTaskBlocksCollectionScans(const int id) {
}
void TaskManager::SetTaskProgress(const int id, const qint64 progress, const qint64 max) {
void TaskManager::SetTaskProgress(const int id, const quint64 progress, const quint64 max) {
{
QMutexLocker l(&mutex_);
@@ -93,7 +93,7 @@ void TaskManager::SetTaskProgress(const int id, const qint64 progress, const qin
emit TasksChanged();
}
void TaskManager::IncreaseTaskProgress(const int id, const qint64 progress, const qint64 max) {
void TaskManager::IncreaseTaskProgress(const int id, const quint64 progress, const quint64 max) {
{
QMutexLocker l(&mutex_);
@@ -134,7 +134,7 @@ void TaskManager::SetTaskFinished(const int id) {
}
int TaskManager::GetTaskProgress(int id) {
quint64 TaskManager::GetTaskProgress(int id) {
{
QMutexLocker l(&mutex_);

View File

@@ -41,8 +41,8 @@ class TaskManager : public QObject {
Task() : id(0), progress(0), progress_max(0), blocks_collection_scans(false) {}
int id;
QString name;
qint64 progress;
qint64 progress_max;
quint64 progress;
quint64 progress_max;
bool blocks_collection_scans;
};
@@ -64,10 +64,10 @@ class TaskManager : public QObject {
int StartTask(const QString &name);
void SetTaskBlocksCollectionScans(const int id);
void SetTaskProgress(const int id, const qint64 progress, const qint64 max = 0);
void IncreaseTaskProgress(const int id, const qint64 progress, const qint64 max = 0);
void SetTaskProgress(const int id, const quint64 progress, const quint64 max = 0);
void IncreaseTaskProgress(const int id, const quint64 progress, const quint64 max = 0);
void SetTaskFinished(const int id);
int GetTaskProgress(const int id);
quint64 GetTaskProgress(const int id);
signals:
void TasksChanged();

View File

@@ -191,13 +191,13 @@ QString PrettySize(const quint64 bytes) {
ret = QString::number(bytes) + " bytes";
}
else if (bytes <= 1000 * 1000) {
ret = QString::asprintf("%.1f KB", static_cast<float>(bytes) / 1000);
ret = QString::asprintf("%.1f KB", static_cast<float>(bytes) / 1000.0F);
}
else if (bytes <= 1000 * 1000 * 1000) {
ret = QString::asprintf("%.1f MB", static_cast<float>(bytes) / (1000 * 1000));
ret = QString::asprintf("%.1f MB", static_cast<float>(bytes) / (1000.0F * 1000.0F));
}
else {
ret = QString::asprintf("%.1f GB", static_cast<float>(bytes) / (1000 * 1000 * 1000));
ret = QString::asprintf("%.1f GB", static_cast<float>(bytes) / (1000.0F * 1000.0F * 1000.0F));
}
}
return ret;
@@ -479,15 +479,15 @@ void OpenInFileBrowser(const QList<QUrl> &urls) {
QByteArray Hmac(const QByteArray &key, const QByteArray &data, const QCryptographicHash::Algorithm method) {
const int kBlockSize = 64; // bytes
Q_ASSERT(key.length() <= kBlockSize);
constexpr int block_size = 64;
Q_ASSERT(key.length() <= block_size);
QByteArray inner_padding(kBlockSize, static_cast<char>(0x36));
QByteArray outer_padding(kBlockSize, static_cast<char>(0x5c));
QByteArray inner_padding(block_size, static_cast<char>(0x36));
QByteArray outer_padding(block_size, static_cast<char>(0x5c));
for (int i = 0; i < key.length(); ++i) {
inner_padding[i] = inner_padding[i] ^ key[i];
outer_padding[i] = outer_padding[i] ^ key[i];
inner_padding[i] = static_cast<char>(inner_padding[i] ^ key[i]);
outer_padding[i] = static_cast<char>(outer_padding[i] ^ key[i]);
}
QByteArray part;
@@ -652,13 +652,14 @@ QString DecodeHtmlEntities(const QString &text) {
.replace("&lt;", "<")
.replace("&#60;", "<")
.replace("&gt;", ">")
.replace("&#62;", ">");
.replace("&#62;", ">")
.replace("&#x27;", "'");
return copy;
}
int SetThreadIOPriority(const IoPriority priority) {
long SetThreadIOPriority(const IoPriority priority) {
#ifdef Q_OS_LINUX
return syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, GetThreadId(), 4 | priority << IOPRIO_CLASS_SHIFT);
@@ -671,7 +672,7 @@ int SetThreadIOPriority(const IoPriority priority) {
}
int GetThreadId() {
long GetThreadId() {
#ifdef Q_OS_LINUX
return syscall(SYS_gettid);
@@ -746,7 +747,7 @@ QString GetRandomString(const int len, const QString &UseCharacters) {
QString randstr;
for (int i = 0; i < len; ++i) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
const int index = QRandomGenerator::global()->bounded(0, UseCharacters.length());
const qint64 index = QRandomGenerator::global()->bounded(0, UseCharacters.length());
#else
const int index = qrand() % UseCharacters.length();
#endif
@@ -767,7 +768,7 @@ QString DesktopEnvironment() {
if (!qEnvironmentVariableIsEmpty("GNOME_DESKTOP_SESSION_ID")) return "Gnome";
QString session = GetEnv("DESKTOP_SESSION");
int slash = session.lastIndexOf('/');
qint64 slash = session.lastIndexOf('/');
if (slash != -1) {
QSettings desktop_file(QString(session + ".desktop"), QSettings::IniFormat);
desktop_file.beginGroup("Desktop Entry");
@@ -864,7 +865,7 @@ QString ReplaceMessage(const QString &message, const Song &song, const QString &
QString copy(message);
// Replace the first line
int pos = 0;
qint64 pos = 0;
QRegularExpressionMatch match;
for (match = variable_replacer.match(message, pos); match.hasMatch(); match = variable_replacer.match(message, pos)) {
pos = match.capturedStart();
@@ -873,7 +874,7 @@ QString ReplaceMessage(const QString &message, const Song &song, const QString &
pos += match.capturedLength();
}
int index_of = copy.indexOf(QRegularExpression(" - (>|$)"));
qint64 index_of = copy.indexOf(QRegularExpression(" - (>|$)"));
if (index_of >= 0) copy = copy.remove(index_of, 3);
return copy;
@@ -1039,4 +1040,3 @@ ScopedWCharArray::ScopedWCharArray(const QString &str)
str.toWCharArray(data_.get());
data_[chars_] = '\0';
}

View File

@@ -122,8 +122,8 @@ enum IoPriority {
};
static const int IOPRIO_CLASS_SHIFT = 13;
int SetThreadIOPriority(const IoPriority priority);
int GetThreadId();
long SetThreadIOPriority(const IoPriority priority);
long GetThreadId();
QString GetRandomStringWithChars(const int len);
QString GetRandomStringWithCharsAndNumbers(const int len);
@@ -159,13 +159,13 @@ class ScopedWCharArray {
wchar_t *get() const { return data_.get(); }
explicit operator wchar_t*() const { return get(); }
int characters() const { return chars_; }
int bytes() const { return (chars_ + 1) *sizeof(wchar_t); }
qint64 characters() const { return chars_; }
qint64 bytes() const { return (chars_ + 1) *sizeof(wchar_t); }
private:
Q_DISABLE_COPY(ScopedWCharArray)
int chars_;
qint64 chars_;
std::unique_ptr<wchar_t[]> data_;
};

View File

@@ -534,9 +534,12 @@ void AlbumCoverChoiceController::SaveArtAutomaticToSong(Song *song, const QUrl &
if (!song->is_valid()) return;
song->set_art_automatic(art_automatic);
if (song->has_embedded_cover()) {
song->clear_art_manual();
}
if (song->source() == Song::Source_Collection) {
app_->collection_backend()->UpdateAutomaticAlbumArtAsync(song->effective_albumartist(), song->album(), art_automatic);
app_->collection_backend()->UpdateAutomaticAlbumArtAsync(song->effective_albumartist(), song->album(), art_automatic, song->has_embedded_cover());
}
if (*song == app_->current_albumcover_loader()->last_song()) {

View File

@@ -45,15 +45,17 @@ void AlbumCoverExporter::SetDialogResult(const AlbumCoverExport::DialogResult &d
void AlbumCoverExporter::AddExportRequest(const Song &song) {
requests_.append(new CoverExportRunnable(dialog_result_, song));
all_ = requests_.count();
all_ = static_cast<int>(requests_.count());
}
void AlbumCoverExporter::Cancel() { requests_.clear(); }
void AlbumCoverExporter::StartExporting() {
exported_ = 0;
skipped_ = 0;
AddJobsToPool();
}
void AlbumCoverExporter::AddJobsToPool() {
@@ -70,13 +72,17 @@ void AlbumCoverExporter::AddJobsToPool() {
}
void AlbumCoverExporter::CoverExported() {
exported_++;
++exported_;
emit AlbumCoversExportUpdate(exported_, skipped_, all_);
AddJobsToPool();
}
void AlbumCoverExporter::CoverSkipped() {
skipped_++;
++skipped_;
emit AlbumCoversExportUpdate(exported_, skipped_, all_);
AddJobsToPool();
}

View File

@@ -46,7 +46,7 @@ class AlbumCoverExporter : public QObject {
void StartExporting();
void Cancel();
int request_count() { return requests_.size(); }
int request_count() { return static_cast<int>(requests_.size()); }
signals:
void AlbumCoversExportUpdate(int exported, int skipped, int all);

View File

@@ -64,7 +64,7 @@ AlbumCoverFetcher::~AlbumCoverFetcher() {
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool batch) {
CoverSearchRequest request;
request.id = next_id_++;
request.id = ++next_id_;
request.artist = artist;
request.album = album;
request.album = request.album.remove(Song::kAlbumRemoveDisc);
@@ -81,7 +81,7 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album, const QString &title) {
CoverSearchRequest request;
request.id = next_id_++;
request.id = ++next_id_;
request.artist = artist;
request.album = album;
request.album = request.album.remove(Song::kAlbumRemoveDisc);

View File

@@ -46,7 +46,7 @@ class AlbumCoverFetcherSearch;
// This class represents a single search-for-cover request. It identifies and describes the request.
struct CoverSearchRequest {
explicit CoverSearchRequest() : id(-1), search(false), batch(false) {}
explicit CoverSearchRequest() : id(0), search(false), batch(false) {}
// An unique (for one AlbumCoverFetcher) request identifier
quint64 id;

View File

@@ -549,7 +549,7 @@ quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, c
QMutexLocker l(&mutex_save_image_async_);
quint64 id = ++save_image_async_id_;
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QString, cover_filename));
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QString, cover_filename));
return id;
}
@@ -558,7 +558,7 @@ quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, c
QMutexLocker l(&mutex_save_image_async_);
quint64 id = ++save_image_async_id_;
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QImage, image));
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QImage, image));
return id;
}
@@ -567,7 +567,7 @@ quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, c
QMutexLocker l(&mutex_save_image_async_);
quint64 id = ++save_image_async_id_;
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QByteArray, image_data));
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QByteArray, image_data));
return id;
}
@@ -576,7 +576,7 @@ quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const
QMutexLocker l(&mutex_save_image_async_);
quint64 id = ++save_image_async_id_;
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QString, cover_filename));
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QString, cover_filename));
return id;
}
@@ -585,7 +585,7 @@ quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const
QMutexLocker l(&mutex_save_image_async_);
quint64 id = ++save_image_async_id_;
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QImage, image));
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QImage, image));
return id;
}
@@ -594,12 +594,12 @@ quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const
QMutexLocker l(&mutex_save_image_async_);
quint64 id = ++save_image_async_id_;
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QByteArray, image_data));
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QByteArray, image_data));
return id;
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QByteArray &image_data) {
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QByteArray &image_data) {
TagReaderReply *reply = TagReaderClient::Instance()->SaveEmbeddedArt(song_filename, image_data);
tagreader_save_embedded_art_requests_.insert(id, reply);
@@ -608,7 +608,7 @@ void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString &song_fi
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QImage &image) {
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QImage &image) {
QByteArray image_data;
@@ -624,7 +624,7 @@ void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString &song_fi
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QString &cover_filename) {
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QString &cover_filename) {
QFile file(cover_filename);
@@ -646,7 +646,7 @@ void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString &song_fi
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QImage &image) {
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QImage &image) {
if (image.isNull()) {
for (const QUrl &url : urls) {
@@ -671,7 +671,7 @@ void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> &url
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QString &cover_filename) {
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QString &cover_filename) {
QFile file(cover_filename);
@@ -692,7 +692,7 @@ void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> &url
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QByteArray &image_data) {
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QByteArray &image_data) {
for (const QUrl &url : urls) {
SaveEmbeddedCover(id, url.toLocalFile(), image_data);
@@ -700,7 +700,7 @@ void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> &url
}
void AlbumCoverLoader::SaveEmbeddedArtFinished(const qint64 id, TagReaderReply *reply, const bool cleared) {
void AlbumCoverLoader::SaveEmbeddedArtFinished(const quint64 id, TagReaderReply *reply, const bool cleared) {
if (tagreader_save_embedded_art_requests_.contains(id)) {
tagreader_save_embedded_art_requests_.remove(id, reply);

View File

@@ -98,14 +98,14 @@ class AlbumCoverLoader : public QObject {
void ProcessTasks();
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
void SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QString &cover_filename);
void SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QImage &image);
void SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QByteArray &image_data);
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QImage &image);
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QString &cover_filename);
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QByteArray &image_data);
void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QString &cover_filename);
void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QImage &image);
void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QByteArray &image_data);
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QImage &image);
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QString &cover_filename);
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QByteArray &image_data);
void SaveEmbeddedArtFinished(const qint64 id, TagReaderReply *reply, const bool cleared);
void SaveEmbeddedArtFinished(const quint64 id, TagReaderReply *reply, const bool cleared);
protected:
@@ -167,7 +167,7 @@ class AlbumCoverLoader : public QObject {
QThread *original_thread_;
QMultiMap<qint64, TagReaderReply*> tagreader_save_embedded_art_requests_;
QMultiMap<quint64, TagReaderReply*> tagreader_save_embedded_art_requests_;
};

View File

@@ -87,6 +87,10 @@
</item>
<item>
<widget class="QToolButton" name="view">
<property name="styleSheet">
<string notr="true">padding-right: 16px;
</string>
</property>
<property name="text">
<string>View</string>
</property>

View File

@@ -229,7 +229,7 @@ QByteArray DiscogsCoverProvider::GetReplyData(QNetworkReply *reply) {
}
void DiscogsCoverProvider::HandleSearchReply(QNetworkReply *reply, const quint64 id) {
void DiscogsCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
if (!replies_.contains(reply)) return;
replies_.removeAll(reply);
@@ -336,7 +336,7 @@ void DiscogsCoverProvider::SendReleaseRequest(const DiscogsCoverReleaseContext &
}
void DiscogsCoverProvider::HandleReleaseReply(QNetworkReply *reply, const quint64 search_id, const quint64 release_id) {
void DiscogsCoverProvider::HandleReleaseReply(QNetworkReply *reply, const int search_id, const quint64 release_id) {
if (!replies_.contains(reply)) return;
replies_.removeAll(reply);

View File

@@ -62,7 +62,7 @@ class DiscogsCoverProvider : public JsonCoverProvider {
struct DiscogsCoverReleaseContext {
explicit DiscogsCoverReleaseContext(const quint64 _search_id = 0, const quint64 _id = 0, const QUrl &_url = QUrl()) : search_id(_search_id), id(_id), url(_url) {}
quint64 search_id;
int search_id;
quint64 id;
QUrl url;
};
@@ -90,8 +90,8 @@ class DiscogsCoverProvider : public JsonCoverProvider {
private slots:
void FlushRequests();
void HandleSearchReply(QNetworkReply *reply, const quint64 id);
void HandleReleaseReply(QNetworkReply *reply, const quint64 id, const quint64 release_id);
void HandleSearchReply(QNetworkReply *reply, const int id);
void HandleReleaseReply(QNetworkReply *reply, const int search_id, const quint64 release_id);
private:
static const char *kUrlSearch;

View File

@@ -127,11 +127,11 @@ void MusixmatchCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
QString content = data;
QString data_begin = "var __mxmState = ";
QString data_end = ";</script>";
int begin_idx = content.indexOf(data_begin);
qint64 begin_idx = content.indexOf(data_begin);
QString content_json;
if (begin_idx > 0) {
begin_idx += data_begin.length();
int end_idx = content.indexOf(data_end, begin_idx);
qint64 end_idx = content.indexOf(data_end, begin_idx);
if (end_idx > begin_idx) {
content_json = content.mid(begin_idx, end_idx - begin_idx);
}

View File

@@ -80,7 +80,7 @@ SpotifyCoverProvider::SpotifyCoverProvider(Application *app, NetworkAccessManage
s.endGroup();
if (!refresh_token_.isEmpty()) {
qint64 time = expires_in_ - (QDateTime::currentDateTime().toSecsSinceEpoch() - login_time_);
qint64 time = static_cast<qint64>(expires_in_) - (QDateTime::currentDateTime().toSecsSinceEpoch() - static_cast<qint64>(login_time_));
if (time < 1) time = 1;
refresh_login_timer_.setInterval(static_cast<int>(time * kMsecPerSec));
refresh_login_timer_.start();

View File

@@ -106,8 +106,8 @@ bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
#endif
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
if (!service_->access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + service_->access_token().toUtf8());
if (!service_->session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", service_->session_id().toUtf8());
if (service_->oauth() && !service_->access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + service_->access_token().toUtf8());
else if (!service_->session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", service_->session_id().toUtf8());
QNetworkReply *reply = network_->get(req);
replies_ << reply;

View File

@@ -1,99 +0,0 @@
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<?xml-stylesheet type="text/xsl" href="introspect.xsl"?>
<!DOCTYPE node SYSTEM "introspect.dtd">
<!--
This file is part of avahi.
avahi is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
avahi 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 Lesser General Public
License along with avahi; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
-->
<node>
<interface name="org.freedesktop.Avahi.EntryGroup">
<method name="Free"/>
<method name="Commit"/>
<method name="Reset"/>
<method name="GetState">
<arg name="state" type="i" direction="out"/>
</method>
<signal name="StateChanged">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
<method name="IsEmpty">
<arg name="empty" type="b" direction="out"/>
</method>
<method name="AddService">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="host" type="s" direction="in"/>
<arg name="port" type="q" direction="in"/>
<arg name="txt" type="aay" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In8"
value="QList&lt;QByteArray&gt;" />
</method>
<method name="AddServiceSubtype">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="subtype" type="s" direction="in"/>
</method>
<method name="UpdateServiceTxt">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="txt" type="aay" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In6"
value="QList&lt;QByteArray&gt;" />
</method>
<method name="AddAddress">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="address" type="s" direction="in"/>
</method>
<method name="AddRecord">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="clazz" type="q" direction="in"/>
<arg name="type" type="q" direction="in"/>
<arg name="ttl" type="u" direction="in"/>
<arg name="rdata" type="ay" direction="in"/>
</method>
</interface>
</node>

View File

@@ -1,218 +0,0 @@
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<?xml-stylesheet type="text/xsl" href="introspect.xsl"?>
<!DOCTYPE node SYSTEM "introspect.dtd">
<!--
This file is part of avahi.
avahi is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
avahi 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 Lesser General Public
License along with avahi; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
-->
<node>
<interface name="org.freedesktop.Avahi.Server">
<method name="GetVersionString">
<arg name="version" type="s" direction="out"/>
</method>
<method name="GetAPIVersion">
<arg name="version" type="u" direction="out"/>
</method>
<method name="GetHostName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="SetHostName">
<arg name="name" type="s" direction="in"/>
</method>
<method name="GetHostNameFqdn">
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetDomainName">
<arg name="name" type="s" direction="out"/>
</method>
<method name="IsNSSSupportAvailable">
<arg name="yes" type="b" direction="out"/>
</method>
<method name="GetState">
<arg name="state" type="i" direction="out"/>
</method>
<signal name="StateChanged">
<arg name="state" type="i"/>
<arg name="error" type="s"/>
</signal>
<method name="GetLocalServiceCookie">
<arg name="cookie" type="u" direction="out"/>
</method>
<method name="GetAlternativeHostName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetAlternativeServiceName">
<arg name="name" type="s" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceNameByIndex">
<arg name="index" type="i" direction="in"/>
<arg name="name" type="s" direction="out"/>
</method>
<method name="GetNetworkInterfaceIndexByName">
<arg name="name" type="s" direction="in"/>
<arg name="index" type="i" direction="out"/>
</method>
<method name="ResolveHostName">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<method name="ResolveAddress">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="flags" type="u" direction="out"/>
</method>
<!--
Disabled as it breaks the code generation due to its _11_ parameters.
<method name="ResolveService">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="interface" type="i" direction="out"/>
<arg name="protocol" type="i" direction="out"/>
<arg name="name" type="s" direction="out"/>
<arg name="type" type="s" direction="out"/>
<arg name="domain" type="s" direction="out"/>
<arg name="host" type="s" direction="out"/>
<arg name="aprotocol" type="i" direction="out"/>
<arg name="address" type="s" direction="out"/>
<arg name="port" type="q" direction="out"/>
<arg name="txt" type="aay" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out9"
value="QList&lt;QByteArray&gt;" />
<arg name="flags" type="u" direction="out"/>
</method>
-->
<method name="EntryGroupNew">
<arg name="path" type="o" direction="out"/>
</method>
<method name="DomainBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="btype" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceTypeBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="ServiceResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="type" type="s" direction="in"/>
<arg name="domain" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="HostNameResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="aprotocol" type="i" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="AddressResolverNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="address" type="s" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
<method name="RecordBrowserNew">
<arg name="interface" type="i" direction="in"/>
<arg name="protocol" type="i" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="clazz" type="q" direction="in"/>
<arg name="type" type="q" direction="in"/>
<arg name="flags" type="u" direction="in"/>
<arg name="path" type="o" direction="out"/>
</method>
</interface>
</node>

View File

@@ -175,7 +175,7 @@ void CddaSongLoader::LoadSongs() {
int i = 0;
for (GList *node = entries; node != nullptr; node = node->next) {
GstTocEntry *entry = static_cast<GstTocEntry*>(node->data);
quint64 duration = 0;
qint64 duration = 0;
gint64 start = 0, stop = 0;
if (gst_toc_entry_get_start_stop_times(entry, &start, &stop)) duration = stop - start;
songs[i++].set_length_nanosec(duration);

View File

@@ -61,7 +61,7 @@ class ConnectedDevice : public QObject, public virtual MusicStorage, public std:
QString unique_id() const { return unique_id_; }
CollectionModel *model() const { return model_; }
QUrl url() const { return url_; }
int song_count() const { return song_count_; }
qint64 song_count() const { return song_count_; }
void FinishCopy(bool success) override;
void FinishDelete(bool success) override;
@@ -94,7 +94,7 @@ class ConnectedDevice : public QObject, public virtual MusicStorage, public std:
CollectionBackend *backend_;
CollectionModel *model_;
int song_count_;
qint64 song_count_;
private slots:
void BackendTotalSongCountUpdated(int count);

View File

@@ -265,7 +265,7 @@ void DeviceManager::AddDeviceFromDB(DeviceInfo *info) {
}
else {
qLog(Info) << "Device added from database: " << info->friendly_name_;
beginInsertRows(ItemToIndex(root_), devices_.count(), devices_.count());
beginInsertRows(ItemToIndex(root_), static_cast<int>(devices_.count()), static_cast<int>(devices_.count()));
devices_ << info;
endInsertRows();
}
@@ -485,7 +485,7 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) {
info->friendly_name_ = lister->MakeFriendlyName(id);
info->size_ = lister->DeviceCapacity(id);
info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
beginInsertRows(ItemToIndex(root_), devices_.count(), devices_.count());
beginInsertRows(ItemToIndex(root_), static_cast<int>(devices_.count()), static_cast<int>(devices_.count()));
devices_ << info;
endInsertRows();
}

View File

@@ -155,7 +155,7 @@ bool MtpDevice::StartCopy(QList<Song::FileType> *supported_types) {
static int ProgressCallback(uint64_t const sent, uint64_t const total, void const *const data) {
const MusicStorage::CopyJob *job = reinterpret_cast<const MusicStorage::CopyJob*>(data);
job->progress_(static_cast<float>(sent) / total);
job->progress_(static_cast<float>(sent) / static_cast<float>(total));
return 0;

View File

@@ -53,8 +53,6 @@
#include "udisks2lister.h"
constexpr char Udisks2Lister::udisks2_service_[];
Udisks2Lister::Udisks2Lister(QObject *parent) : DeviceLister(parent) {}
Udisks2Lister::~Udisks2Lister() = default;
@@ -390,7 +388,7 @@ Udisks2Lister::PartitionData Udisks2Lister::ReadPartitionData(const QDBusObjectP
for (const QByteArray &p : filesystem.mountPoints()) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // Workaround a bytearray to string conversion issue with Qt 6
QString mountpoint = QByteArray(p.data(), strlen(p.data()));
QString mountpoint = QByteArray(p.data(), static_cast<qint64>(strlen(p.data())));
#else
QString mountpoint = p;
#endif

View File

@@ -37,7 +37,7 @@
#include "about.h"
#include "ui_about.h"
About::About(QWidget *parent) : QDialog(parent) {
About::About(QWidget *parent) : QDialog(parent), ui_{} {
ui_.setupUi(this);
setWindowFlags(windowFlags()|Qt::WindowStaysOnTopHint);

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