Remove external tagreader
This commit is contained in:
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -52,7 +52,6 @@ jobs:
|
||||
dbus-1-devel
|
||||
alsa-devel
|
||||
libnotify-devel
|
||||
protobuf-devel
|
||||
sqlite3-devel
|
||||
libpulse-devel
|
||||
gstreamer-devel
|
||||
@@ -170,8 +169,6 @@ jobs:
|
||||
rsync
|
||||
boost-devel
|
||||
dbus-devel
|
||||
protobuf-devel
|
||||
protobuf-compiler
|
||||
sqlite-devel
|
||||
alsa-lib-devel
|
||||
pulseaudio-libs-devel
|
||||
@@ -258,8 +255,6 @@ jobs:
|
||||
glibc-devel
|
||||
boost-devel
|
||||
dbus-devel
|
||||
protobuf-devel
|
||||
protobuf-compiler
|
||||
sqlite-devel
|
||||
libasound-devel
|
||||
pulseaudio-devel
|
||||
@@ -360,7 +355,6 @@ jobs:
|
||||
rpmdevtools
|
||||
gettext
|
||||
lib64boost-devel
|
||||
lib64protobuf-devel
|
||||
lib64sqlite3-devel
|
||||
lib64alsa2-devel
|
||||
lib64pulseaudio-devel
|
||||
@@ -385,7 +379,6 @@ jobs:
|
||||
lib64qt6dbus-devel
|
||||
lib64qt6help-devel
|
||||
lib64qt6test-devel
|
||||
protobuf-compiler
|
||||
desktop-file-utils
|
||||
appstream-util
|
||||
hicolor-icon-theme
|
||||
@@ -458,8 +451,6 @@ jobs:
|
||||
libglib2.0-dev
|
||||
libdbus-1-dev
|
||||
libboost-dev
|
||||
libprotobuf-dev
|
||||
protobuf-compiler
|
||||
libsqlite3-dev
|
||||
libasound2-dev
|
||||
libpulse-dev
|
||||
@@ -539,8 +530,6 @@ jobs:
|
||||
libglib2.0-dev
|
||||
libboost-dev
|
||||
libdbus-1-dev
|
||||
libprotobuf-dev
|
||||
protobuf-compiler
|
||||
libsqlite3-dev
|
||||
libasound2-dev
|
||||
libpulse-dev
|
||||
@@ -623,7 +612,6 @@ jobs:
|
||||
libglib2.0-dev
|
||||
libboost-dev
|
||||
libdbus-1-dev
|
||||
libprotobuf-dev
|
||||
libsqlite3-dev
|
||||
libasound2-dev
|
||||
libpulse-dev
|
||||
@@ -645,7 +633,6 @@ jobs:
|
||||
qt6-l10n-tools
|
||||
gstreamer1.0-alsa
|
||||
gstreamer1.0-pulseaudio
|
||||
protobuf-compiler
|
||||
- name: Install keyboxd
|
||||
if: matrix.ubuntu_version == 'noble'
|
||||
env:
|
||||
@@ -789,7 +776,7 @@ jobs:
|
||||
- name: Manually Codesign
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-13'
|
||||
working-directory: build
|
||||
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libpcre2-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libfreetype.6.dylib,libzstd.1.dylib,libutf8_validity.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
|
||||
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libpcre2-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libfreetype.6.dylib,libzstd.1.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
|
||||
|
||||
- name: Manually Codesign
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-14'
|
||||
@@ -1008,7 +995,6 @@ jobs:
|
||||
-DENABLE_LIBMTP=OFF
|
||||
-DENABLE_AUDIOCD=OFF
|
||||
-DENABLE_SPOTIFY=OFF
|
||||
-DProtobuf_PROTOC_EXECUTABLE="/strawberry-mxe/usr/x86_64-pc-linux-gnu/bin/protoc"
|
||||
|
||||
- name: Run Make
|
||||
run: cmake --build build --config "${{env.cmake_buildtype}}" --parallel $(nproc)
|
||||
|
||||
@@ -120,13 +120,6 @@ if(NOT Boost_FOUND)
|
||||
find_package(Boost REQUIRED)
|
||||
endif()
|
||||
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
||||
find_package(Protobuf CONFIG)
|
||||
if(NOT Protobuf_FOUND)
|
||||
find_package(Protobuf REQUIRED)
|
||||
endif()
|
||||
if(NOT TARGET protobuf::protoc)
|
||||
message(FATAL_ERROR "Missing Protobuf compiler.")
|
||||
endif()
|
||||
if(LINUX)
|
||||
find_package(ALSA REQUIRED)
|
||||
pkg_check_modules(DBUS REQUIRED dbus-1)
|
||||
@@ -223,39 +216,19 @@ if(X11_FOUND)
|
||||
|
||||
endif(X11_FOUND)
|
||||
|
||||
option(USE_TAGLIB "Build with TagLib" ON)
|
||||
option(USE_TAGPARSER "Build with TagParser" OFF)
|
||||
|
||||
# TAGLIB
|
||||
if(USE_TAGLIB)
|
||||
find_package(TagLib 2.0)
|
||||
if(TARGET TagLib::TagLib)
|
||||
set(TAGLIB_FOUND ON)
|
||||
set(TAGLIB_LIBRARIES TagLib::TagLib)
|
||||
set(HAVE_TAGLIB_DSFFILE ON)
|
||||
set(HAVE_TAGLIB_DSDIFFFILE ON)
|
||||
else()
|
||||
pkg_check_modules(TAGLIB REQUIRED taglib>=1.12)
|
||||
endif()
|
||||
set(HAVE_TAGLIB ON)
|
||||
# TagLib
|
||||
find_package(TagLib 2.0)
|
||||
if(TARGET TagLib::TagLib)
|
||||
set(TAGLIB_FOUND ON)
|
||||
set(TAGLIB_LIBRARIES TagLib::TagLib)
|
||||
set(HAVE_TAGLIB_DSFFILE ON)
|
||||
set(HAVE_TAGLIB_DSDIFFFILE ON)
|
||||
else()
|
||||
set(HAVE_TAGLIB OFF)
|
||||
endif()
|
||||
|
||||
# TAGPARSER
|
||||
if(USE_TAGPARSER)
|
||||
pkg_check_modules(TAGPARSER REQUIRED tagparser)
|
||||
set(HAVE_TAGPARSER ON)
|
||||
else()
|
||||
set(HAVE_TAGPARSER OFF)
|
||||
pkg_check_modules(TAGLIB REQUIRED taglib>=1.12)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(LIBEBUR128 IMPORTED_TARGET libebur128)
|
||||
|
||||
if(NOT HAVE_TAGLIB AND NOT HAVE_TAGPARSER)
|
||||
message(FATAL_ERROR "You need either TagLib or TagParser!")
|
||||
endif()
|
||||
|
||||
# SingleApplication
|
||||
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication-qt${QT_VERSION_MAJOR}")
|
||||
find_package(${KDSINGLEAPPLICATION_NAME} 1.1.0)
|
||||
@@ -443,17 +416,11 @@ add_definitions(
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DUNICODE)
|
||||
if(MSVC)
|
||||
add_definitions(-DPROTOBUF_USE_DLLS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Subdirectories
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(dist)
|
||||
add_subdirectory(ext/libstrawberry-common)
|
||||
add_subdirectory(ext/libstrawberry-tagreader)
|
||||
add_subdirectory(ext/strawberry-tagreader)
|
||||
|
||||
if(GTest_FOUND AND GMOCK_LIBRARY AND Qt${QT_VERSION_MAJOR}Test_FOUND)
|
||||
add_subdirectory(tests)
|
||||
|
||||
@@ -77,7 +77,6 @@ To build Strawberry from source you need the following installed on your system
|
||||
* [GLib](https://developer.gnome.org/glib/)
|
||||
* [Qt 6.4.0 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [SQLite 3.9 or newer](https://www.sqlite.org)
|
||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
|
||||
|
||||
@@ -34,9 +34,9 @@ if(MACDEPLOYQT_EXECUTABLE)
|
||||
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
|
||||
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/strawberry.icns ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources/
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh ${CMAKE_BINARY_DIR}/strawberry.app
|
||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}
|
||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS strawberry strawberry-tagreader
|
||||
DEPENDS strawberry
|
||||
)
|
||||
if(MACDEPLOYCHECK_EXECUTABLE)
|
||||
add_custom_target(deploycheck
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -8,10 +8,8 @@ Build-Depends: debhelper (>= 11),
|
||||
cmake,
|
||||
gcc,
|
||||
g++,
|
||||
protobuf-compiler,
|
||||
libglib2.0-dev,
|
||||
libdbus-1-dev,
|
||||
libprotobuf-dev,
|
||||
libboost-dev,
|
||||
libsqlite3-dev,
|
||||
libasound2-dev,
|
||||
|
||||
8
debian/copyright
vendored
8
debian/copyright
vendored
@@ -9,10 +9,8 @@ Copyright: 2010-2015, David Sansome <me@davidsansome.com>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/utilities/timeconstants.h
|
||||
ext/libstrawberry-common/core/logging.cpp
|
||||
ext/libstrawberry-common/core/logging.h
|
||||
ext/libstrawberry-common/core/messagehandler.cpp
|
||||
ext/libstrawberry-common/core/messagehandler.h
|
||||
src/core/logging.cpp
|
||||
src/core/logging.h
|
||||
Copyright: 2011, 2012, David Sansome <me@davidsansome.com>
|
||||
2018-2022, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: Apache-2.0
|
||||
@@ -100,8 +98,6 @@ Files: src/core/main.h
|
||||
src/spotify/*
|
||||
src/transcoder/transcoderoptionswavpack.cpp
|
||||
src/transcoder/transcoderoptionswavpack.h
|
||||
ext/libstrawberry-tagreader/tagreadertagparser.cpp
|
||||
ext/libstrawberry-tagreader/tagreadertagparser.h
|
||||
src/widgets/resizabletextedit.cpp
|
||||
src/widgets/resizabletextedit.h
|
||||
src/widgets/fancytabdata.cpp
|
||||
|
||||
2
dist/CMakeLists.txt
vendored
2
dist/CMakeLists.txt
vendored
@@ -22,7 +22,7 @@ if(UNIX AND NOT APPLE)
|
||||
install(FILES ../data/icons/128x128/strawberry.png DESTINATION share/icons/hicolor/128x128/apps/)
|
||||
install(FILES unix/org.strawberrymusicplayer.strawberry.desktop DESTINATION share/applications)
|
||||
install(FILES unix/org.strawberrymusicplayer.strawberry.appdata.xml DESTINATION share/metainfo)
|
||||
install(FILES unix/strawberry.1 unix/strawberry-tagreader.1 DESTINATION share/man/man1)
|
||||
install(FILES unix/strawberry.1 DESTINATION share/man/man1)
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
if(APPLE)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<project_license>GPL-3.0+</project_license>
|
||||
<provides>
|
||||
<binary>strawberry</binary>
|
||||
<binary>strawberry-tagreader</binary>
|
||||
</provides>
|
||||
<name>Strawberry Music Player</name>
|
||||
<summary>A music player and collection organizer</summary>
|
||||
|
||||
10
dist/unix/strawberry-tagreader.1
vendored
10
dist/unix/strawberry-tagreader.1
vendored
@@ -1,10 +0,0 @@
|
||||
.TH STRAWBERRY-TAGREADER "1"
|
||||
.SH NAME
|
||||
strawberry-tagreader \- internal tag reader for strawberry
|
||||
.SH SYNOPSIS
|
||||
.B strawberry-tagreader
|
||||
.SH DESCRIPTION
|
||||
This program is used internally by Strawberry to parse tags in music files without exposing the whole application to crashes caused by malformed files. It is not meant to be run on its own.
|
||||
.SH "AUTHORS"
|
||||
.PP
|
||||
Strawberry main developer is Jonas Kvinge <jonas@jkvinge.net>.
|
||||
3
dist/unix/strawberry.spec.in
vendored
3
dist/unix/strawberry.spec.in
vendored
@@ -40,7 +40,6 @@ BuildRequires: pkgconfig(gio-unix-2.0)
|
||||
BuildRequires: pkgconfig(gthread-2.0)
|
||||
BuildRequires: pkgconfig(dbus-1)
|
||||
BuildRequires: pkgconfig(alsa)
|
||||
BuildRequires: pkgconfig(protobuf)
|
||||
BuildRequires: pkgconfig(sqlite3) >= 3.9
|
||||
BuildRequires: pkgconfig(taglib)
|
||||
BuildRequires: pkgconfig(fftw3)
|
||||
@@ -143,11 +142,9 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicpl
|
||||
%doc README.md Changelog
|
||||
%license COPYING
|
||||
%{_bindir}/strawberry
|
||||
%{_bindir}/strawberry-tagreader
|
||||
%{_datadir}/applications/*.desktop
|
||||
%{_datadir}/icons/hicolor/*/apps/strawberry.*
|
||||
%{_mandir}/man1/%{name}.1.*
|
||||
%{_mandir}/man1/%{name}-tagreader.1.*
|
||||
%if 0%{?suse_version}
|
||||
%{_datadir}/metainfo/*.appdata.xml
|
||||
%else
|
||||
|
||||
104
dist/windows/strawberry.nsi.in
vendored
104
dist/windows/strawberry.nsi.in
vendored
@@ -236,7 +236,6 @@ Section "Strawberry" Strawberry
|
||||
; Common executables
|
||||
|
||||
File "strawberry.exe"
|
||||
File "strawberry-tagreader.exe"
|
||||
File "strawberry.ico"
|
||||
File "sqlite3.exe"
|
||||
File "gst-launch-1.0.exe"
|
||||
@@ -330,7 +329,6 @@ Section "Strawberry" Strawberry
|
||||
File "libtasn1-6.dll"
|
||||
File "libtwolame-0.dll"
|
||||
File "libunistring-5.dll"
|
||||
File "libutf8_validity.dll"
|
||||
File "libvorbis-0.dll"
|
||||
File "libvorbisenc-2.dll"
|
||||
File "libvorbisfile-3.dll"
|
||||
@@ -339,48 +337,6 @@ Section "Strawberry" Strawberry
|
||||
File "libzstd.dll"
|
||||
File "zlib1.dll"
|
||||
|
||||
File "libabsl_base.dll"
|
||||
File "libabsl_city.dll"
|
||||
File "libabsl_cord.dll"
|
||||
File "libabsl_cord_internal.dll"
|
||||
File "libabsl_cordz_handle.dll"
|
||||
File "libabsl_cordz_info.dll"
|
||||
File "libabsl_crc32c.dll"
|
||||
File "libabsl_crc_cord_state.dll"
|
||||
File "libabsl_crc_internal.dll"
|
||||
File "libabsl_die_if_null.dll"
|
||||
File "libabsl_examine_stack.dll"
|
||||
File "libabsl_hash.dll"
|
||||
File "libabsl_int128.dll"
|
||||
File "libabsl_kernel_timeout_internal.dll"
|
||||
File "libabsl_log_globals.dll"
|
||||
File "libabsl_log_internal_check_op.dll"
|
||||
File "libabsl_log_internal_conditions.dll"
|
||||
File "libabsl_log_internal_format.dll"
|
||||
File "libabsl_log_internal_globals.dll"
|
||||
File "libabsl_log_internal_log_sink_set.dll"
|
||||
File "libabsl_log_internal_message.dll"
|
||||
File "libabsl_log_internal_nullguard.dll"
|
||||
File "libabsl_log_internal_proto.dll"
|
||||
File "libabsl_log_sink.dll"
|
||||
File "libabsl_low_level_hash.dll"
|
||||
File "libabsl_malloc_internal.dll"
|
||||
File "libabsl_raw_hash_set.dll"
|
||||
File "libabsl_raw_logging_internal.dll"
|
||||
File "libabsl_spinlock_wait.dll"
|
||||
File "libabsl_stacktrace.dll"
|
||||
File "libabsl_status.dll"
|
||||
File "libabsl_statusor.dll"
|
||||
File "libabsl_strerror.dll"
|
||||
File "libabsl_str_format_internal.dll"
|
||||
File "libabsl_strings.dll"
|
||||
File "libabsl_strings_internal.dll"
|
||||
File "libabsl_symbolize.dll"
|
||||
File "libabsl_synchronization.dll"
|
||||
File "libabsl_throw_delegate.dll"
|
||||
File "libabsl_time.dll"
|
||||
File "libabsl_time_zone.dll"
|
||||
|
||||
!ifdef debug
|
||||
File "gdb.exe"
|
||||
File "libexpat-1.dll"
|
||||
@@ -390,7 +346,6 @@ Section "Strawberry" Strawberry
|
||||
File "libpcre2-16d.dll"
|
||||
File "libreadline8.dll"
|
||||
File "libtermcap.dll"
|
||||
File "libabsl_graphcycles_internal.dll"
|
||||
!else
|
||||
File "libpcre2-8.dll"
|
||||
File "libpcre2-16.dll"
|
||||
@@ -412,7 +367,6 @@ Section "Strawberry" Strawberry
|
||||
!endif
|
||||
|
||||
File "FLAC.dll"
|
||||
File "abseil_dll.dll"
|
||||
File "brotlicommon.dll"
|
||||
File "brotlidec.dll"
|
||||
File "chromaprint.dll"
|
||||
@@ -466,7 +420,6 @@ Section "Strawberry" Strawberry
|
||||
File "soup-3.0-0.dll"
|
||||
File "sqlite3.dll"
|
||||
File "tag.dll"
|
||||
File "utf8_validity.dll"
|
||||
File "vorbis.dll"
|
||||
File "vorbisfile.dll"
|
||||
File "wavpackdll.dll"
|
||||
@@ -504,11 +457,6 @@ Section "Strawberry" Strawberry
|
||||
|
||||
File "icudt75.dll"
|
||||
File "libfftw3-3.dll"
|
||||
!ifdef debug
|
||||
File "libprotobufd.dll"
|
||||
!else
|
||||
File "libprotobuf.dll"
|
||||
!endif
|
||||
!ifdef msvc && debug
|
||||
File "icuin75d.dll"
|
||||
File "icuuc75d.dll"
|
||||
@@ -810,7 +758,6 @@ Section "Uninstall"
|
||||
; Delete all the files
|
||||
|
||||
Delete "$INSTDIR\strawberry.exe"
|
||||
Delete "$INSTDIR\strawberry-tagreader.exe"
|
||||
Delete "$INSTDIR\strawberry.ico"
|
||||
Delete "$INSTDIR\sqlite3.exe"
|
||||
Delete "$INSTDIR\gst-launch-1.0.exe"
|
||||
@@ -905,7 +852,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libtasn1-6.dll"
|
||||
Delete "$INSTDIR\libtwolame-0.dll"
|
||||
Delete "$INSTDIR\libunistring-5.dll"
|
||||
Delete "$INSTDIR\libutf8_validity.dll"
|
||||
Delete "$INSTDIR\libvorbis-0.dll"
|
||||
Delete "$INSTDIR\libvorbisenc-2.dll"
|
||||
Delete "$INSTDIR\libvorbisfile-3.dll"
|
||||
@@ -914,48 +860,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libzstd.dll"
|
||||
Delete "$INSTDIR\zlib1.dll"
|
||||
|
||||
Delete "$INSTDIR\libabsl_base.dll"
|
||||
Delete "$INSTDIR\libabsl_city.dll"
|
||||
Delete "$INSTDIR\libabsl_cord.dll"
|
||||
Delete "$INSTDIR\libabsl_cord_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_cordz_handle.dll"
|
||||
Delete "$INSTDIR\libabsl_cordz_info.dll"
|
||||
Delete "$INSTDIR\libabsl_crc32c.dll"
|
||||
Delete "$INSTDIR\libabsl_crc_cord_state.dll"
|
||||
Delete "$INSTDIR\libabsl_crc_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_die_if_null.dll"
|
||||
Delete "$INSTDIR\libabsl_examine_stack.dll"
|
||||
Delete "$INSTDIR\libabsl_hash.dll"
|
||||
Delete "$INSTDIR\libabsl_int128.dll"
|
||||
Delete "$INSTDIR\libabsl_kernel_timeout_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_log_globals.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_check_op.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_conditions.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_format.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_globals.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_log_sink_set.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_message.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_nullguard.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_proto.dll"
|
||||
Delete "$INSTDIR\libabsl_log_sink.dll"
|
||||
Delete "$INSTDIR\libabsl_low_level_hash.dll"
|
||||
Delete "$INSTDIR\libabsl_malloc_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_raw_hash_set.dll"
|
||||
Delete "$INSTDIR\libabsl_raw_logging_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_spinlock_wait.dll"
|
||||
Delete "$INSTDIR\libabsl_stacktrace.dll"
|
||||
Delete "$INSTDIR\libabsl_status.dll"
|
||||
Delete "$INSTDIR\libabsl_statusor.dll"
|
||||
Delete "$INSTDIR\libabsl_strerror.dll"
|
||||
Delete "$INSTDIR\libabsl_str_format_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_strings.dll"
|
||||
Delete "$INSTDIR\libabsl_strings_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_symbolize.dll"
|
||||
Delete "$INSTDIR\libabsl_synchronization.dll"
|
||||
Delete "$INSTDIR\libabsl_throw_delegate.dll"
|
||||
Delete "$INSTDIR\libabsl_time.dll"
|
||||
Delete "$INSTDIR\libabsl_time_zone.dll"
|
||||
|
||||
!ifdef debug
|
||||
Delete "$INSTDIR\gdb.exe"
|
||||
Delete "$INSTDIR\libexpat-1.dll"
|
||||
@@ -965,7 +869,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libpcre2-16d.dll"
|
||||
Delete "$INSTDIR\libreadline8.dll"
|
||||
Delete "$INSTDIR\libtermcap.dll"
|
||||
Delete "$INSTDIR\libabsl_graphcycles_internal.dll"
|
||||
!else
|
||||
Delete "$INSTDIR\libpcre2-8.dll"
|
||||
Delete "$INSTDIR\libpcre2-16.dll"
|
||||
@@ -987,7 +890,6 @@ Section "Uninstall"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\FLAC.dll"
|
||||
Delete "$INSTDIR\abseil_dll.dll"
|
||||
Delete "$INSTDIR\brotlicommon.dll"
|
||||
Delete "$INSTDIR\brotlidec.dll"
|
||||
Delete "$INSTDIR\chromaprint.dll"
|
||||
@@ -1041,7 +943,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\soup-3.0-0.dll"
|
||||
Delete "$INSTDIR\sqlite3.dll"
|
||||
Delete "$INSTDIR\tag.dll"
|
||||
Delete "$INSTDIR\utf8_validity.dll"
|
||||
Delete "$INSTDIR\vorbis.dll"
|
||||
Delete "$INSTDIR\vorbisfile.dll"
|
||||
Delete "$INSTDIR\wavpackdll.dll"
|
||||
@@ -1078,11 +979,6 @@ Section "Uninstall"
|
||||
|
||||
Delete "$INSTDIR\icudt75.dll"
|
||||
Delete "$INSTDIR\libfftw3-3.dll"
|
||||
!ifdef debug
|
||||
Delete "$INSTDIR\libprotobufd.dll"
|
||||
!else
|
||||
Delete "$INSTDIR\libprotobuf.dll"
|
||||
!endif
|
||||
!ifdef msvc && debug
|
||||
Delete "$INSTDIR\icuin75d.dll"
|
||||
Delete "$INSTDIR\icuuc75d.dll"
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(SOURCES
|
||||
core/logging.cpp
|
||||
core/messagehandler.cpp
|
||||
core/messagereply.cpp
|
||||
core/workerpool.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
core/logging.h
|
||||
core/messagehandler.h
|
||||
core/messagereply.h
|
||||
core/workerpool.h
|
||||
)
|
||||
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
|
||||
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
|
||||
|
||||
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${GLIB_INCLUDE_DIRS})
|
||||
target_include_directories(libstrawberry-common PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_BINARY_DIR}/src
|
||||
)
|
||||
|
||||
if(Backtrace_FOUND)
|
||||
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${Backtrace_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
target_link_directories(libstrawberry-common PRIVATE ${GLIB_LIBRARY_DIRS})
|
||||
|
||||
target_link_libraries(libstrawberry-common PRIVATE
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${GLIB_LIBRARIES}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
)
|
||||
|
||||
if(Backtrace_FOUND)
|
||||
target_link_libraries(libstrawberry-common PRIVATE ${Backtrace_LIBRARIES})
|
||||
endif()
|
||||
@@ -1,117 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#include "messagehandler.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QAbstractSocket>
|
||||
#include <QDataStream>
|
||||
#include <QIODevice>
|
||||
#include <QLocalSocket>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
_MessageHandlerBase::_MessageHandlerBase(QIODevice *device, QObject *parent)
|
||||
: QObject(parent),
|
||||
device_(nullptr),
|
||||
flush_abstract_socket_(nullptr),
|
||||
flush_local_socket_(nullptr),
|
||||
reading_protobuf_(false),
|
||||
expected_length_(0),
|
||||
is_device_closed_(false) {
|
||||
if (device) {
|
||||
SetDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
void _MessageHandlerBase::SetDevice(QIODevice *device) {
|
||||
|
||||
device_ = device;
|
||||
|
||||
buffer_.open(QIODevice::ReadWrite);
|
||||
|
||||
QObject::connect(device, &QIODevice::readyRead, this, &_MessageHandlerBase::DeviceReadyRead);
|
||||
|
||||
// Yeah I know.
|
||||
if (QAbstractSocket *abstractsocket = qobject_cast<QAbstractSocket*>(device)) {
|
||||
flush_abstract_socket_ = &QAbstractSocket::flush;
|
||||
QObject::connect(abstractsocket, &QAbstractSocket::disconnected, this, &_MessageHandlerBase::DeviceClosed);
|
||||
}
|
||||
else if (QLocalSocket *localsocket = qobject_cast<QLocalSocket*>(device)) {
|
||||
flush_local_socket_ = &QLocalSocket::flush;
|
||||
QObject::connect(localsocket, &QLocalSocket::disconnected, this, &_MessageHandlerBase::DeviceClosed);
|
||||
}
|
||||
else {
|
||||
qFatal("Unsupported device type passed to _MessageHandlerBase");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _MessageHandlerBase::DeviceReadyRead() {
|
||||
|
||||
while (device_->bytesAvailable() > 0) {
|
||||
if (!reading_protobuf_) {
|
||||
// Read the length of the next message
|
||||
QDataStream s(device_);
|
||||
s >> expected_length_;
|
||||
|
||||
reading_protobuf_ = true;
|
||||
}
|
||||
|
||||
// Read some of the message
|
||||
buffer_.write(device_->read(expected_length_ - buffer_.size()));
|
||||
|
||||
// Did we get everything?
|
||||
if (buffer_.size() == expected_length_) {
|
||||
// Parse the message
|
||||
if (!RawMessageArrived(buffer_.data())) {
|
||||
qLog(Error) << "Malformed protobuf message";
|
||||
device_->close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the buffer
|
||||
buffer_.close();
|
||||
buffer_.setData(QByteArray());
|
||||
buffer_.open(QIODevice::ReadWrite);
|
||||
reading_protobuf_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _MessageHandlerBase::WriteMessage(const QByteArray &data) {
|
||||
|
||||
QDataStream s(device_);
|
||||
s << static_cast<quint32>(data.length());
|
||||
s.writeRawData(data.data(), static_cast<int>(data.length()));
|
||||
|
||||
// Sorry.
|
||||
if (flush_abstract_socket_) {
|
||||
((qobject_cast<QAbstractSocket*>(device_))->*(flush_abstract_socket_))();
|
||||
}
|
||||
else if (flush_local_socket_) {
|
||||
((qobject_cast<QLocalSocket*>(device_))->*(flush_local_socket_))();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _MessageHandlerBase::DeviceClosed() {
|
||||
is_device_closed_ = true;
|
||||
AbortAll();
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef MESSAGEHANDLER_H
|
||||
#define MESSAGEHANDLER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QBuffer>
|
||||
#include <QByteArray>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QLocalSocket>
|
||||
#include <QAbstractSocket>
|
||||
|
||||
#include "core/messagereply.h"
|
||||
|
||||
class QIODevice;
|
||||
|
||||
// Reads and writes uint32 length encoded protobufs to a socket.
|
||||
// This base QObject is separate from AbstractMessageHandler because moc can't handle templated classes.
|
||||
// Use AbstractMessageHandler instead.
|
||||
class _MessageHandlerBase : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// device can be nullptr, in which case you must call SetDevice before writing any messages.
|
||||
_MessageHandlerBase(QIODevice *device, QObject *parent);
|
||||
|
||||
void SetDevice(QIODevice *device);
|
||||
|
||||
// After this is true, messages cannot be sent to the handler any more.
|
||||
bool is_device_closed() const { return is_device_closed_; }
|
||||
|
||||
protected Q_SLOTS:
|
||||
void WriteMessage(const QByteArray &data);
|
||||
void DeviceReadyRead();
|
||||
virtual void DeviceClosed();
|
||||
|
||||
protected:
|
||||
virtual bool RawMessageArrived(const QByteArray &data) = 0;
|
||||
virtual void AbortAll() = 0;
|
||||
|
||||
protected:
|
||||
typedef bool (QAbstractSocket::*FlushAbstractSocket)();
|
||||
typedef bool (QLocalSocket::*FlushLocalSocket)();
|
||||
|
||||
QIODevice *device_;
|
||||
FlushAbstractSocket flush_abstract_socket_;
|
||||
FlushLocalSocket flush_local_socket_;
|
||||
|
||||
bool reading_protobuf_;
|
||||
quint32 expected_length_;
|
||||
QBuffer buffer_;
|
||||
|
||||
bool is_device_closed_;
|
||||
};
|
||||
|
||||
// Reads and writes uint32 length encoded MessageType messages to a socket.
|
||||
// You should subclass this and implement the MessageArrived(MessageType) method.
|
||||
template<typename MT>
|
||||
class AbstractMessageHandler : public _MessageHandlerBase {
|
||||
public:
|
||||
AbstractMessageHandler(QIODevice *device, QObject *parent);
|
||||
~AbstractMessageHandler() override { AbstractMessageHandler::AbortAll(); }
|
||||
|
||||
using MessageType = MT;
|
||||
using ReplyType = MessageReply<MT>;
|
||||
|
||||
// Serialises the message and writes it to the socket.
|
||||
// This version MUST be called from the thread in which the AbstractMessageHandler was created.
|
||||
void SendMessage(const MessageType &message);
|
||||
|
||||
// Serialises the message and writes it to the socket.
|
||||
// This version may be called from any thread.
|
||||
void SendMessageAsync(const MessageType &message);
|
||||
|
||||
// Sends the request message inside and takes ownership of the MessageReply.
|
||||
// The MessageReply's Finished() signal will be emitted when a reply arrives with the same ID. Must be called from my thread.
|
||||
void SendRequest(ReplyType *reply);
|
||||
|
||||
// Sets the "id" field of reply to the same as the request, and sends the reply on the socket. Used on the worker side.
|
||||
void SendReply(const MessageType &request, MessageType *reply);
|
||||
|
||||
protected:
|
||||
// Called when a message is received from the socket.
|
||||
virtual void MessageArrived(const MessageType &message) { Q_UNUSED(message); }
|
||||
|
||||
// _MessageHandlerBase
|
||||
bool RawMessageArrived(const QByteArray &data) override;
|
||||
void AbortAll() override;
|
||||
|
||||
private:
|
||||
QMap<int, ReplyType*> pending_replies_;
|
||||
};
|
||||
|
||||
template<typename MT>
|
||||
AbstractMessageHandler<MT>::AbstractMessageHandler(QIODevice *device, QObject *parent)
|
||||
: _MessageHandlerBase(device, parent) {}
|
||||
|
||||
template<typename MT>
|
||||
void AbstractMessageHandler<MT>::SendMessage(const MessageType &message) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
const std::string data = message.SerializeAsString();
|
||||
WriteMessage(QByteArray(data.data(), data.size()));
|
||||
}
|
||||
|
||||
template<typename MT>
|
||||
void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType &message) {
|
||||
const std::string data = message.SerializeAsString();
|
||||
QMetaObject::invokeMethod(this, "WriteMessage", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray(data.data(), data.size())));
|
||||
}
|
||||
|
||||
template<typename MT>
|
||||
void AbstractMessageHandler<MT>::SendRequest(ReplyType *reply) {
|
||||
pending_replies_[reply->id()] = reply;
|
||||
SendMessage(reply->request_message());
|
||||
}
|
||||
|
||||
template<typename MT>
|
||||
void AbstractMessageHandler<MT>::SendReply(const MessageType &request, MessageType *reply) {
|
||||
reply->set_id(request.id());
|
||||
SendMessage(*reply);
|
||||
}
|
||||
|
||||
template<typename MT>
|
||||
bool AbstractMessageHandler<MT>::RawMessageArrived(const QByteArray &data) {
|
||||
|
||||
MessageType message;
|
||||
if (!message.ParseFromArray(data.constData(), data.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pending_replies_.contains(message.id())) {
|
||||
// This is a reply to a message that we created earlier.
|
||||
ReplyType *reply = pending_replies_.take(message.id());
|
||||
reply->SetReply(message);
|
||||
}
|
||||
else {
|
||||
MessageArrived(message);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
template<typename MT>
|
||||
void AbstractMessageHandler<MT>::AbortAll() {
|
||||
|
||||
for (ReplyType *reply : pending_replies_) {
|
||||
reply->Abort();
|
||||
}
|
||||
pending_replies_.clear();
|
||||
|
||||
}
|
||||
|
||||
#endif // MESSAGEHANDLER_H
|
||||
@@ -1,48 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "messagereply.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
_MessageReplyBase::_MessageReplyBase(QObject *parent)
|
||||
: QObject(parent), finished_(false), success_(false) {}
|
||||
|
||||
bool _MessageReplyBase::WaitForFinished() {
|
||||
|
||||
qLog(Debug) << "Waiting on ID" << id();
|
||||
semaphore_.acquire();
|
||||
qLog(Debug) << "Acquired ID" << id();
|
||||
return success_;
|
||||
|
||||
}
|
||||
|
||||
void _MessageReplyBase::Abort() {
|
||||
|
||||
Q_ASSERT(!finished_);
|
||||
finished_ = true;
|
||||
success_ = false;
|
||||
|
||||
Q_EMIT Finished();
|
||||
qLog(Debug) << "Releasing ID" << id() << "(aborted)";
|
||||
semaphore_.release();
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MESSAGEREPLY_H
|
||||
#define MESSAGEREPLY_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QSemaphore>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
// Base QObject for a reply future class that is returned immediately for requests that will occur in the background.
|
||||
// Similar to QNetworkReply. Use MessageReply instead.
|
||||
class _MessageReplyBase : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit _MessageReplyBase(QObject *parent = nullptr);
|
||||
|
||||
virtual int id() const = 0;
|
||||
bool is_finished() const { return finished_; }
|
||||
bool is_successful() const { return success_; }
|
||||
|
||||
// Waits for the reply to finish by waiting on a semaphore. Never call this from the MessageHandler's thread or it will block forever.
|
||||
// Returns true if the call was successful.
|
||||
bool WaitForFinished();
|
||||
|
||||
void Abort();
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished();
|
||||
|
||||
protected:
|
||||
bool finished_;
|
||||
bool success_;
|
||||
|
||||
QSemaphore semaphore_;
|
||||
};
|
||||
|
||||
// A reply future class that is returned immediately for requests that will occur in the background. Similar to QNetworkReply.
|
||||
template<typename MessageType>
|
||||
class MessageReply : public _MessageReplyBase {
|
||||
public:
|
||||
explicit MessageReply(const MessageType &request_message, QObject *parent = nullptr);
|
||||
|
||||
int id() const override { return request_message_.id(); }
|
||||
const MessageType &request_message() const { return request_message_; }
|
||||
const MessageType &message() const { return reply_message_; }
|
||||
|
||||
void SetReply(const MessageType &message);
|
||||
|
||||
private:
|
||||
MessageType request_message_;
|
||||
MessageType reply_message_;
|
||||
};
|
||||
|
||||
|
||||
template<typename MessageType>
|
||||
MessageReply<MessageType>::MessageReply(const MessageType &request_message, QObject *parent) : _MessageReplyBase(parent) {
|
||||
request_message_.MergeFrom(request_message);
|
||||
}
|
||||
|
||||
template<typename MessageType>
|
||||
void MessageReply<MessageType>::SetReply(const MessageType &message) {
|
||||
|
||||
Q_ASSERT(!finished_);
|
||||
|
||||
reply_message_.MergeFrom(message);
|
||||
finished_ = true;
|
||||
success_ = true;
|
||||
|
||||
qLog(Debug) << "Releasing ID" << id() << "(finished)";
|
||||
|
||||
// Delay the signal as workaround to fix the signal periodically not emitted.
|
||||
QTimer::singleShot(1, this, &_MessageReplyBase::Finished);
|
||||
|
||||
semaphore_.release();
|
||||
|
||||
}
|
||||
|
||||
#endif // MESSAGEREPLY_H
|
||||
@@ -1,23 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "workerpool.h"
|
||||
|
||||
_WorkerPoolBase::_WorkerPoolBase(QObject *parent) : QObject(parent) {}
|
||||
@@ -1,466 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WORKERPOOL_H
|
||||
#define WORKERPOOL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QLocalServer>
|
||||
#include <QProcess>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QAtomicInt>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
class QLocalSocket;
|
||||
|
||||
// Base class containing signals and slots - required because moc doesn't do templated objects.
|
||||
class _WorkerPoolBase : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit _WorkerPoolBase(QObject *parent = nullptr);
|
||||
|
||||
Q_SIGNALS:
|
||||
// Emitted when a worker failed to start. This usually happens when the worker wasn't found, or couldn't be executed.
|
||||
void WorkerFailedToStart();
|
||||
|
||||
protected Q_SLOTS:
|
||||
virtual void DoStart() {}
|
||||
virtual void NewConnection() {}
|
||||
virtual void ProcessReadyReadStandardOutput() {}
|
||||
virtual void ProcessReadyReadStandardError() {}
|
||||
virtual void ProcessError(QProcess::ProcessError) {}
|
||||
virtual void SendQueuedMessages() {}
|
||||
};
|
||||
|
||||
|
||||
// Manages a pool of one or more external processes.
|
||||
// A local socket server is started for each process, and the address is passed to the process as argv[1].
|
||||
// The process is expected to connect back to the socket server, and when it does a HandlerType is created for it.
|
||||
// Instances of HandlerType are created in the WorkerPool's thread.
|
||||
template<typename HandlerType>
|
||||
class WorkerPool : public _WorkerPoolBase {
|
||||
public:
|
||||
explicit WorkerPool(QObject *parent = nullptr);
|
||||
~WorkerPool() override;
|
||||
|
||||
using MessageType = typename HandlerType::MessageType;
|
||||
using ReplyType = typename HandlerType::ReplyType;
|
||||
|
||||
// Sets the name of the worker executable. This is looked for first in the current directory, and then in $PATH.
|
||||
// You must call this before calling Start().
|
||||
void SetExecutableName(const QString &executable_name);
|
||||
|
||||
// Sets the number of worker process to use. Defaults to 1 <= (processors / 2) <= 2.
|
||||
void SetWorkerCount(const int count);
|
||||
|
||||
// Sets the prefix to use for the local server (on unix this is a named pipe in /tmp).
|
||||
// Defaults to QApplication::applicationName().
|
||||
// A random number is appended to this name when creating each server.
|
||||
void SetLocalServerName(const QString &local_server_name);
|
||||
|
||||
// Starts all workers.
|
||||
void Start();
|
||||
|
||||
// Fills in the message's "id" field and creates a reply future.
|
||||
// The message is queued and the WorkerPool's thread will send it to the next available worker.
|
||||
// Can be called from any thread.
|
||||
ReplyType *SendMessageWithReply(MessageType *message);
|
||||
|
||||
protected:
|
||||
// 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;
|
||||
|
||||
private:
|
||||
struct Worker {
|
||||
Worker() : local_server_(nullptr), local_socket_(nullptr), process_(nullptr), handler_(nullptr) {}
|
||||
|
||||
QLocalServer *local_server_;
|
||||
QLocalSocket *local_socket_;
|
||||
QProcess *process_;
|
||||
HandlerType *handler_;
|
||||
};
|
||||
|
||||
// Must only ever be called on my thread.
|
||||
void StartOneWorker(Worker *worker);
|
||||
|
||||
template<typename T>
|
||||
Worker *FindWorker(T Worker::*member, T value) {
|
||||
for (typename QList<Worker>::iterator it = workers_.begin(); it != workers_.end(); ++it) {
|
||||
if ((*it).*member == value) {
|
||||
return &(*it);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void DeleteQObjectPointerLater(T **p) {
|
||||
if (*p) {
|
||||
(*p)->deleteLater();
|
||||
*p = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new reply future for the request with the next sequential ID,
|
||||
// and sets the request's ID to the ID of the reply. Can be called from any thread
|
||||
ReplyType *NewReply(MessageType *message);
|
||||
|
||||
// Returns the next handler, or nullptr if there isn't one. Must be called from my thread.
|
||||
HandlerType *NextHandler() const;
|
||||
|
||||
private:
|
||||
QString local_server_name_;
|
||||
QString executable_name_;
|
||||
QString executable_path_;
|
||||
|
||||
int worker_count_;
|
||||
mutable int next_worker_;
|
||||
QList<Worker> workers_;
|
||||
|
||||
QAtomicInt next_id_;
|
||||
|
||||
QMutex message_queue_mutex_;
|
||||
QQueue<ReplyType *> message_queue_;
|
||||
};
|
||||
|
||||
|
||||
template<typename HandlerType>
|
||||
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
||||
: _WorkerPoolBase(parent),
|
||||
worker_count_(1),
|
||||
next_worker_(0),
|
||||
next_id_(0) {
|
||||
|
||||
local_server_name_ = qApp->applicationName().toLower();
|
||||
|
||||
if (local_server_name_.isEmpty()) {
|
||||
local_server_name_ = QStringLiteral("workerpool");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
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";
|
||||
worker.local_socket_->close();
|
||||
worker.process_->waitForFinished(500);
|
||||
}
|
||||
|
||||
if (worker.process_ && worker.process_->state() == QProcess::Running) {
|
||||
// The worker is still running - kill it.
|
||||
qLog(Debug) << "Killing worker process";
|
||||
worker.process_->terminate();
|
||||
if (!worker.process_->waitForFinished(500)) {
|
||||
worker.process_->kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ReplyType *reply : message_queue_) {
|
||||
reply->Abort();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::SetWorkerCount(const int count) {
|
||||
Q_ASSERT(workers_.isEmpty());
|
||||
worker_count_ = count;
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::SetLocalServerName(const QString &local_server_name) {
|
||||
Q_ASSERT(workers_.isEmpty());
|
||||
local_server_name_ = local_server_name;
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::SetExecutableName(const QString &executable_name) {
|
||||
Q_ASSERT(workers_.isEmpty());
|
||||
executable_name_ = executable_name;
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::Start() {
|
||||
QMetaObject::invokeMethod(this, &WorkerPool<HandlerType>::DoStart);
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::DoStart() {
|
||||
|
||||
Q_ASSERT(workers_.isEmpty());
|
||||
Q_ASSERT(!executable_name_.isEmpty());
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
// Find the executable if we can, default to searching $PATH
|
||||
executable_path_ = executable_name_;
|
||||
|
||||
QStringList search_path;
|
||||
search_path << QCoreApplication::applicationDirPath();
|
||||
#if defined(Q_OS_UNIX)
|
||||
search_path << QStringLiteral("/usr/libexec");
|
||||
search_path << QStringLiteral("/usr/local/libexec");
|
||||
#endif
|
||||
#if defined(Q_OS_MACOS)
|
||||
search_path << QDir::cleanPath(QCoreApplication::applicationDirPath() + QStringLiteral("/../PlugIns"));
|
||||
#endif
|
||||
|
||||
for (const QString &path_prefix : std::as_const(search_path)) {
|
||||
const QString executable_path = path_prefix + QLatin1Char('/') + executable_name_;
|
||||
if (QFile::exists(executable_path)) {
|
||||
executable_path_ = executable_path;
|
||||
qLog(Debug) << "Using worker" << executable_name_ << "from" << path_prefix;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (executable_path_ == executable_name_) {
|
||||
qLog(Debug) << "Using worker" << executable_name_;
|
||||
}
|
||||
|
||||
// Start all the workers
|
||||
for (int i = 0; i < worker_count_; ++i) {
|
||||
Worker worker;
|
||||
StartOneWorker(&worker);
|
||||
|
||||
workers_ << worker;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
DeleteQObjectPointerLater(&worker->local_server_);
|
||||
DeleteQObjectPointerLater(&worker->local_socket_);
|
||||
DeleteQObjectPointerLater(&worker->process_);
|
||||
DeleteQObjectPointerLater(&worker->handler_);
|
||||
|
||||
worker->local_server_ = new QLocalServer(this);
|
||||
worker->process_ = new QProcess(this);
|
||||
|
||||
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
|
||||
Q_FOREVER {
|
||||
const quint32 unique_number = QRandomGenerator::global()->bounded(static_cast<quint32>(quint64(this) & 0xFFFFFFFF));
|
||||
const QString name = QStringLiteral("%1_%2").arg(local_server_name_).arg(unique_number);
|
||||
|
||||
if (worker->local_server_->listen(name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qLog(Debug) << "Starting worker" << worker << executable_path_ << worker->local_server_->fullServerName();
|
||||
|
||||
#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());
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::NewConnection() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
QLocalServer *server = qobject_cast<QLocalServer*>(sender());
|
||||
|
||||
// Find the worker with this server.
|
||||
Worker *worker = FindWorker(&Worker::local_server_, server);
|
||||
if (!worker) return;
|
||||
|
||||
qLog(Debug) << "Worker" << worker << "connected to" << server->fullServerName();
|
||||
|
||||
// Accept the connection.
|
||||
worker->local_socket_ = server->nextPendingConnection();
|
||||
|
||||
// We only ever accept one connection per worker, so destroy the server now.
|
||||
worker->local_socket_->setParent(this);
|
||||
worker->local_server_->deleteLater();
|
||||
worker->local_server_ = nullptr;
|
||||
|
||||
// Create the handler.
|
||||
worker->handler_ = new HandlerType(worker->local_socket_, this);
|
||||
|
||||
SendQueuedMessages();
|
||||
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
QProcess *process = qobject_cast<QProcess*>(sender());
|
||||
|
||||
// Find the worker with this process.
|
||||
Worker *worker = FindWorker(&Worker::process_, process);
|
||||
if (!worker) return;
|
||||
|
||||
switch (error) {
|
||||
case QProcess::FailedToStart:
|
||||
// Failed to start errors are bad - it usually means the worker isn't installed.
|
||||
// Don't restart the process, but tell our owner, who will probably want to do something fatal.
|
||||
qLog(Error) << "Worker failed to start";
|
||||
Q_EMIT WorkerFailedToStart();
|
||||
break;
|
||||
|
||||
default:
|
||||
// On any other error we just restart the process.
|
||||
qLog(Debug) << "Worker" << worker << "failed with error" << error << "- restarting";
|
||||
StartOneWorker(worker);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
const int id = next_id_.fetchAndAddOrdered(1);
|
||||
message->set_id(id);
|
||||
|
||||
return new ReplyType(*message);
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
typename WorkerPool<HandlerType>::ReplyType*
|
||||
WorkerPool<HandlerType>::SendMessageWithReply(MessageType *message) {
|
||||
|
||||
ReplyType *reply = NewReply(message);
|
||||
|
||||
// Add the pending reply to the queue
|
||||
{
|
||||
QMutexLocker l(&message_queue_mutex_);
|
||||
message_queue_.enqueue(reply);
|
||||
}
|
||||
|
||||
// Wake up the main thread
|
||||
QMetaObject::invokeMethod(this, &WorkerPool<HandlerType>::SendQueuedMessages, Qt::QueuedConnection);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::SendQueuedMessages() {
|
||||
|
||||
QMutexLocker l(&message_queue_mutex_);
|
||||
|
||||
while (!message_queue_.isEmpty()) {
|
||||
ReplyType *reply = message_queue_.dequeue();
|
||||
|
||||
// Find a worker for this message
|
||||
HandlerType *handler = NextHandler();
|
||||
if (!handler) {
|
||||
// No available handlers - put the message on the front of the queue.
|
||||
message_queue_.prepend(reply);
|
||||
qLog(Debug) << "No available handlers to process request";
|
||||
break;
|
||||
}
|
||||
|
||||
handler->SendRequest(reply);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
HandlerType *WorkerPool<HandlerType>::NextHandler() const {
|
||||
|
||||
for (int i = 0; i < workers_.count(); ++i) {
|
||||
const int worker_index = (next_worker_ + i) % workers_.count();
|
||||
|
||||
if (workers_[worker_index].handler_ && !workers_[worker_index].handler_->is_device_closed()) {
|
||||
next_worker_ = (worker_index + 1) % workers_.count();
|
||||
return workers_[worker_index].handler_;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
}
|
||||
|
||||
#endif // WORKERPOOL_H
|
||||
@@ -1,67 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
# Workaround a bug in protobuf-generate.cmake (https://github.com/protocolbuffers/protobuf/issues/12450)
|
||||
if(NOT protobuf_PROTOC_EXE)
|
||||
set(protobuf_PROTOC_EXE "protobuf::protoc")
|
||||
endif()
|
||||
|
||||
if(NOT Protobuf_LIBRARIES)
|
||||
set(Protobuf_LIBRARIES protobuf::libprotobuf)
|
||||
endif()
|
||||
|
||||
set(SOURCES tagreaderbase.cpp tagreadermessages.proto)
|
||||
|
||||
if(HAVE_TAGLIB)
|
||||
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGPARSER)
|
||||
list(APPEND SOURCES tagreadertagparser.cpp)
|
||||
endif()
|
||||
|
||||
add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
|
||||
|
||||
if(NOT MSVC)
|
||||
target_compile_options(libstrawberry-tagreader PRIVATE -Wno-missing-declarations)
|
||||
endif()
|
||||
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
${PROTOBUF_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_include_directories(libstrawberry-tagreader PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_BINARY_DIR}/src
|
||||
)
|
||||
|
||||
target_link_directories(libstrawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
${Protobuf_LIBRARIES}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
libstrawberry-common
|
||||
)
|
||||
|
||||
if(HAVE_TAGLIB)
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||
target_link_directories(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARY_DIRS})
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGPARSER)
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||
target_link_directories(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARY_DIRS})
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||
endif()
|
||||
|
||||
protobuf_generate(TARGET libstrawberry-tagreader)
|
||||
@@ -1,173 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
#include <QBuffer>
|
||||
#include <QImage>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderbase.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
TagReaderBase::TagReaderBase() = default;
|
||||
TagReaderBase::~TagReaderBase() = default;
|
||||
|
||||
QString TagReaderBase::ErrorString(const Result &result) {
|
||||
|
||||
switch (result.error_code) {
|
||||
case Result::ErrorCode::Success:
|
||||
return QObject::tr("Success");
|
||||
case Result::ErrorCode::Unsupported:
|
||||
return QObject::tr("File is unsupported");
|
||||
case Result::ErrorCode::FilenameMissing:
|
||||
return QObject::tr("Filename is missing");
|
||||
case Result::ErrorCode::FileDoesNotExist:
|
||||
return QObject::tr("File does not exist");
|
||||
case Result::ErrorCode::FileOpenError:
|
||||
return QObject::tr("File could not be opened");
|
||||
case Result::ErrorCode::FileParseError:
|
||||
return QObject::tr("Could not parse file");
|
||||
case Result::ErrorCode::FileSaveError:
|
||||
return QObject::tr("Could save file");
|
||||
case Result::ErrorCode::CustomError:
|
||||
return result.error;
|
||||
}
|
||||
|
||||
return QObject::tr("Unknown error");
|
||||
|
||||
}
|
||||
|
||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||
|
||||
if (POPM_rating < 0x01) return 0.0F;
|
||||
if (POPM_rating < 0x40) return 0.20F;
|
||||
if (POPM_rating < 0x80) return 0.40F;
|
||||
if (POPM_rating < 0xC0) return 0.60F;
|
||||
if (POPM_rating < 0xFC) return 0.80F;
|
||||
|
||||
return 1.0F;
|
||||
|
||||
}
|
||||
|
||||
int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
||||
|
||||
if (rating < 0.20) return 0x00;
|
||||
if (rating < 0.40) return 0x01;
|
||||
if (rating < 0.60) return 0x40;
|
||||
if (rating < 0.80) return 0x80;
|
||||
if (rating < 1.0) return 0xC0;
|
||||
|
||||
return 0xFF;
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::WriteFileRequest &request) {
|
||||
|
||||
if (!request.has_save_cover() || !request.save_cover()) {
|
||||
return Cover();
|
||||
}
|
||||
|
||||
QString cover_filename;
|
||||
if (request.has_cover_filename()) {
|
||||
cover_filename = QString::fromStdString(request.cover_filename());
|
||||
}
|
||||
QByteArray cover_data;
|
||||
if (request.has_cover_data()) {
|
||||
cover_data = QByteArray(request.cover_data().data(), static_cast<qint64>(request.cover_data().size()));
|
||||
}
|
||||
QString cover_mime_type;
|
||||
if (request.has_cover_mime_type()) {
|
||||
cover_mime_type = QString::fromStdString(request.cover_mime_type());
|
||||
}
|
||||
|
||||
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::SaveEmbeddedArtRequest &request) {
|
||||
|
||||
QString cover_filename;
|
||||
if (request.has_cover_filename()) {
|
||||
cover_filename = QString::fromStdString(request.cover_filename());
|
||||
}
|
||||
QByteArray cover_data;
|
||||
if (request.has_cover_data()) {
|
||||
cover_data = QByteArray(request.cover_data().data(), static_cast<qint64>(request.cover_data().size()));
|
||||
}
|
||||
QString cover_mime_type;
|
||||
if (request.has_cover_mime_type()) {
|
||||
cover_mime_type = QString::fromStdString(request.cover_mime_type());
|
||||
}
|
||||
|
||||
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type) {
|
||||
|
||||
if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
|
||||
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
|
||||
QFile file(cover_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
|
||||
return Cover();
|
||||
}
|
||||
cover_data = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (!cover_data.isEmpty()) {
|
||||
if (cover_mime_type.isEmpty()) {
|
||||
cover_mime_type = QMimeDatabase().mimeTypeForData(cover_data).name();
|
||||
}
|
||||
if (cover_mime_type == "image/jpeg"_L1) {
|
||||
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||
return Cover(cover_data, cover_mime_type);
|
||||
}
|
||||
if (cover_mime_type == "image/png"_L1) {
|
||||
qLog(Debug) << "Using cover from PNG data for" << song_filename;
|
||||
return Cover(cover_data, cover_mime_type);
|
||||
}
|
||||
// Convert image to JPEG.
|
||||
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
|
||||
QImage cover_image;
|
||||
if (!cover_image.loadFromData(cover_data)) {
|
||||
qLog(Error) << "Failed to load image from cover data for" << song_filename;
|
||||
return Cover();
|
||||
}
|
||||
cover_data.clear();
|
||||
QBuffer buffer(&cover_data);
|
||||
if (buffer.open(QIODevice::WriteOnly)) {
|
||||
cover_image.save(&buffer, "JPEG");
|
||||
buffer.close();
|
||||
}
|
||||
return Cover(cover_data, QStringLiteral("image/jpeg"));
|
||||
}
|
||||
|
||||
return Cover();
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERBASE_H
|
||||
#define TAGREADERBASE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
/*
|
||||
* This class holds all useful methods to read and write tags from/to files.
|
||||
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||
*/
|
||||
class TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderBase();
|
||||
virtual ~TagReaderBase();
|
||||
|
||||
class Result {
|
||||
public:
|
||||
enum class ErrorCode {
|
||||
Success,
|
||||
Unsupported,
|
||||
FilenameMissing,
|
||||
FileDoesNotExist,
|
||||
FileOpenError,
|
||||
FileParseError,
|
||||
FileSaveError,
|
||||
CustomError,
|
||||
};
|
||||
Result(const ErrorCode _error_code, const QString &_error = QString()) : error_code(_error_code), error(_error) {}
|
||||
ErrorCode error_code;
|
||||
QString error;
|
||||
bool success() const { return error_code == ErrorCode::Success; }
|
||||
};
|
||||
|
||||
class Cover {
|
||||
public:
|
||||
explicit Cover(const QByteArray &_data = QByteArray(), const QString &_mime_type = QString()) : data(_data), mime_type(_mime_type) {}
|
||||
QByteArray data;
|
||||
QString mime_type;
|
||||
QString error;
|
||||
};
|
||||
|
||||
static QString ErrorString(const Result &result);
|
||||
|
||||
virtual bool IsMediaFile(const QString &filename) const = 0;
|
||||
|
||||
virtual Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
||||
virtual Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const = 0;
|
||||
|
||||
virtual Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const = 0;
|
||||
virtual Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const = 0;
|
||||
|
||||
virtual Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const = 0;
|
||||
virtual Result SaveSongRatingToFile(const QString &filename, const float rating) const = 0;
|
||||
|
||||
protected:
|
||||
static float ConvertPOPMRating(const int POPM_rating);
|
||||
static int ConvertToPOPMRating(const float rating);
|
||||
|
||||
static Cover LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::WriteFileRequest &request);
|
||||
static Cover LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::SaveEmbeddedArtRequest &request);
|
||||
|
||||
private:
|
||||
static Cover LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type);
|
||||
|
||||
Q_DISABLE_COPY(TagReaderBase)
|
||||
};
|
||||
|
||||
#endif // TAGREADERBASE_H
|
||||
@@ -1,195 +0,0 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package spb.tagreader;
|
||||
|
||||
message SongMetadata {
|
||||
|
||||
enum FileType {
|
||||
UNKNOWN = 0;
|
||||
WAV = 1;
|
||||
FLAC = 2;
|
||||
WAVPACK = 3;
|
||||
OGGFLAC = 4;
|
||||
OGGVORBIS = 5;
|
||||
OGGOPUS = 6;
|
||||
OGGSPEEX = 7;
|
||||
MPEG = 8;
|
||||
MP4 = 9;
|
||||
ASF = 10;
|
||||
AIFF = 11;
|
||||
MPC = 12;
|
||||
TRUEAUDIO = 13;
|
||||
DSF = 14;
|
||||
DSDIFF = 15;
|
||||
PCM = 16;
|
||||
APE = 17;
|
||||
MOD = 18;
|
||||
S3M = 19;
|
||||
XM = 20;
|
||||
IT = 21;
|
||||
SPC = 22;
|
||||
VGM = 23;
|
||||
CDDA = 90;
|
||||
STREAM = 91;
|
||||
}
|
||||
|
||||
optional bool valid = 1;
|
||||
|
||||
optional string title = 2;
|
||||
optional string album = 3;
|
||||
optional string artist = 4;
|
||||
optional string albumartist = 5;
|
||||
optional int32 track = 6;
|
||||
optional int32 disc = 7;
|
||||
optional int32 year = 8;
|
||||
optional int32 originalyear = 9;
|
||||
optional string genre = 10;
|
||||
optional bool compilation = 11;
|
||||
optional string composer = 12;
|
||||
optional string performer = 13;
|
||||
optional string grouping = 14;
|
||||
optional string comment = 15;
|
||||
optional string lyrics = 16;
|
||||
|
||||
optional uint64 length_nanosec = 17;
|
||||
|
||||
optional int32 bitrate = 18;
|
||||
optional int32 samplerate = 19;
|
||||
optional int32 bitdepth = 20;
|
||||
|
||||
optional string url = 21;
|
||||
optional string basefilename = 22;
|
||||
optional FileType filetype = 23;
|
||||
optional int64 filesize = 24;
|
||||
optional int64 mtime = 25;
|
||||
optional int64 ctime = 26;
|
||||
|
||||
optional uint32 playcount = 27;
|
||||
optional uint32 skipcount = 28;
|
||||
optional int64 lastplayed = 29;
|
||||
optional int64 lastseen = 30;
|
||||
|
||||
optional bool art_embedded = 31;
|
||||
|
||||
optional float rating = 32;
|
||||
|
||||
optional string acoustid_id = 33;
|
||||
optional string acoustid_fingerprint = 34;
|
||||
|
||||
optional string musicbrainz_album_artist_id = 35; // MusicBrainz Release Artist ID (MUSICBRAINZ_ALBUMARTISTID)
|
||||
optional string musicbrainz_artist_id = 36; // MusicBrainz Artist ID (MUSICBRAINZ_ARTISTID)
|
||||
optional string musicbrainz_original_artist_id = 37; // MusicBrainz Original Artist ID (MUSICBRAINZ_ORIGINALARTISTID)
|
||||
optional string musicbrainz_album_id = 38; // MusicBrainz Release ID (MUSICBRAINZ_ALBUMID)
|
||||
optional string musicbrainz_original_album_id = 39; // MusicBrainz Original Release ID (MUSICBRAINZ_ORIGINALALBUMID)
|
||||
optional string musicbrainz_recording_id = 40; // MusicBrainz Recording ID (MUSICBRAINZ_TRACKID)
|
||||
optional string musicbrainz_track_id = 41; // MusicBrainz Track ID (MUSICBRAINZ_RELEASETRACKID)
|
||||
optional string musicbrainz_disc_id = 42; // MusicBrainz Disc ID (MUSICBRAINZ_DISCID)
|
||||
optional string musicbrainz_release_group_id = 43; // MusicBrainz Release Group ID (MUSICBRAINZ_RELEASEGROUPID)
|
||||
optional string musicbrainz_work_id = 44; // MusicBrainz Work ID (MUSICBRAINZ_WORKID)
|
||||
|
||||
optional bool suspicious_tags = 50;
|
||||
|
||||
}
|
||||
|
||||
message IsMediaFileRequest {
|
||||
optional string filename = 1;
|
||||
}
|
||||
|
||||
message IsMediaFileResponse {
|
||||
optional bool success = 1;
|
||||
}
|
||||
|
||||
message ReadFileRequest {
|
||||
optional string filename = 1;
|
||||
}
|
||||
|
||||
message ReadFileResponse {
|
||||
optional bool success = 1;
|
||||
optional SongMetadata metadata = 2;
|
||||
optional string error = 3;
|
||||
}
|
||||
|
||||
message WriteFileRequest {
|
||||
optional string filename = 1;
|
||||
optional bool save_tags = 2;
|
||||
optional bool save_playcount = 3;
|
||||
optional bool save_rating = 4;
|
||||
optional bool save_cover = 5;
|
||||
optional SongMetadata metadata = 6;
|
||||
optional string cover_filename = 7;
|
||||
optional bytes cover_data = 8;
|
||||
optional string cover_mime_type = 9;
|
||||
}
|
||||
|
||||
message WriteFileResponse {
|
||||
optional bool success = 1;
|
||||
optional string error = 2;
|
||||
}
|
||||
|
||||
message LoadEmbeddedArtRequest {
|
||||
optional string filename = 1;
|
||||
}
|
||||
|
||||
message LoadEmbeddedArtResponse {
|
||||
optional bool success = 1;
|
||||
optional bytes data = 2;
|
||||
optional string error = 3;
|
||||
}
|
||||
|
||||
message SaveEmbeddedArtRequest {
|
||||
optional string filename = 1;
|
||||
optional string cover_filename = 2;
|
||||
optional bytes cover_data = 3;
|
||||
optional string cover_mime_type = 4;
|
||||
}
|
||||
|
||||
message SaveEmbeddedArtResponse {
|
||||
optional bool success = 1;
|
||||
optional string error = 2;
|
||||
}
|
||||
|
||||
message SaveSongPlaycountToFileRequest {
|
||||
optional string filename = 1;
|
||||
optional uint32 playcount = 2;
|
||||
}
|
||||
|
||||
message SaveSongPlaycountToFileResponse {
|
||||
optional bool success = 1;
|
||||
optional string error = 2;
|
||||
}
|
||||
|
||||
message SaveSongRatingToFileRequest {
|
||||
optional string filename = 1;
|
||||
optional float rating = 2;
|
||||
}
|
||||
|
||||
message SaveSongRatingToFileResponse {
|
||||
optional bool success = 1;
|
||||
optional string error = 2;
|
||||
}
|
||||
|
||||
message Message {
|
||||
optional int32 id = 1;
|
||||
|
||||
optional ReadFileRequest read_file_request = 2;
|
||||
optional ReadFileResponse read_file_response = 3;
|
||||
|
||||
optional WriteFileRequest write_file_request = 4;
|
||||
optional WriteFileResponse write_file_response = 5;
|
||||
|
||||
optional IsMediaFileRequest is_media_file_request = 6;
|
||||
optional IsMediaFileResponse is_media_file_response = 7;
|
||||
|
||||
optional LoadEmbeddedArtRequest load_embedded_art_request = 8;
|
||||
optional LoadEmbeddedArtResponse load_embedded_art_response = 9;
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2013, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERTAGLIB_H
|
||||
#define TAGREADERTAGLIB_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include <taglib/tstring.h>
|
||||
#include <taglib/fileref.h>
|
||||
#include <taglib/xiphcomment.h>
|
||||
#include <taglib/flacfile.h>
|
||||
#include <taglib/mpegfile.h>
|
||||
#include <taglib/mp4file.h>
|
||||
#include <taglib/apetag.h>
|
||||
#include <taglib/apefile.h>
|
||||
#include <taglib/asffile.h>
|
||||
#include <taglib/id3v2tag.h>
|
||||
#include <taglib/popularimeterframe.h>
|
||||
#include <taglib/mp4tag.h>
|
||||
#include <taglib/asftag.h>
|
||||
|
||||
#include "tagreaderbase.h"
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
#undef TStringToQString
|
||||
#undef QStringToTString
|
||||
|
||||
class FileRefFactory;
|
||||
|
||||
/*
|
||||
* This class holds all useful methods to read and write tags from/to files.
|
||||
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||
*/
|
||||
class TagReaderTagLib : public TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderTagLib();
|
||||
~TagReaderTagLib() override;
|
||||
|
||||
static inline TagLib::String StdStringToTagLibString(const std::string &s) {
|
||||
return TagLib::String(s.c_str(), TagLib::String::UTF8);
|
||||
}
|
||||
|
||||
static inline std::string TagLibStringToStdString(const TagLib::String &s) {
|
||||
return std::string(s.toCString(true), s.length());
|
||||
}
|
||||
|
||||
static inline TagLib::String QStringToTagLibString(const QString &s) {
|
||||
return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8);
|
||||
}
|
||||
|
||||
static inline QString TagLibStringToQString(const TagLib::String &s) {
|
||||
return QString::fromUtf8((s).toCString(true));
|
||||
}
|
||||
|
||||
static inline void AssignTagLibStringToStdString(const TagLib::String &tstr, std::string *output) {
|
||||
|
||||
const QString qstr = TagLibStringToQString(tstr).trimmed();
|
||||
const QByteArray data = qstr.toUtf8();
|
||||
output->assign(data.constData(), data.size());
|
||||
|
||||
}
|
||||
|
||||
bool IsMediaFile(const QString &filename) const override;
|
||||
|
||||
Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||
Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const override;
|
||||
|
||||
Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override;
|
||||
Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||
|
||||
Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override;
|
||||
Result SaveSongRatingToFile(const QString &filename, const float rating) const override;
|
||||
|
||||
private:
|
||||
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||
|
||||
void ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||
void ParseVorbisComments(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||
void ParseAPETags(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||
void ParseMP4Tags(TagLib::MP4::Tag *tag, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||
void ParseASFTags(TagLib::ASF::Tag *tag, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||
void ParseASFAttribute(const TagLib::ASF::AttributeListMap &attributes_map, const char *attribute, std::string *str) const;
|
||||
|
||||
void SetID3v2Tag(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetTextFrame(const char *id, const 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;
|
||||
|
||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetASFTag(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const std::string &value) const;
|
||||
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const int value) const;
|
||||
|
||||
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
|
||||
|
||||
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
||||
|
||||
void SetPlaycount(TagLib::Ogg::XiphComment *vorbis_comment, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::APE::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::ID3v2::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::MP4::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::ASF::Tag *tag, const uint playcount) const;
|
||||
|
||||
void SetRating(TagLib::Ogg::XiphComment *vorbis_comment, const float rating) const;
|
||||
void SetRating(TagLib::APE::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::ID3v2::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::MP4::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::ASF::Tag *tag, const float rating) const;
|
||||
|
||||
void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mime_type) const;
|
||||
void SetEmbeddedArt(TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mime_type) const;
|
||||
void SetEmbeddedArt(TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
||||
void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
||||
|
||||
static TagLib::String TagLibStringListToSlashSeparatedString(const TagLib::StringList &taglib_string_list);
|
||||
|
||||
private:
|
||||
FileRefFactory *factory_;
|
||||
|
||||
Q_DISABLE_COPY(TagReaderTagLib)
|
||||
};
|
||||
|
||||
#endif // TAGREADERTAGLIB_H
|
||||
@@ -1,555 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2021-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "tagreadertagparser.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <tagparser/mediafileinfo.h>
|
||||
#include <tagparser/diagnostics.h>
|
||||
#include <tagparser/progressfeedback.h>
|
||||
#include <tagparser/tag.h>
|
||||
#include <tagparser/abstracttrack.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QDateTime>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/messagehandler.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
|
||||
TagReaderTagParser::TagReaderTagParser() = default;
|
||||
|
||||
bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
||||
|
||||
qLog(Debug) << "Checking for valid file" << filename;
|
||||
|
||||
QFileInfo fileinfo(filename);
|
||||
if (!fileinfo.exists() || fileinfo.suffix().compare(QLatin1String("bak"), Qt::CaseInsensitive) == 0) return false;
|
||||
|
||||
try {
|
||||
TagParser::MediaFileInfo taginfo;
|
||||
TagParser::Diagnostics diag;
|
||||
TagParser::AbortableProgressFeedback progress;
|
||||
|
||||
taginfo.setPath(QFile::encodeName(filename).toStdString());
|
||||
taginfo.open(true);
|
||||
|
||||
taginfo.parseContainerFormat(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
taginfo.parseTracks(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const TagParser::DiagMessage &msg : diag) {
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
const auto tracks = taginfo.tracks();
|
||||
for (TagParser::AbstractTrack *track : tracks) {
|
||||
if (track->mediaType() == TagParser::MediaType::Audio) {
|
||||
taginfo.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
taginfo.close();
|
||||
}
|
||||
catch(...) {}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
||||
|
||||
qLog(Debug) << "Reading tags from" << filename;
|
||||
|
||||
const QFileInfo fileinfo(filename);
|
||||
|
||||
if (!fileinfo.exists() || fileinfo.suffix().compare(QLatin1String("bak"), Qt::CaseInsensitive) == 0) return Result::ErrorCode::FileParseError;
|
||||
|
||||
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
||||
const QByteArray basefilename = fileinfo.fileName().toUtf8();
|
||||
|
||||
song->set_basefilename(basefilename.constData(), basefilename.size());
|
||||
song->set_url(url.constData(), url.size());
|
||||
song->set_filesize(fileinfo.size());
|
||||
|
||||
song->set_mtime(fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
||||
song->set_ctime(fileinfo.birthTime().isValid() ? std::max(fileinfo.birthTime().toSecsSinceEpoch(), 0LL) : fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
||||
|
||||
if (song->ctime() <= 0) {
|
||||
song->set_ctime(song->mtime());
|
||||
}
|
||||
|
||||
song->set_lastseen(QDateTime::currentDateTime().toSecsSinceEpoch());
|
||||
|
||||
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(true);
|
||||
|
||||
taginfo.parseContainerFormat(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
taginfo.parseTracks(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
taginfo.parseTags(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
for (const TagParser::DiagMessage &msg : diag) {
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
std::vector<TagParser::AbstractTrack*> tracks = taginfo.tracks();
|
||||
for (TagParser::AbstractTrack *track : tracks) {
|
||||
switch (track->format().general) {
|
||||
case TagParser::GeneralMediaFormat::Flac:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_FLAC);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::WavPack:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_WAVPACK);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::MonkeysAudio:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_APE);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::WindowsMediaAudio:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_ASF);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::Vorbis:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGVORBIS);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::Opus:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGOPUS);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::Speex:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGSPEEX);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::Mpeg1Audio:
|
||||
switch (track->format().sub) {
|
||||
case TagParser::SubFormats::Mpeg1Layer3:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_MPEG);
|
||||
break;
|
||||
case TagParser::SubFormats::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::Mpc:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_MPC);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::Pcm:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_PCM);
|
||||
break;
|
||||
case TagParser::GeneralMediaFormat::Unknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
song->set_length_nanosec(track->duration().totalMilliseconds() * kNsecPerMsec);
|
||||
song->set_samplerate(track->samplingFrequency());
|
||||
song->set_bitdepth(track->bitsPerSample());
|
||||
song->set_bitrate(std::max(track->bitrate(), track->maxBitrate()));
|
||||
}
|
||||
|
||||
if (song->filetype() == spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_UNKNOWN) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::Unsupported;
|
||||
}
|
||||
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
song->set_albumartist(tag->value(TagParser::KnownField::AlbumArtist).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_artist(tag->value(TagParser::KnownField::Artist).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_album(tag->value(TagParser::KnownField::Album).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_title(tag->value(TagParser::KnownField::Title).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_genre(tag->value(TagParser::KnownField::Genre).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_composer(tag->value(TagParser::KnownField::Composer).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_performer(tag->value(TagParser::KnownField::Performers).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_grouping(tag->value(TagParser::KnownField::Grouping).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_comment(tag->value(TagParser::KnownField::Comment).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_lyrics(tag->value(TagParser::KnownField::Lyrics).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_year(tag->value(TagParser::KnownField::RecordDate).toInteger());
|
||||
song->set_originalyear(tag->value(TagParser::KnownField::ReleaseDate).toInteger());
|
||||
song->set_track(tag->value(TagParser::KnownField::TrackPosition).toInteger());
|
||||
song->set_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger());
|
||||
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
||||
song->set_art_embedded(true);
|
||||
}
|
||||
const float rating = ConvertPOPMRating(tag->value(TagParser::KnownField::Rating));
|
||||
if (song->rating() <= 0 && rating > 0.0 && rating <= 1.0) {
|
||||
song->set_rating(rating);
|
||||
}
|
||||
}
|
||||
|
||||
// Set integer fields to -1 if they're not valid
|
||||
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();
|
||||
|
||||
return Result::ErrorCode::Success;
|
||||
|
||||
}
|
||||
catch(...) {
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderTagParser::WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const {
|
||||
|
||||
if (request.filename().empty()) return Result::ErrorCode::FilenameMissing;
|
||||
|
||||
const spb::tagreader::SongMetadata &song = request.metadata();
|
||||
const bool save_tags = request.has_save_tags() && request.save_tags();
|
||||
const bool save_playcount = request.has_save_playcount() && request.save_playcount();
|
||||
const bool save_rating = request.has_save_rating() && request.save_rating();
|
||||
const bool save_cover = request.has_save_cover() && request.save_cover();
|
||||
|
||||
QStringList save_tags_options;
|
||||
if (save_tags) {
|
||||
save_tags_options << QStringLiteral("tags");
|
||||
}
|
||||
if (save_playcount) {
|
||||
save_tags_options << QStringLiteral("playcount");
|
||||
}
|
||||
if (save_rating) {
|
||||
save_tags_options << QStringLiteral("rating");
|
||||
}
|
||||
if (save_cover) {
|
||||
save_tags_options << QStringLiteral("embedded cover");
|
||||
}
|
||||
|
||||
qLog(Debug) << "Saving" << save_tags_options.join(QLatin1String(", ")) << "to" << filename;
|
||||
|
||||
const Cover cover = LoadCoverFromRequest(filename, request);
|
||||
|
||||
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 Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
taginfo.parseTracks(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
taginfo.parseTags(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
if (taginfo.tags().size() <= 0) {
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
if (save_tags) {
|
||||
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
||||
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
||||
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
||||
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
||||
}
|
||||
if (save_playcount) {
|
||||
SaveSongPlaycountToFile(tag, song.playcount());
|
||||
}
|
||||
if (save_rating) {
|
||||
SaveSongRatingToFile(tag, song.rating());
|
||||
}
|
||||
if (save_cover) {
|
||||
SaveEmbeddedArt(tag, cover.data);
|
||||
}
|
||||
}
|
||||
|
||||
taginfo.applyChanges(diag, progress);
|
||||
taginfo.close();
|
||||
|
||||
for (const TagParser::DiagMessage &msg : diag) {
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
return Result::ErrorCode::Success;
|
||||
}
|
||||
catch(...) {}
|
||||
|
||||
return Result::ErrorCode::FileParseError;
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderTagParser::LoadEmbeddedArt(const QString &filename, QByteArray &data) const {
|
||||
|
||||
if (filename.isEmpty()) return Result::ErrorCode::FilenameMissing;
|
||||
|
||||
qLog(Debug) << "Loading art from" << 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();
|
||||
|
||||
taginfo.parseContainerFormat(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
taginfo.parseTags(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
||||
data = QByteArray(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::Success;
|
||||
}
|
||||
}
|
||||
|
||||
taginfo.close();
|
||||
|
||||
for (const TagParser::DiagMessage &msg : diag) {
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
}
|
||||
catch(...) {}
|
||||
|
||||
return Result::ErrorCode::FileParseError;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderTagParser::SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const {
|
||||
|
||||
tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString()));
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const {
|
||||
|
||||
if (request.filename().empty()) return Result::ErrorCode::FilenameMissing;
|
||||
|
||||
qLog(Debug) << "Saving art to" << filename;
|
||||
|
||||
const Cover cover = LoadCoverFromRequest(filename, request);
|
||||
|
||||
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();
|
||||
|
||||
taginfo.parseContainerFormat(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
taginfo.parseTags(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
if (taginfo.tags().size() <= 0) {
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
SaveEmbeddedArt(tag, cover.data);
|
||||
}
|
||||
|
||||
taginfo.applyChanges(diag, progress);
|
||||
taginfo.close();
|
||||
|
||||
for (const TagParser::DiagMessage &msg : diag) {
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
return Result::ErrorCode::Success;
|
||||
|
||||
}
|
||||
catch(...) {}
|
||||
|
||||
return Result::ErrorCode::FileParseError;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderTagParser::SaveSongPlaycountToFile(TagParser::Tag *tag, const uint playcount) const {
|
||||
|
||||
Q_UNUSED(tag);
|
||||
Q_UNUSED(playcount);
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderTagParser::SaveSongPlaycountToFile(const QString &filename, const uint playcount) const {
|
||||
|
||||
Q_UNUSED(filename);
|
||||
Q_UNUSED(playcount);
|
||||
|
||||
return Result::ErrorCode::Unsupported;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderTagParser::SaveSongRatingToFile(TagParser::Tag *tag, const float rating) const {
|
||||
|
||||
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(rating)));
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const float rating) const {
|
||||
|
||||
if (filename.isEmpty()) return Result::ErrorCode::FilenameMissing;
|
||||
|
||||
qLog(Debug) << "Saving song rating to" << filename;
|
||||
|
||||
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 Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
taginfo.parseTracks(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
taginfo.parseTags(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return Result::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
if (taginfo.tags().size() <= 0) {
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
SaveSongRatingToFile(tag, rating);
|
||||
}
|
||||
|
||||
taginfo.applyChanges(diag, progress);
|
||||
taginfo.close();
|
||||
|
||||
for (const TagParser::DiagMessage &msg : diag) {
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
return Result::ErrorCode::Success;
|
||||
}
|
||||
catch(...) {}
|
||||
|
||||
return Result::ErrorCode::FileParseError;
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2021-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERTAGPARSER_H
|
||||
#define TAGREADERTAGPARSER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <tagparser/tag.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "tagreadermessages.pb.h"
|
||||
#include "tagreaderbase.h"
|
||||
|
||||
/*
|
||||
* This class holds all useful methods to read and write tags from/to files.
|
||||
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||
*/
|
||||
class TagReaderTagParser : public TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderTagParser();
|
||||
|
||||
bool IsMediaFile(const QString &filename) const override;
|
||||
|
||||
Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||
Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const override;
|
||||
|
||||
Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override;
|
||||
Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||
|
||||
Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override;
|
||||
Result SaveSongRatingToFile(const QString &filename, const float rating) const override;
|
||||
|
||||
private:
|
||||
void SaveSongPlaycountToFile(TagParser::Tag *tag, const uint playcount) const;
|
||||
void SaveSongRatingToFile(TagParser::Tag *tag, const float rating) const;
|
||||
void SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const;
|
||||
|
||||
public:
|
||||
Q_DISABLE_COPY(TagReaderTagParser)
|
||||
};
|
||||
|
||||
#endif // TAGREADERTAGPARSER_H
|
||||
@@ -1,58 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
|
||||
|
||||
set(SOURCES main.cpp tagreaderworker.cpp)
|
||||
set(HEADERS tagreaderworker.h)
|
||||
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
|
||||
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
|
||||
|
||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
${PROTOBUF_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_include_directories(strawberry-tagreader PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader
|
||||
${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader
|
||||
${CMAKE_BINARY_DIR}/src
|
||||
)
|
||||
|
||||
target_link_directories(strawberry-tagreader PRIVATE ${GLIB_LIBRARY_DIRS})
|
||||
|
||||
target_link_libraries(strawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
libstrawberry-common
|
||||
libstrawberry-tagreader
|
||||
)
|
||||
|
||||
if(HAVE_TAGLIB)
|
||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_TAGPARSER)
|
||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||
target_link_directories(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARY_DIRS})
|
||||
target_link_libraries(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(FREEBSD)
|
||||
target_link_libraries(strawberry-tagreader PRIVATE execinfo)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(strawberry-tagreader PRIVATE /System/Library/Frameworks/Foundation.framework)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
install(TARGETS strawberry-tagreader DESTINATION ${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns)
|
||||
else()
|
||||
install(TARGETS strawberry-tagreader RUNTIME DESTINATION bin)
|
||||
endif()
|
||||
@@ -1,61 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QLocalSocket>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderworker.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
QCoreApplication a(argc, argv);
|
||||
QStringList args(a.arguments());
|
||||
|
||||
if (args.count() != 2) {
|
||||
std::cerr << "This program is used internally by Strawberry to parse tags in music files\n"
|
||||
"without exposing the whole application to crashes caused by malformed\n"
|
||||
"files. It is not meant to be run on its own.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
logging::Init();
|
||||
qLog(Info) << "TagReader worker connecting to" << args[1];
|
||||
|
||||
// Connect to the parent process.
|
||||
QLocalSocket socket;
|
||||
socket.connectToServer(args[1]);
|
||||
if (!socket.waitForConnected(2000)) {
|
||||
std::cerr << "Failed to connect to the parent process.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
TagReaderWorker worker(&socket);
|
||||
|
||||
return a.exec();
|
||||
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
#include <QIODevice>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "tagreaderworker.h"
|
||||
|
||||
#ifdef HAVE_TAGLIB
|
||||
# include "tagreadertaglib.h"
|
||||
# include "tagreadergme.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TAGPARSER
|
||||
# include "tagreadertagparser.h"
|
||||
#endif
|
||||
|
||||
using std::make_shared;
|
||||
using std::shared_ptr;
|
||||
|
||||
TagReaderWorker::TagReaderWorker(QIODevice *socket, QObject *parent)
|
||||
: AbstractMessageHandler<spb::tagreader::Message>(socket, parent) {
|
||||
|
||||
#ifdef HAVE_TAGLIB
|
||||
tagreaders_ << make_shared<TagReaderTagLib>();
|
||||
tagreaders_ << make_shared<TagReaderGME>();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TAGPARSER
|
||||
tagreaders_ << make_shared<TagReaderTagParser>();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
|
||||
|
||||
spb::tagreader::Message reply;
|
||||
|
||||
HandleMessage(message, reply);
|
||||
SendReply(message, &reply);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderWorker::DeviceClosed() {
|
||||
|
||||
AbstractMessageHandler<spb::tagreader::Message>::DeviceClosed();
|
||||
|
||||
QCoreApplication::exit();
|
||||
|
||||
}
|
||||
|
||||
void TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply) {
|
||||
|
||||
for (shared_ptr<TagReaderBase> reader : std::as_const(tagreaders_)) {
|
||||
|
||||
if (message.has_is_media_file_request()) {
|
||||
const QString filename = QString::fromStdString(message.is_media_file_request().filename());
|
||||
const bool success = reader->IsMediaFile(filename);
|
||||
reply.mutable_is_media_file_response()->set_success(success);
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (message.has_read_file_request()) {
|
||||
const QString filename = QString::fromStdString(message.read_file_request().filename());
|
||||
spb::tagreader::ReadFileResponse *response = reply.mutable_read_file_response();
|
||||
const TagReaderBase::Result result = reader->ReadFile(filename, response->mutable_metadata());
|
||||
response->set_success(result.success());
|
||||
if (result.success()) {
|
||||
if (response->has_error()) {
|
||||
response->clear_error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!response->has_error()) {
|
||||
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (message.has_write_file_request()) {
|
||||
const QString filename = QString::fromStdString(message.write_file_request().filename());
|
||||
const TagReaderBase::Result result = reader->WriteFile(filename, message.write_file_request());
|
||||
spb::tagreader::WriteFileResponse *response = reply.mutable_write_file_response();
|
||||
response->set_success(result.success());
|
||||
if (result.success()) {
|
||||
if (response->has_error()) {
|
||||
response->clear_error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!response->has_error()) {
|
||||
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (message.has_load_embedded_art_request()) {
|
||||
const QString filename = QString::fromStdString(message.load_embedded_art_request().filename());
|
||||
QByteArray data;
|
||||
const TagReaderBase::Result result = reader->LoadEmbeddedArt(filename, data);
|
||||
spb::tagreader::LoadEmbeddedArtResponse *response = reply.mutable_load_embedded_art_response();
|
||||
response->set_success(result.success());
|
||||
if (result.success()) {
|
||||
response->set_data(data.toStdString());
|
||||
if (response->has_error()) {
|
||||
response->clear_error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!response->has_error()) {
|
||||
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (message.has_save_embedded_art_request()) {
|
||||
const QString filename = QString::fromStdString(message.save_embedded_art_request().filename());
|
||||
const TagReaderBase::Result result = reader->SaveEmbeddedArt(filename, message.save_embedded_art_request());
|
||||
spb::tagreader::SaveEmbeddedArtResponse *response = reply.mutable_save_embedded_art_response();
|
||||
response->set_success(result.success());
|
||||
if (result.success()) {
|
||||
if (response->has_error()) {
|
||||
response->clear_error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!response->has_error()) {
|
||||
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (message.has_save_song_playcount_to_file_request()) {
|
||||
const QString filename = QString::fromStdString(message.save_song_playcount_to_file_request().filename());
|
||||
const TagReaderBase::Result result = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().playcount());
|
||||
spb::tagreader::SaveSongPlaycountToFileResponse *response = reply.mutable_save_song_playcount_to_file_response();
|
||||
response->set_success(result.success());
|
||||
if (result.success()) {
|
||||
if (response->has_error()) {
|
||||
response->clear_error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!response->has_error()) {
|
||||
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (message.has_save_song_rating_to_file_request()) {
|
||||
const QString filename = QString::fromStdString(message.save_song_rating_to_file_request().filename());
|
||||
const TagReaderBase::Result result = reader->SaveSongRatingToFile(filename, message.save_song_rating_to_file_request().rating());
|
||||
spb::tagreader::SaveSongRatingToFileResponse *response = reply.mutable_save_song_rating_to_file_response();
|
||||
response->set_success(result.success());
|
||||
if (result.success()) {
|
||||
if (response->has_error()) {
|
||||
response->clear_error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (!response->has_error()) {
|
||||
response->set_error(TagReaderBase::ErrorString(result).toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
||||
Strawberry is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Strawberry is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERWORKER_H
|
||||
#define TAGREADERWORKER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
|
||||
#include "core/messagehandler.h"
|
||||
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
class QIODevice;
|
||||
class TagReaderBase;
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderWorker(QIODevice *socket, QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void MessageArrived(const spb::tagreader::Message &message) override;
|
||||
void DeviceClosed() override;
|
||||
|
||||
private:
|
||||
void HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply);
|
||||
|
||||
QList<shared_ptr<TagReaderBase>> tagreaders_;
|
||||
};
|
||||
|
||||
#endif // TAGREADERWORKER_H
|
||||
@@ -5,6 +5,7 @@ if(HAVE_TRANSLATIONS)
|
||||
endif()
|
||||
|
||||
set(SOURCES
|
||||
core/logging.cpp
|
||||
core/mainwindow.cpp
|
||||
core/application.cpp
|
||||
core/player.cpp
|
||||
@@ -31,7 +32,6 @@ set(SOURCES
|
||||
core/songloader.cpp
|
||||
core/stylehelper.cpp
|
||||
core/stylesheetloader.cpp
|
||||
core/tagreaderclient.cpp
|
||||
core/taskmanager.cpp
|
||||
core/thread.cpp
|
||||
core/urlhandler.cpp
|
||||
@@ -63,6 +63,27 @@ set(SOURCES
|
||||
utilities/screenutils.cpp
|
||||
utilities/textencodingutils.cpp
|
||||
|
||||
tagreader/tagreaderclient.cpp
|
||||
tagreader/tagreaderresult.cpp
|
||||
tagreader/tagreaderbase.cpp
|
||||
tagreader/tagreadertaglib.cpp
|
||||
tagreader/tagreadergme.cpp
|
||||
tagreader/tagreaderrequest.cpp
|
||||
tagreader/tagreaderismediafilerequest.cpp
|
||||
tagreader/tagreaderreadfilerequest.cpp
|
||||
tagreader/tagreaderwritefilerequest.cpp
|
||||
tagreader/tagreaderloadcoverdatarequest.cpp
|
||||
tagreader/tagreaderloadcoverimagerequest.cpp
|
||||
tagreader/tagreadersavecoverrequest.cpp
|
||||
tagreader/tagreadersaveplaycountrequest.cpp
|
||||
tagreader/tagreadersaveratingrequest.cpp
|
||||
tagreader/albumcovertagdata.cpp
|
||||
tagreader/savetagcoverdata.cpp
|
||||
tagreader/tagreaderreply.cpp
|
||||
tagreader/tagreaderreadfilereply.cpp
|
||||
tagreader/tagreaderloadcoverdatareply.cpp
|
||||
tagreader/tagreaderloadcoverimagereply.cpp
|
||||
|
||||
filterparser/filterparser.cpp
|
||||
filterparser/filtertree.cpp
|
||||
|
||||
@@ -319,6 +340,7 @@ set(SOURCES
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
core/logging.h
|
||||
core/mainwindow.h
|
||||
core/application.h
|
||||
core/player.h
|
||||
@@ -333,7 +355,6 @@ set(HEADERS
|
||||
core/qtfslistener.h
|
||||
core/settings.h
|
||||
core/songloader.h
|
||||
core/tagreaderclient.h
|
||||
core/taskmanager.h
|
||||
core/thread.h
|
||||
core/urlhandler.h
|
||||
@@ -344,6 +365,12 @@ set(HEADERS
|
||||
core/stylesheetloader.h
|
||||
core/localredirectserver.h
|
||||
|
||||
tagreader/tagreaderclient.h
|
||||
tagreader/tagreaderreply.h
|
||||
tagreader/tagreaderreadfilereply.h
|
||||
tagreader/tagreaderloadcoverdatareply.h
|
||||
tagreader/tagreaderloadcoverimagereply.h
|
||||
|
||||
engine/enginebase.h
|
||||
engine/devicefinders.h
|
||||
|
||||
@@ -1059,8 +1086,8 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
${GOBJECT_INCLUDE_DIRS}
|
||||
${SQLITE_INCLUDE_DIRS}
|
||||
${PROTOBUF_INCLUDE_DIRS}
|
||||
${ICU_INCLUDE_DIRS}
|
||||
${TAGLIB_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||
@@ -1072,9 +1099,6 @@ target_include_directories(strawberry_lib PUBLIC
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader
|
||||
${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader
|
||||
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
@@ -1083,9 +1107,9 @@ target_link_directories(strawberry_lib PUBLIC
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${SQLITE_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${ICU_LIBRARY_DIRS}
|
||||
${TAGLIB_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(strawberry_lib PUBLIC
|
||||
@@ -1094,16 +1118,14 @@ target_link_libraries(strawberry_lib PUBLIC
|
||||
${GOBJECT_LIBRARIES}
|
||||
${SQLITE_LIBRARIES}
|
||||
${ICU_LIBRARIES}
|
||||
${TAGLIB_LIBRARIES}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
Qt${QT_VERSION_MAJOR}::Sql
|
||||
${Protobuf_LIBRARIES}
|
||||
${SINGLEAPPLICATION_LIBRARIES}
|
||||
libstrawberry-common
|
||||
libstrawberry-tagreader
|
||||
)
|
||||
|
||||
if(HAVE_DBUS)
|
||||
@@ -1268,9 +1290,6 @@ endif()
|
||||
|
||||
target_link_libraries(strawberry PRIVATE strawberry_lib)
|
||||
|
||||
# macdeploy.py relies on the blob being built first.
|
||||
add_dependencies(strawberry strawberry-tagreader)
|
||||
|
||||
if(NOT APPLE)
|
||||
install(TARGETS strawberry RUNTIME DESTINATION bin)
|
||||
endif()
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
#include "core/application.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/database.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/thread.h"
|
||||
#include "core/song.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/settings.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "utilities/threadutils.h"
|
||||
#include "collection.h"
|
||||
#include "collectionwatcher.h"
|
||||
@@ -216,7 +216,7 @@ void SCollection::SyncPlaycountAndRatingToFiles() {
|
||||
void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
|
||||
|
||||
if (save_tags || save_playcounts_to_files_) {
|
||||
app_->tag_reader_client()->SaveSongsPlaycount(songs);
|
||||
app_->tag_reader_client()->SaveSongsPlaycountAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -224,7 +224,7 @@ void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_t
|
||||
void SCollection::SongsRatingChanged(const SongList &songs, const bool save_tags) {
|
||||
|
||||
if (save_tags || save_ratings_to_files_) {
|
||||
app_->tag_reader_client()->SaveSongsRating(songs);
|
||||
app_->tag_reader_client()->SaveSongsRatingAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "collectionplaylistitem.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
|
||||
class SqlRow;
|
||||
|
||||
@@ -42,9 +42,9 @@ QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
||||
|
||||
void CollectionPlaylistItem::Reload() {
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not reload file" << song_.url() << result.error;
|
||||
qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
|
||||
return;
|
||||
}
|
||||
UpdateTemporaryMetadata(song_);
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
|
||||
#include "core/filesystemwatcherinterface.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/settings.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collectiondirectory.h"
|
||||
#include "collectionbackend.h"
|
||||
#include "collectionwatcher.h"
|
||||
@@ -866,7 +866,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||
}
|
||||
|
||||
Song song_on_disk(source_);
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
|
||||
if (result.success() && song_on_disk.is_valid()) {
|
||||
song_on_disk.set_source(source_);
|
||||
song_on_disk.set_directory_id(t->dir());
|
||||
@@ -919,7 +919,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
||||
}
|
||||
else { // It's a normal media file
|
||||
Song song(source_);
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(file, &song);
|
||||
if (result.success() && song.is_valid()) {
|
||||
song.set_source(source_);
|
||||
PerformEBUR128Analysis(song);
|
||||
|
||||
@@ -37,10 +37,8 @@
|
||||
#cmakedefine HAVE_KEYSYMDEF_H
|
||||
#cmakedefine HAVE_XF86KEYSYM_H
|
||||
|
||||
#cmakedefine HAVE_TAGLIB
|
||||
#cmakedefine HAVE_TAGLIB_DSFFILE
|
||||
#cmakedefine HAVE_TAGLIB_DSDIFFFILE
|
||||
#cmakedefine HAVE_TAGPARSER
|
||||
|
||||
#cmakedefine USE_BUNDLE
|
||||
|
||||
|
||||
@@ -36,12 +36,11 @@
|
||||
|
||||
#include "shared_ptr.h"
|
||||
#include "lazy.h"
|
||||
#include "tagreaderclient.h"
|
||||
#include "database.h"
|
||||
#include "taskmanager.h"
|
||||
#include "player.h"
|
||||
#include "networkaccessmanager.h"
|
||||
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "engine/devicefinders.h"
|
||||
#ifndef Q_OS_WIN
|
||||
# include "device/devicemanager.h"
|
||||
@@ -118,7 +117,6 @@ class ApplicationImpl {
|
||||
tag_reader_client_([app](){
|
||||
TagReaderClient *client = new TagReaderClient();
|
||||
app->MoveToNewThread(client);
|
||||
client->Start();
|
||||
return client;
|
||||
}),
|
||||
database_([app]() {
|
||||
|
||||
@@ -2177,21 +2177,20 @@ void MainWindow::RenumberTracks() {
|
||||
Song song = item->OriginalMetadata();
|
||||
if (song.IsEditable()) {
|
||||
song.set_track(track);
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
|
||||
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
|
||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||
}
|
||||
++track;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx) {
|
||||
void MainWindow::SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx) {
|
||||
|
||||
if (reply->is_successful() && idx.isValid()) {
|
||||
if (reply->success() && idx.isValid()) {
|
||||
app_->playlist_manager()->current()->ReloadItems(QList<int>() << idx.row());
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
}
|
||||
|
||||
@@ -2209,9 +2208,9 @@ void MainWindow::SelectionSetValue() {
|
||||
Song song = item->OriginalMetadata();
|
||||
if (!song.is_valid()) continue;
|
||||
if (song.url().isLocalFile() && Playlist::set_column_value(song, column, column_value)) {
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
|
||||
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
|
||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||
}
|
||||
else if (song.source() == Song::Source::Stream) {
|
||||
app_->playlist_manager()->current()->setData(source_index, column_value, 0);
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
#include "lazy.h"
|
||||
#include "platforminterface.h"
|
||||
#include "song.h"
|
||||
#include "tagreaderclient.h"
|
||||
#include "settings.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "osd/osdbase.h"
|
||||
#include "playlist/playlist.h"
|
||||
@@ -218,7 +218,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
|
||||
void PlayingWidgetPositionChanged(const bool above_status_bar);
|
||||
|
||||
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx);
|
||||
void SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx);
|
||||
|
||||
void ShowCoverManager();
|
||||
void ShowEqualizer();
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QSqlRecord>
|
||||
|
||||
#include <taglib/tstring.h>
|
||||
|
||||
#include "core/iconloader.h"
|
||||
#include "engine/enginemetadata.h"
|
||||
#include "utilities/strutils.h"
|
||||
@@ -55,13 +57,13 @@
|
||||
#include "utilities/coverutils.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "utilities/sqlhelper.h"
|
||||
|
||||
#include "song.h"
|
||||
#include "sqlquery.h"
|
||||
#include "sqlrow.h"
|
||||
#ifdef HAVE_DBUS
|
||||
# include "mpris_common.h"
|
||||
#endif
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
@@ -482,6 +484,29 @@ const QString &Song::musicbrainz_work_id() const { return d->musicbrainz_work_id
|
||||
std::optional<double> Song::ebur128_integrated_loudness_lufs() const { return d->ebur128_integrated_loudness_lufs_; }
|
||||
std::optional<double> Song::ebur128_loudness_range_lu() const { return d->ebur128_loudness_range_lu_; }
|
||||
|
||||
QString *Song::mutable_title() { return &d->title_; }
|
||||
QString *Song::mutable_album() { return &d->album_; }
|
||||
QString *Song::mutable_artist() { return &d->artist_; }
|
||||
QString *Song::mutable_albumartist() { return &d->albumartist_; }
|
||||
QString *Song::mutable_genre() { return &d->genre_; }
|
||||
QString *Song::mutable_composer() { return &d->composer_; }
|
||||
QString *Song::mutable_performer() { return &d->performer_; }
|
||||
QString *Song::mutable_grouping() { return &d->grouping_; }
|
||||
QString *Song::mutable_comment() { return &d->comment_; }
|
||||
QString *Song::mutable_lyrics() { return &d->lyrics_; }
|
||||
QString *Song::mutable_acoustid_id() { return &d->acoustid_id_; }
|
||||
QString *Song::mutable_acoustid_fingerprint() { return &d->acoustid_fingerprint_; }
|
||||
QString *Song::mutable_musicbrainz_album_artist_id() { return &d->musicbrainz_album_artist_id_; }
|
||||
QString *Song::mutable_musicbrainz_artist_id() { return &d->musicbrainz_artist_id_; }
|
||||
QString *Song::mutable_musicbrainz_original_artist_id() { return &d->musicbrainz_original_artist_id_; }
|
||||
QString *Song::mutable_musicbrainz_album_id() { return &d->musicbrainz_album_id_; }
|
||||
QString *Song::mutable_musicbrainz_original_album_id() { return &d->musicbrainz_original_album_id_; }
|
||||
QString *Song::mutable_musicbrainz_recording_id() { return &d->musicbrainz_recording_id_; }
|
||||
QString *Song::mutable_musicbrainz_track_id() { return &d->musicbrainz_track_id_; }
|
||||
QString *Song::mutable_musicbrainz_disc_id() { return &d->musicbrainz_disc_id_; }
|
||||
QString *Song::mutable_musicbrainz_release_group_id() { return &d->musicbrainz_release_group_id_; }
|
||||
QString *Song::mutable_musicbrainz_work_id() { return &d->musicbrainz_work_id_; }
|
||||
|
||||
bool Song::init_from_file() const { return d->init_from_file_; }
|
||||
|
||||
const QString &Song::title_sortable() const { return d->title_sortable_; }
|
||||
@@ -503,7 +528,7 @@ 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_compilation(const bool v) { d->compilation_ = v; }
|
||||
void Song::set_composer(const QString &v) { d->composer_ = v; }
|
||||
void Song::set_performer(const QString &v) { d->performer_ = v; }
|
||||
void Song::set_grouping(const QString &v) { d->grouping_ = v; }
|
||||
@@ -571,6 +596,59 @@ void Song::set_ebur128_loudness_range_lu(const std::optional<double> v) { d->ebu
|
||||
|
||||
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
|
||||
|
||||
void Song::set_title(const TagLib::String &v) {
|
||||
|
||||
const QString title = TagLibStringToQString(v);
|
||||
d->title_sortable_ = sortable(title);
|
||||
d->title_ = title;
|
||||
|
||||
}
|
||||
|
||||
void Song::set_album(const TagLib::String &v) {
|
||||
|
||||
const QString album = TagLibStringToQString(v);
|
||||
d->album_sortable_ = sortable(album);
|
||||
d->album_ = album;
|
||||
|
||||
}
|
||||
void Song::set_artist(const TagLib::String &v) {
|
||||
|
||||
const QString artist = TagLibStringToQString(v);
|
||||
d->artist_sortable_ = sortable(artist);
|
||||
d->artist_ = artist;
|
||||
|
||||
}
|
||||
|
||||
void Song::set_albumartist(const TagLib::String &v) {
|
||||
|
||||
const QString albumartist = TagLibStringToQString(v);
|
||||
d->albumartist_sortable_ = sortable(albumartist);
|
||||
d->albumartist_ = albumartist;
|
||||
|
||||
}
|
||||
|
||||
void Song::set_genre(const TagLib::String &v) { d->genre_ = TagLibStringToQString(v); }
|
||||
void Song::set_composer(const TagLib::String &v) { d->composer_ = TagLibStringToQString(v); }
|
||||
void Song::set_performer(const TagLib::String &v) { d->performer_ = TagLibStringToQString(v); }
|
||||
void Song::set_grouping(const TagLib::String &v) { d->grouping_ = TagLibStringToQString(v); }
|
||||
void Song::set_comment(const TagLib::String &v) { d->comment_ = TagLibStringToQString(v); }
|
||||
void Song::set_lyrics(const TagLib::String &v) { d->lyrics_ = TagLibStringToQString(v); }
|
||||
void Song::set_artist_id(const TagLib::String &v) { d->artist_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_album_id(const TagLib::String &v) { d->album_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_song_id(const TagLib::String &v) { d->song_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_acoustid_id(const TagLib::String &v) { d->acoustid_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_acoustid_fingerprint(const TagLib::String &v) { d->acoustid_fingerprint_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_album_artist_id(const TagLib::String &v) { d->musicbrainz_album_artist_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_artist_id(const TagLib::String &v) { d->musicbrainz_artist_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_original_artist_id(const TagLib::String &v) { d->musicbrainz_original_artist_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_album_id(const TagLib::String &v) { d->musicbrainz_album_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_original_album_id(const TagLib::String &v) { d->musicbrainz_original_album_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_recording_id(const TagLib::String &v) { d->musicbrainz_recording_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_track_id(const TagLib::String &v) { d->musicbrainz_track_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_disc_id(const TagLib::String &v) { d->musicbrainz_disc_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_release_group_id(const TagLib::String &v) { d->musicbrainz_release_group_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_work_id(const TagLib::String &v) { d->musicbrainz_work_id_ = TagLibStringToQString(v); }
|
||||
|
||||
const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
|
||||
const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
|
||||
const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
|
||||
@@ -1312,125 +1390,6 @@ void Song::Init(const QString &title, const QString &artist, const QString &albu
|
||||
|
||||
}
|
||||
|
||||
void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
|
||||
|
||||
if (d->source_ == Source::Unknown) d->source_ = Source::LocalFile;
|
||||
|
||||
d->init_from_file_ = true;
|
||||
d->valid_ = pb.valid();
|
||||
set_title(QString::fromStdString(pb.title()));
|
||||
set_album(QString::fromStdString(pb.album()));
|
||||
set_artist(QString::fromStdString(pb.artist()));
|
||||
set_albumartist(QString::fromStdString(pb.albumartist()));
|
||||
d->track_ = pb.track();
|
||||
d->disc_ = pb.disc();
|
||||
d->year_ = pb.year();
|
||||
d->originalyear_ = pb.originalyear();
|
||||
d->genre_ = QString::fromStdString(pb.genre());
|
||||
d->compilation_ = pb.compilation();
|
||||
d->composer_ = QString::fromStdString(pb.composer());
|
||||
d->performer_ = QString::fromStdString(pb.performer());
|
||||
d->grouping_ = QString::fromStdString(pb.grouping());
|
||||
d->comment_ = QString::fromStdString(pb.comment());
|
||||
d->lyrics_ = QString::fromStdString(pb.lyrics());
|
||||
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(QString::fromStdString(pb.url()).toUtf8()));
|
||||
d->basefilename_ = QString::fromStdString(pb.basefilename());
|
||||
d->filetype_ = static_cast<FileType>(pb.filetype());
|
||||
d->filesize_ = pb.filesize();
|
||||
d->mtime_ = pb.mtime();
|
||||
d->ctime_ = pb.ctime();
|
||||
d->skipcount_ = pb.skipcount();
|
||||
d->lastplayed_ = pb.lastplayed();
|
||||
d->lastseen_ = pb.lastseen();
|
||||
|
||||
if (pb.has_playcount()) {
|
||||
d->playcount_ = pb.playcount();
|
||||
}
|
||||
if (pb.has_rating()) {
|
||||
d->rating_ = pb.rating();
|
||||
}
|
||||
|
||||
d->art_embedded_ = pb.has_art_embedded();
|
||||
|
||||
d->acoustid_id_ = QString::fromStdString(pb.acoustid_id());
|
||||
d->acoustid_fingerprint_ = QString::fromStdString(pb.acoustid_fingerprint());
|
||||
|
||||
d->musicbrainz_album_artist_id_ = QString::fromStdString(pb.musicbrainz_album_artist_id());
|
||||
d->musicbrainz_artist_id_ = QString::fromStdString(pb.musicbrainz_artist_id().data());
|
||||
d->musicbrainz_original_artist_id_ = QString::fromStdString(pb.musicbrainz_original_artist_id());
|
||||
d->musicbrainz_album_id_ = QString::fromStdString(pb.musicbrainz_album_id());
|
||||
d->musicbrainz_original_album_id_ = QString::fromStdString(pb.musicbrainz_original_album_id());
|
||||
d->musicbrainz_recording_id_ = QString::fromStdString(pb.musicbrainz_recording_id());
|
||||
d->musicbrainz_track_id_ = QString::fromStdString(pb.musicbrainz_track_id());
|
||||
d->musicbrainz_disc_id_ = QString::fromStdString(pb.musicbrainz_disc_id());
|
||||
d->musicbrainz_release_group_id_ = QString::fromStdString(pb.musicbrainz_release_group_id());
|
||||
d->musicbrainz_work_id_ = QString::fromStdString(pb.musicbrainz_work_id());
|
||||
|
||||
d->suspicious_tags_ = pb.suspicious_tags();
|
||||
|
||||
InitArtManual();
|
||||
|
||||
}
|
||||
|
||||
void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
|
||||
|
||||
const QByteArray url(d->url_.toEncoded());
|
||||
|
||||
pb->set_valid(d->valid_);
|
||||
pb->set_title(d->title_.toStdString());
|
||||
pb->set_album(d->album_.toStdString());
|
||||
pb->set_artist(d->artist_.toStdString());
|
||||
pb->set_albumartist(d->albumartist_.toStdString());
|
||||
pb->set_track(d->track_);
|
||||
pb->set_disc(d->disc_);
|
||||
pb->set_year(d->year_);
|
||||
pb->set_originalyear(d->originalyear_);
|
||||
pb->set_genre(d->genre_.toStdString());
|
||||
pb->set_compilation(d->compilation_);
|
||||
pb->set_composer(d->composer_.toStdString());
|
||||
pb->set_performer(d->performer_.toStdString());
|
||||
pb->set_grouping(d->grouping_.toStdString());
|
||||
pb->set_comment(d->comment_.toStdString());
|
||||
pb->set_lyrics(d->lyrics_.toStdString());
|
||||
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(d->basefilename_.toStdString());
|
||||
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_playcount(d->playcount_);
|
||||
pb->set_skipcount(d->skipcount_);
|
||||
pb->set_lastplayed(d->lastplayed_);
|
||||
pb->set_lastseen(d->lastseen_);
|
||||
pb->set_art_embedded(d->art_embedded_);
|
||||
pb->set_rating(d->rating_);
|
||||
|
||||
pb->set_acoustid_id(d->acoustid_id_.toStdString());
|
||||
pb->set_acoustid_fingerprint(d->acoustid_fingerprint_.toStdString());
|
||||
|
||||
pb->set_musicbrainz_album_artist_id(d->musicbrainz_album_artist_id_.toStdString());
|
||||
pb->set_musicbrainz_artist_id(d->musicbrainz_artist_id_.toStdString());
|
||||
pb->set_musicbrainz_original_artist_id(d->musicbrainz_original_artist_id_.toStdString());
|
||||
pb->set_musicbrainz_album_id(d->musicbrainz_album_id_.toStdString());
|
||||
pb->set_musicbrainz_original_album_id(d->musicbrainz_original_album_id_.toStdString());
|
||||
pb->set_musicbrainz_recording_id(d->musicbrainz_recording_id_.toStdString());
|
||||
pb->set_musicbrainz_track_id(d->musicbrainz_track_id_.toStdString());
|
||||
pb->set_musicbrainz_disc_id(d->musicbrainz_disc_id_.toStdString());
|
||||
pb->set_musicbrainz_release_group_id(d->musicbrainz_release_group_id_.toStdString());
|
||||
pb->set_musicbrainz_work_id(d->musicbrainz_work_id_.toStdString());
|
||||
|
||||
pb->set_suspicious_tags(d->suspicious_tags_);
|
||||
|
||||
}
|
||||
|
||||
void Song::InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col) {
|
||||
|
||||
Q_ASSERT(kRowIdColumns.count() + col <= r.count());
|
||||
|
||||
@@ -41,17 +41,15 @@
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
|
||||
#include <taglib/tstring.h>
|
||||
#undef TStringToQString
|
||||
#undef QStringToTString
|
||||
|
||||
class SqlQuery;
|
||||
class QSqlRecord;
|
||||
|
||||
class EngineMetadata;
|
||||
|
||||
namespace spb {
|
||||
namespace tagreader {
|
||||
class SongMetadata;
|
||||
} // namespace tagreader
|
||||
} // namespace spb
|
||||
|
||||
#ifdef HAVE_LIBGPOD
|
||||
struct _Itdb_Track;
|
||||
#endif
|
||||
@@ -81,9 +79,6 @@ class Song {
|
||||
Spotify = 11
|
||||
};
|
||||
|
||||
// Don't change these values - they're stored in the database, and defined in the tag reader protobuf.
|
||||
// If a new lossless file is added, also add it to IsFileLossless().
|
||||
|
||||
enum class FileType {
|
||||
Unknown = 0,
|
||||
WAV = 1,
|
||||
@@ -227,6 +222,29 @@ class Song {
|
||||
std::optional<double> ebur128_integrated_loudness_lufs() const;
|
||||
std::optional<double> ebur128_loudness_range_lu() const;
|
||||
|
||||
QString *mutable_title();
|
||||
QString *mutable_album();
|
||||
QString *mutable_artist();
|
||||
QString *mutable_albumartist();
|
||||
QString *mutable_genre();
|
||||
QString *mutable_composer();
|
||||
QString *mutable_performer();
|
||||
QString *mutable_grouping();
|
||||
QString *mutable_comment();
|
||||
QString *mutable_lyrics();
|
||||
QString *mutable_acoustid_id();
|
||||
QString *mutable_acoustid_fingerprint();
|
||||
QString *mutable_musicbrainz_album_artist_id();
|
||||
QString *mutable_musicbrainz_artist_id();
|
||||
QString *mutable_musicbrainz_original_artist_id();
|
||||
QString *mutable_musicbrainz_album_id();
|
||||
QString *mutable_musicbrainz_original_album_id();
|
||||
QString *mutable_musicbrainz_recording_id();
|
||||
QString *mutable_musicbrainz_track_id();
|
||||
QString *mutable_musicbrainz_disc_id();
|
||||
QString *mutable_musicbrainz_release_group_id();
|
||||
QString *mutable_musicbrainz_work_id();
|
||||
|
||||
bool init_from_file() const;
|
||||
|
||||
const QString &title_sortable() const;
|
||||
@@ -317,6 +335,32 @@ class Song {
|
||||
|
||||
void set_stream_url(const QUrl &v);
|
||||
|
||||
void set_title(const TagLib::String &v);
|
||||
void set_album(const TagLib::String &v);
|
||||
void set_artist(const TagLib::String &v);
|
||||
void set_albumartist(const TagLib::String &v);
|
||||
void set_genre(const TagLib::String &v);
|
||||
void set_composer(const TagLib::String &v);
|
||||
void set_performer(const TagLib::String &v);
|
||||
void set_grouping(const TagLib::String &v);
|
||||
void set_comment(const TagLib::String &v);
|
||||
void set_lyrics(const TagLib::String &v);
|
||||
void set_artist_id(const TagLib::String &v);
|
||||
void set_album_id(const TagLib::String &v);
|
||||
void set_song_id(const TagLib::String &v);
|
||||
void set_acoustid_id(const TagLib::String &v);
|
||||
void set_acoustid_fingerprint(const TagLib::String &v);
|
||||
void set_musicbrainz_album_artist_id(const TagLib::String &v);
|
||||
void set_musicbrainz_artist_id(const TagLib::String &v);
|
||||
void set_musicbrainz_original_artist_id(const TagLib::String &v);
|
||||
void set_musicbrainz_album_id(const TagLib::String &v);
|
||||
void set_musicbrainz_original_album_id(const TagLib::String &v);
|
||||
void set_musicbrainz_recording_id(const TagLib::String &v);
|
||||
void set_musicbrainz_track_id(const TagLib::String &v);
|
||||
void set_musicbrainz_disc_id(const TagLib::String &v);
|
||||
void set_musicbrainz_release_group_id(const TagLib::String &v);
|
||||
void set_musicbrainz_work_id(const TagLib::String &v);
|
||||
|
||||
const QUrl &effective_stream_url() const;
|
||||
const QString &effective_albumartist() const;
|
||||
const QString &effective_albumartist_sortable() const;
|
||||
@@ -422,7 +466,6 @@ class Song {
|
||||
// Constructors
|
||||
void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec);
|
||||
void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end);
|
||||
void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
|
||||
void InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col = 0);
|
||||
void InitFromQuery(const SqlQuery &query, const bool reliable_metadata, const int col = 0);
|
||||
void InitFromQuery(const SqlRow &row, const bool reliable_metadata, const int col = 0);
|
||||
@@ -445,7 +488,6 @@ class Song {
|
||||
#ifdef HAVE_DBUS
|
||||
void ToXesam(QVariantMap *map) const;
|
||||
#endif
|
||||
void ToProtobuf(spb::tagreader::SongMetadata *pb) const;
|
||||
|
||||
bool MergeFromEngineMetadata(const EngineMetadata &engine_metadata);
|
||||
|
||||
@@ -465,6 +507,10 @@ class Song {
|
||||
static QString AlbumRemoveDiscMisc(const QString &album);
|
||||
static QString TitleRemoveMisc(const QString &title);
|
||||
|
||||
static inline QString TagLibStringToQString(const TagLib::String &s) {
|
||||
return QString::fromUtf8((s).toCString(true));
|
||||
}
|
||||
|
||||
private:
|
||||
struct Private;
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
#include "player.h"
|
||||
#include "song.h"
|
||||
#include "songloader.h"
|
||||
#include "tagreaderclient.h"
|
||||
#include "database.h"
|
||||
#include "sqlrow.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "playlistparsers/cueparser.h"
|
||||
@@ -362,9 +362,9 @@ void SongLoader::EffectiveSongLoad(Song *song) {
|
||||
else {
|
||||
// It's a normal media file
|
||||
const QString filename = song->url().toLocalFile();
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not read file" << song->url() << result.error;
|
||||
qLog(Error) << "Could not read file" << song->url() << result.error_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/workerpool.h"
|
||||
|
||||
#include "song.h"
|
||||
#include "tagreaderclient.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace {
|
||||
constexpr char kWorkerExecutableName[] = "strawberry-tagreader";
|
||||
}
|
||||
|
||||
TagReaderClient *TagReaderClient::sInstance = nullptr;
|
||||
|
||||
TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool<HandlerType>(this)) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
sInstance = this;
|
||||
original_thread_ = thread();
|
||||
|
||||
worker_pool_->SetExecutableName(QLatin1String(kWorkerExecutableName));
|
||||
QObject::connect(worker_pool_, &WorkerPool<HandlerType>::WorkerFailedToStart, this, &TagReaderClient::WorkerFailedToStart);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::Start() { worker_pool_->Start(); }
|
||||
|
||||
void TagReaderClient::ExitAsync() {
|
||||
QMetaObject::invokeMethod(this, &TagReaderClient::Exit, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void TagReaderClient::Exit() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
moveToThread(original_thread_);
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
message.mutable_is_media_file_request()->set_filename(filename.toStdString());
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
message.mutable_read_file_request()->set_filename(filename.toStdString());
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::WriteFile(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::WriteFileRequest *request = message.mutable_write_file_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
|
||||
request->set_save_tags(save_types.testFlag(SaveType::Tags));
|
||||
request->set_save_playcount(save_types.testFlag(SaveType::PlayCount));
|
||||
request->set_save_rating(save_types.testFlag(SaveType::Rating));
|
||||
request->set_save_cover(save_types.testFlag(SaveType::Cover));
|
||||
|
||||
if (!save_cover_options.cover_filename.isEmpty()) {
|
||||
request->set_cover_filename(save_cover_options.cover_filename.toStdString());
|
||||
}
|
||||
if (!save_cover_options.cover_data.isEmpty()) {
|
||||
request->set_cover_data(save_cover_options.cover_data.toStdString());
|
||||
}
|
||||
if (!save_cover_options.mime_type.isEmpty()) {
|
||||
request->set_cover_mime_type(save_cover_options.mime_type.toStdString());
|
||||
}
|
||||
|
||||
metadata.ToProtobuf(request->mutable_metadata());
|
||||
|
||||
ReplyType *reply = worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::LoadEmbeddedArtRequest *request = message.mutable_load_embedded_art_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::SaveEmbeddedArtRequest *request = message.mutable_save_embedded_art_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
|
||||
if (!save_cover_options.cover_filename.isEmpty()) {
|
||||
request->set_cover_filename(save_cover_options.cover_filename.toStdString());
|
||||
}
|
||||
if (!save_cover_options.cover_data.isEmpty()) {
|
||||
request->set_cover_data(save_cover_options.cover_data.toStdString());
|
||||
}
|
||||
if (!save_cover_options.mime_type.isEmpty()) {
|
||||
request->set_cover_mime_type(save_cover_options.mime_type.toStdString());
|
||||
}
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::SaveSongPlaycount(const QString &filename, const uint playcount) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::SaveSongPlaycountToFileRequest *request = message.mutable_save_song_playcount_to_file_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
request->set_playcount(playcount);
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::SaveSongsPlaycount(const SongList &songs) {
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReply *reply = SaveSongPlaycount(song.url().toLocalFile(), song.playcount());
|
||||
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::SaveSongRating(const QString &filename, const float rating) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::SaveSongRatingToFileRequest *request = message.mutable_save_song_rating_to_file_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
request->set_rating(rating);
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::SaveSongsRating(const SongList &songs) {
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReply *reply = SaveSongRating(song.url().toLocalFile(), song.rating());
|
||||
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool success = false;
|
||||
|
||||
TagReaderReply *reply = IsMediaFile(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::IsMediaFileResponse &response = reply->message().is_media_file_response();
|
||||
if (response.has_success()) {
|
||||
success = response.success();
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return success;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = ReadFile(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::ReadFileResponse &response = reply->message().read_file_response();
|
||||
if (response.has_success()) {
|
||||
if (response.success()) {
|
||||
result.error_code = Result::ErrorCode::Success;
|
||||
if (response.has_metadata()) {
|
||||
song->InitFromProtobuf(response.metadata());
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.error_code = Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = WriteFile(filename, metadata, save_types, save_cover_options);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::WriteFileResponse &response = reply->message().write_file_response();
|
||||
if (response.has_success()) {
|
||||
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = LoadEmbeddedArt(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::LoadEmbeddedArtResponse &response = reply->message().load_embedded_art_response();
|
||||
if (response.has_success()) {
|
||||
if (response.success()) {
|
||||
result.error_code = Result::ErrorCode::Success;
|
||||
if (response.has_data()) {
|
||||
data = QByteArray(response.data().data(), static_cast<qint64>(response.data().size()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.error_code = Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
QByteArray data;
|
||||
Result result = LoadEmbeddedArtBlocking(filename, data);
|
||||
if (result.error_code == Result::ErrorCode::Success && !image.loadFromData(data)) {
|
||||
result.error_code = Result::ErrorCode::Failure;
|
||||
result.error = QObject::tr("Failed to load image from data for %1").arg(filename);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = SaveEmbeddedArt(filename, save_cover_options);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::SaveEmbeddedArtResponse &response = reply->message().save_embedded_art_response();
|
||||
if (response.has_success()) {
|
||||
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = SaveSongPlaycount(filename, playcount);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::SaveSongPlaycountToFileResponse &response = reply->message().save_song_playcount_to_file_response();
|
||||
if (response.has_success()) {
|
||||
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = SaveSongRating(filename, rating);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::SaveSongRatingToFileResponse &response = reply->message().save_song_rating_to_file_response();
|
||||
if (response.has_success()) {
|
||||
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERCLIENT_H
|
||||
#define TAGREADERCLIENT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include "core/messagehandler.h"
|
||||
#include "core/workerpool.h"
|
||||
|
||||
#include "song.h"
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
class QThread;
|
||||
class Song;
|
||||
template<typename HandlerType> class WorkerPool;
|
||||
|
||||
class TagReaderClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderClient(QObject *parent = nullptr);
|
||||
|
||||
using HandlerType = AbstractMessageHandler<spb::tagreader::Message>;
|
||||
using ReplyType = HandlerType::ReplyType;
|
||||
|
||||
void Start();
|
||||
void ExitAsync();
|
||||
|
||||
enum class SaveType {
|
||||
NoType = 0,
|
||||
Tags = 1,
|
||||
PlayCount = 2,
|
||||
Rating = 4,
|
||||
Cover = 8
|
||||
};
|
||||
Q_DECLARE_FLAGS(SaveTypes, SaveType)
|
||||
|
||||
class SaveCoverOptions {
|
||||
public:
|
||||
explicit SaveCoverOptions(const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray(), const QString &_mime_type = QString()) : cover_filename(_cover_filename), cover_data(_cover_data), mime_type(_mime_type) {}
|
||||
explicit SaveCoverOptions(const QString &_cover_filename, const QString &_mime_type = QString()) : cover_filename(_cover_filename), mime_type(_mime_type) {}
|
||||
explicit SaveCoverOptions(const QByteArray &_cover_data, const QString &_mime_type = QString()) : cover_data(_cover_data), mime_type(_mime_type) {}
|
||||
QString cover_filename;
|
||||
QByteArray cover_data;
|
||||
QString mime_type;
|
||||
};
|
||||
|
||||
class Result {
|
||||
public:
|
||||
enum class ErrorCode {
|
||||
Success,
|
||||
Unsupported,
|
||||
Failure,
|
||||
};
|
||||
Result(const ErrorCode _error_code, const QString &_error = QString()) : error_code(_error_code), error(_error) {}
|
||||
ErrorCode error_code;
|
||||
QString error;
|
||||
bool success() const { return error_code == TagReaderClient::Result::ErrorCode::Success; }
|
||||
};
|
||||
|
||||
class Cover {
|
||||
public:
|
||||
explicit Cover(const QByteArray &_data = QByteArray(), const QString &_mime_type = QString()) : data(_data), mime_type(_mime_type) {}
|
||||
QByteArray data;
|
||||
QString mime_type;
|
||||
QString error;
|
||||
};
|
||||
|
||||
ReplyType *IsMediaFile(const QString &filename);
|
||||
ReplyType *ReadFile(const QString &filename);
|
||||
ReplyType *WriteFile(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
|
||||
ReplyType *LoadEmbeddedArt(const QString &filename);
|
||||
ReplyType *SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options);
|
||||
ReplyType *SaveSongPlaycount(const QString &filename, const uint playcount);
|
||||
ReplyType *SaveSongRating(const QString &filename, const float rating);
|
||||
|
||||
// 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.
|
||||
Result ReadFileBlocking(const QString &filename, Song *song);
|
||||
Result WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
|
||||
bool IsMediaFileBlocking(const QString &filename);
|
||||
Result LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data);
|
||||
Result LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image);
|
||||
Result SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options);
|
||||
Result SaveSongPlaycountBlocking(const QString &filename, const uint playcount);
|
||||
Result SaveSongRatingBlocking(const QString &filename, const float rating);
|
||||
|
||||
// TODO: Make this not a singleton
|
||||
static TagReaderClient *Instance() { return sInstance; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void ExitFinished();
|
||||
|
||||
private Q_SLOTS:
|
||||
void Exit();
|
||||
void WorkerFailedToStart();
|
||||
|
||||
public Q_SLOTS:
|
||||
void SaveSongsPlaycount(const SongList &songs);
|
||||
void SaveSongsRating(const SongList &songs);
|
||||
|
||||
private:
|
||||
static TagReaderClient *sInstance;
|
||||
|
||||
WorkerPool<HandlerType> *worker_pool_;
|
||||
QList<spb::tagreader::Message> message_queue_;
|
||||
QThread *original_thread_;
|
||||
};
|
||||
|
||||
using TagReaderReply = TagReaderClient::ReplyType;
|
||||
|
||||
#endif // TAGREADERCLIENT_H
|
||||
@@ -63,9 +63,8 @@
|
||||
#include "core/application.h"
|
||||
#include "core/song.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionfilteroptions.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "settings/coverssettingspage.h"
|
||||
@@ -447,7 +446,7 @@ void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:{
|
||||
if (song.art_embedded() && !song.url().isEmpty() && song.url().isValid() && song.url().isLocalFile()) {
|
||||
QImage image_embedded_cover;
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song.url().toLocalFile(), image_embedded_cover);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song.url().toLocalFile(), image_embedded_cover);
|
||||
if (result.success() && !image_embedded_cover.isNull()) {
|
||||
QPixmap pixmap = QPixmap::fromImage(image_embedded_cover);
|
||||
if (!pixmap.isNull()) {
|
||||
@@ -737,8 +736,8 @@ void AlbumCoverChoiceController::SaveCoverEmbeddedToSong(const Song &song, const
|
||||
QMutexLocker l(&mutex_cover_save_tasks_);
|
||||
cover_save_tasks_.append(song);
|
||||
const bool art_embedded = !image_data.isNull();
|
||||
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(song.url().toLocalFile(), TagReaderClient::SaveCoverOptions(cover_filename, image_data, mime_type));
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, song, art_embedded]() { SaveEmbeddedCoverFinished(reply, song, art_embedded); });
|
||||
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(song.url().toLocalFile(), SaveTagCoverData(cover_filename, image_data, mime_type));
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, song, art_embedded]() { SaveEmbeddedCoverFinished(reply, song, art_embedded); });
|
||||
|
||||
}
|
||||
|
||||
@@ -815,12 +814,12 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded) {
|
||||
void AlbumCoverChoiceController::SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, Song song, const bool art_embedded) {
|
||||
|
||||
if (!cover_save_tasks_.contains(song)) return;
|
||||
cover_save_tasks_.removeAll(song);
|
||||
|
||||
if (reply->is_successful()) {
|
||||
if (reply->success()) {
|
||||
SaveArtEmbeddedToSong(&song, art_embedded);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
#include <QMutex>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "utilities/coveroptions.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
@@ -160,7 +160,7 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
|
||||
private Q_SLOTS:
|
||||
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
|
||||
void SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded);
|
||||
void SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, Song song, const bool art_embedded);
|
||||
|
||||
Q_SIGNALS:
|
||||
void Error(const QString &error);
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
#include "core/logging.h"
|
||||
#include "core/networkaccessmanager.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "utilities/mimeutils.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
@@ -318,7 +318,7 @@ AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, cons
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadEmbeddedImage(TaskPtr task) {
|
||||
|
||||
if (task->art_embedded && task->song_url.isValid() && task->song_url.isLocalFile()) {
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile(), task->album_cover.image_data);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverDataBlocking(task->song_url.toLocalFile(), task->album_cover.image_data);
|
||||
if (result.success() && !task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
|
||||
return LoadImageResult(AlbumCoverLoaderResult::Type::Embedded, LoadImageResult::Status::Success);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
#include "core/logging.h"
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/database.h"
|
||||
#include "core/sqlrow.h"
|
||||
#include "core/settings.h"
|
||||
@@ -77,6 +76,7 @@
|
||||
#include "utilities/screenutils.h"
|
||||
#include "widgets/forcescrollperpixel.h"
|
||||
#include "widgets/searchfield.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
@@ -758,9 +758,9 @@ void AlbumCoverManager::SaveCoverToFile() {
|
||||
return;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song.art_embedded()) {
|
||||
const TagReaderClient::Result tagreaderclient_result = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile(), result.image_data);
|
||||
const TagReaderResult tagreaderclient_result = TagReaderClient::Instance()->LoadCoverDataBlocking(song.url().toLocalFile(), result.image_data);
|
||||
if (!tagreaderclient_result.success()) {
|
||||
qLog(Error) << "Could not load embedded art from" << song.url() << tagreaderclient_result.error;
|
||||
qLog(Error) << "Could not load embedded art from" << song.url() << tagreaderclient_result.error_string();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -845,8 +845,8 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
|
||||
case CoverOptions::CoverType::Embedded:{
|
||||
for (const QUrl &url : std::as_const(album_item->urls)) {
|
||||
const bool art_embedded = !result.image_data.isEmpty();
|
||||
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.image_data, result.mime_type));
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(url.toLocalFile(), SaveTagCoverData(result.image_data, result.mime_type));
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
|
||||
});
|
||||
cover_save_tasks_.insert(album_item, url);
|
||||
@@ -995,8 +995,8 @@ void AlbumCoverManager::SaveAndSetCover(AlbumItem *album_item, const AlbumCoverI
|
||||
if (album_cover_choice_controller_->get_save_album_cover_type() == CoverOptions::CoverType::Embedded && Song::save_embedded_cover_supported(filetype) && !has_cue) {
|
||||
for (const QUrl &url : urls) {
|
||||
const bool art_embedded = !result.image_data.isEmpty();
|
||||
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.cover_url.isValid() ? result.cover_url.toLocalFile() : QString(), result.image_data, result.mime_type));
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(url.toLocalFile(), SaveTagCoverData(result.cover_url.isValid() ? result.cover_url.toLocalFile() : QString(), result.image_data, result.mime_type));
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
|
||||
});
|
||||
cover_save_tasks_.insert(album_item, url);
|
||||
@@ -1094,13 +1094,13 @@ bool AlbumCoverManager::ItemHasCover(const AlbumItem &album_item) const {
|
||||
return album_item.icon().cacheKey() != icon_nocover_item_.cacheKey();
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded) {
|
||||
void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded) {
|
||||
|
||||
if (cover_save_tasks_.contains(album_item, url)) {
|
||||
cover_save_tasks_.remove(album_item, url);
|
||||
}
|
||||
|
||||
if (!reply->is_successful()) {
|
||||
if (!reply->success()) {
|
||||
Q_EMIT Error(tr("Could not save cover to file %1.").arg(url.toLocalFile()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverchoicecontroller.h"
|
||||
@@ -185,7 +185,7 @@ class AlbumCoverManager : public QMainWindow {
|
||||
void UpdateCoverInList(AlbumItem *album_item, const QUrl &cover);
|
||||
void UpdateExportStatus(const int exported, const int skipped, const int max);
|
||||
|
||||
void SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded);
|
||||
void SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded);
|
||||
|
||||
private:
|
||||
Ui_CoverManager *ui_;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <QImage>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverexport.h"
|
||||
#include "coverexportrunnable.h"
|
||||
@@ -78,7 +78,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song_.art_embedded() && dialog_result_.export_embedded_) {
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile(), image);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song_.url().toLocalFile(), image);
|
||||
if (result.success() && !image.isNull()) {
|
||||
extension = "jpg"_L1;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ void CoverExportRunnable::ExportCover() {
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song_.art_embedded() && dialog_result_.export_embedded_) {
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile(), image);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song_.url().toLocalFile(), image);
|
||||
if (result.success() && !image.isNull()) {
|
||||
embedded_cover = true;
|
||||
extension = "jpg"_L1;
|
||||
|
||||
@@ -73,13 +73,13 @@
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/settings.h"
|
||||
#include "utilities/strutils.h"
|
||||
#include "utilities/timeutils.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "utilities/coverutils.h"
|
||||
#include "utilities/coveroptions.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "widgets/busyindicator.h"
|
||||
#include "widgets/lineedit.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
@@ -99,7 +99,6 @@
|
||||
#include "covermanager/albumcoverimageresult.h"
|
||||
#include "edittagdialog.h"
|
||||
#include "ui_edittagdialog.h"
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
@@ -401,7 +400,7 @@ QList<EditTagDialog::Data> EditTagDialog::LoadData(const SongList &songs) {
|
||||
if (song.IsEditable()) {
|
||||
// Try reloading the tags from file
|
||||
Song copy(song);
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), ©);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), ©);
|
||||
if (result.success() && copy.is_valid()) {
|
||||
copy.MergeUserSetData(song, false, false);
|
||||
ret << Data(copy);
|
||||
@@ -1276,31 +1275,31 @@ void EditTagDialog::SaveData() {
|
||||
if (ref.current_.originalyear() <= 0) { ref.current_.set_originalyear(-1); }
|
||||
if (ref.current_.lastplayed() <= 0) { ref.current_.set_lastplayed(-1); }
|
||||
++save_tag_pending_;
|
||||
TagReaderClient::SaveCoverOptions savecover_options;
|
||||
SaveTagCoverData save_tag_cover_data;
|
||||
if (save_embedded_cover && ref.cover_action_ == UpdateCoverAction::New) {
|
||||
if (!ref.cover_result_.image.isNull()) {
|
||||
savecover_options.mime_type = ref.cover_result_.mime_type;
|
||||
save_tag_cover_data.cover_mimetype = ref.cover_result_.mime_type;
|
||||
}
|
||||
else if (!embedded_cover_from_file.isEmpty()) {
|
||||
savecover_options.cover_filename = embedded_cover_from_file;
|
||||
save_tag_cover_data.cover_filename = embedded_cover_from_file;
|
||||
}
|
||||
savecover_options.cover_data = ref.cover_result_.image_data;
|
||||
save_tag_cover_data.cover_data = ref.cover_result_.image_data;
|
||||
}
|
||||
TagReaderClient::SaveTypes save_types;
|
||||
TagReaderClient::SaveOptions save_tags_options;
|
||||
if (save_tags) {
|
||||
save_types |= TagReaderClient::SaveType::Tags;
|
||||
save_tags_options |= TagReaderClient::SaveOption::Tags;
|
||||
}
|
||||
if (save_playcount) {
|
||||
save_types |= TagReaderClient::SaveType::PlayCount;
|
||||
save_tags_options |= TagReaderClient::SaveOption::Playcount;
|
||||
}
|
||||
if (save_rating) {
|
||||
save_types |= TagReaderClient::SaveType::Rating;
|
||||
save_tags_options |= TagReaderClient::SaveOption::Rating;
|
||||
}
|
||||
if (save_embedded_cover) {
|
||||
save_types |= TagReaderClient::SaveType::Cover;
|
||||
save_tags_options |= TagReaderClient::SaveOption::Cover;
|
||||
}
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(ref.current_.url().toLocalFile(), ref.current_, save_types, savecover_options);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_); }, Qt::QueuedConnection);
|
||||
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(ref.current_.url().toLocalFile(), ref.current_, save_tags_options, save_tag_cover_data);
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_); }, Qt::QueuedConnection);
|
||||
}
|
||||
// If the cover was changed, but no tags written, make sure to update the collection.
|
||||
else if (ref.cover_action_ != UpdateCoverAction::None && !ref.current_.effective_albumartist().isEmpty() && !ref.current_.album().isEmpty()) {
|
||||
@@ -1451,15 +1450,12 @@ void EditTagDialog::UpdateLyrics(const quint64 id, const QString &provider, cons
|
||||
|
||||
}
|
||||
|
||||
void EditTagDialog::SongSaveTagsComplete(TagReaderReply *reply, const QString &filename, Song song, const UpdateCoverAction cover_action) {
|
||||
void EditTagDialog::SongSaveTagsComplete(TagReaderReplyPtr reply, const QString &filename, Song song, const UpdateCoverAction cover_action) {
|
||||
|
||||
--save_tag_pending_;
|
||||
const bool success = reply->message().write_file_response().success();
|
||||
QString error;
|
||||
if (!success && reply->message().write_file_response().has_error()) {
|
||||
error = QString::fromStdString(reply->message().write_file_response().error());
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
const bool success = reply->success();
|
||||
const QString error = reply->error();
|
||||
|
||||
if (success) {
|
||||
if (song.is_collection_song()) {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
#include <QImage>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
@@ -133,7 +133,7 @@ class EditTagDialog : public QDialog {
|
||||
void PreviousSong();
|
||||
void NextSong();
|
||||
|
||||
void SongSaveTagsComplete(TagReaderReply *reply, const QString &filename, Song song, const UpdateCoverAction cover_action);
|
||||
void SongSaveTagsComplete(TagReaderReplyPtr reply, const QString &filename, Song song, const UpdateCoverAction cover_action);
|
||||
|
||||
private:
|
||||
struct FieldData {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "widgets/busyindicator.h"
|
||||
#include "trackselectiondialog.h"
|
||||
#include "ui_trackselectiondialog.h"
|
||||
@@ -278,9 +278,9 @@ void TrackSelectionDialog::SaveData(const QList<Data> &data) {
|
||||
copy.set_track(new_metadata.track());
|
||||
copy.set_year(new_metadata.year());
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->WriteFileBlocking(copy.url().toLocalFile(), copy, TagReaderClient::SaveType::Tags, TagReaderClient::SaveCoverOptions());
|
||||
const TagReaderResult result = TagReaderClient::Instance()->WriteFileBlocking(copy.url().toLocalFile(), copy, TagReaderClient::SaveOption::Tags, SaveTagCoverData());
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Failed to write new auto-tags to" << copy.url().toLocalFile() << result.error;
|
||||
qLog(Error) << "Failed to write new auto-tags to" << copy.url().toLocalFile() << result.error_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/song.h"
|
||||
#include "utilities/strutils.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "organize.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
# include "transcoder/transcoder.h"
|
||||
@@ -245,9 +245,9 @@ void Organize::ProcessSomeFiles() {
|
||||
}
|
||||
}
|
||||
else if (destination_->source() == Song::Source::Device) {
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(task.song_info_.song_.url().toLocalFile(), job.cover_image_);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(task.song_info_.song_.url().toLocalFile(), job.cover_image_);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not load embedded art from" << task.song_info_.song_.url() << result.error;
|
||||
qLog(Error) << "Could not load embedded art from" << task.song_info_.song_.url() << result.error_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,12 +59,12 @@
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/settings.h"
|
||||
#include "utilities/strutils.h"
|
||||
#include "utilities/screenutils.h"
|
||||
#include "widgets/freespacebar.h"
|
||||
#include "widgets/linetextedit.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "organize.h"
|
||||
#include "organizeformat.h"
|
||||
@@ -423,12 +423,12 @@ SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
|
||||
if (result.success() && song.is_valid()) {
|
||||
songs << song;
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Could not read file" << filename << result.error;
|
||||
qLog(Error) << "Could not read file" << filename << result.error_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/song.h"
|
||||
#include "core/settings.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collection.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionplaylistitem.h"
|
||||
@@ -418,9 +418,9 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
|
||||
if (!set_column_value(song, static_cast<Column>(idx.column()), value)) return false;
|
||||
|
||||
if (song.url().isLocalFile()) {
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
|
||||
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
|
||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(idx);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index, item]() { SongSaveComplete(reply, persistent_index, item->OriginalMetadata()); }, Qt::QueuedConnection);
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index, item]() { SongSaveComplete(reply, persistent_index, item->OriginalMetadata()); }, Qt::QueuedConnection);
|
||||
}
|
||||
else if (song.is_radio()) {
|
||||
item->SetMetadata(song);
|
||||
@@ -431,18 +431,18 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
|
||||
|
||||
}
|
||||
|
||||
void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata) {
|
||||
void Playlist::SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx, const Song &old_metadata) {
|
||||
|
||||
if (reply->is_successful() && idx.isValid()) {
|
||||
if (reply->message().write_file_response().success()) {
|
||||
if (reply->success() && idx.isValid()) {
|
||||
if (reply->success()) {
|
||||
ItemReload(idx, old_metadata, true);
|
||||
}
|
||||
else {
|
||||
if (reply->request_message().write_file_response().has_error()) {
|
||||
Q_EMIT Error(tr("Could not write metadata to %1: %2").arg(QString::fromStdString(reply->request_message().write_file_request().filename()), QString::fromStdString(reply->request_message().write_file_response().error())));
|
||||
if (reply->error().isEmpty()) {
|
||||
Q_EMIT Error(tr("Could not write metadata to %1").arg(reply->filename()));
|
||||
}
|
||||
else {
|
||||
Q_EMIT Error(tr("Could not write metadata to %1").arg(QString::fromStdString(reply->request_message().write_file_request().filename())));
|
||||
Q_EMIT Error(tr("Could not write metadata to %1: %2").arg(reply->filename(), reply->error()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "playlistitem.h"
|
||||
#include "playlistsequence.h"
|
||||
@@ -350,7 +350,7 @@ class Playlist : public QAbstractListModel {
|
||||
void TracksDequeued();
|
||||
void TracksEnqueued(const QModelIndex &parent_idx, const int begin, const int end);
|
||||
void QueueLayoutChanged();
|
||||
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata);
|
||||
void SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx, const Song &old_metadata);
|
||||
void ItemReloadComplete(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit);
|
||||
void ItemsLoaded();
|
||||
void ScheduleSave();
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
#include "core/sqlrow.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "playlistitem.h"
|
||||
#include "songplaylistitem.h"
|
||||
|
||||
@@ -44,9 +44,9 @@ void SongPlaylistItem::Reload() {
|
||||
|
||||
if (!song_.url().isLocalFile()) return;
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not reload file" << song_.url() << result.error;
|
||||
qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
|
||||
}
|
||||
|
||||
UpdateTemporaryMetadata(song_);
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "settings/playlistsettingspage.h"
|
||||
#include "parserbase.h"
|
||||
@@ -105,9 +105,9 @@ void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning
|
||||
}
|
||||
}
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not read file" << filename << result.error;
|
||||
qLog(Error) << "Could not read file" << filename << result.error_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
27
src/tagreader/albumcovertagdata.cpp
Normal file
27
src/tagreader/albumcovertagdata.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "albumcovertagdata.h"
|
||||
|
||||
AlbumCoverTagData::AlbumCoverTagData(const QByteArray &_data, const QString &_mimetype)
|
||||
: data(_data),
|
||||
mimetype(_mimetype) {}
|
||||
34
src/tagreader/albumcovertagdata.h
Normal file
34
src/tagreader/albumcovertagdata.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERTAGDATA_H
|
||||
#define ALBUMCOVERTAGDATA_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
class AlbumCoverTagData {
|
||||
public:
|
||||
explicit AlbumCoverTagData(const QByteArray &_data = QByteArray(), const QString &_mimetype = QString());
|
||||
QByteArray data;
|
||||
QString mimetype;
|
||||
QString error;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERTAGDATA_H
|
||||
32
src/tagreader/savetagcoverdata.cpp
Normal file
32
src/tagreader/savetagcoverdata.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
SaveTagCoverData::SaveTagCoverData(const QString &_cover_filename, const QByteArray &_cover_data, const QString &_cover_mimetype)
|
||||
: cover_filename(_cover_filename),
|
||||
cover_data(_cover_data),
|
||||
cover_mimetype(_cover_mimetype) {}
|
||||
|
||||
SaveTagCoverData::SaveTagCoverData(const QByteArray &_cover_data, const QString &_cover_mimetype)
|
||||
: cover_data(_cover_data),
|
||||
cover_mimetype(_cover_mimetype) {}
|
||||
35
src/tagreader/savetagcoverdata.h
Normal file
35
src/tagreader/savetagcoverdata.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SAVETAGCOVERDATA_H
|
||||
#define SAVETAGCOVERDATA_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
class SaveTagCoverData {
|
||||
public:
|
||||
SaveTagCoverData(const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray(), const QString &_cover_mimetype = QString());
|
||||
SaveTagCoverData(const QByteArray &_cover_data, const QString &_cover_mimetype = QString());
|
||||
QString cover_filename;
|
||||
QByteArray cover_data;
|
||||
QString cover_mimetype;
|
||||
};
|
||||
|
||||
#endif // SAVETAGCOVERDATA_H
|
||||
34
src/tagreader/savetagsoptions.h
Normal file
34
src/tagreader/savetagsoptions.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SAVETAGSOPTIONS_H
|
||||
#define SAVETAGSOPTIONS_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
enum class SaveTagsOption {
|
||||
NoType = 0,
|
||||
Tags = 1,
|
||||
Playcount = 2,
|
||||
Rating = 4,
|
||||
Cover = 8
|
||||
};
|
||||
Q_DECLARE_FLAGS(SaveTagsOptions, SaveTagsOption)
|
||||
|
||||
#endif // SAVETAGSOPTIONS_H
|
||||
110
src/tagreader/tagreaderbase.cpp
Normal file
110
src/tagreader/tagreaderbase.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
#include <QBuffer>
|
||||
#include <QImage>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderbase.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
TagReaderBase::TagReaderBase() = default;
|
||||
TagReaderBase::~TagReaderBase() = default;
|
||||
|
||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||
|
||||
if (POPM_rating < 0x01) return 0.0F;
|
||||
if (POPM_rating < 0x40) return 0.20F;
|
||||
if (POPM_rating < 0x80) return 0.40F;
|
||||
if (POPM_rating < 0xC0) return 0.60F;
|
||||
if (POPM_rating < 0xFC) return 0.80F;
|
||||
|
||||
return 1.0F;
|
||||
|
||||
}
|
||||
|
||||
int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
||||
|
||||
if (rating < 0.20) return 0x00;
|
||||
if (rating < 0.40) return 0x01;
|
||||
if (rating < 0.60) return 0x40;
|
||||
if (rating < 0.80) return 0x80;
|
||||
if (rating < 1.0) return 0xC0;
|
||||
|
||||
return 0xFF;
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverTagData TagReaderBase::LoadAlbumCoverTagData(const QString &song_filename, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
const QString &cover_filename = save_tag_cover_data.cover_filename;
|
||||
QByteArray cover_data = save_tag_cover_data.cover_data;
|
||||
QString cover_mimetype = save_tag_cover_data.cover_mimetype;
|
||||
|
||||
if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
|
||||
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
|
||||
QFile file(cover_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
|
||||
return AlbumCoverTagData();
|
||||
}
|
||||
cover_data = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (!cover_data.isEmpty()) {
|
||||
if (cover_mimetype.isEmpty()) {
|
||||
cover_mimetype = QMimeDatabase().mimeTypeForData(cover_data).name();
|
||||
}
|
||||
if (cover_mimetype == "image/jpeg"_L1) {
|
||||
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||
return AlbumCoverTagData(cover_data, cover_mimetype);
|
||||
}
|
||||
if (cover_mimetype == "image/png"_L1) {
|
||||
qLog(Debug) << "Using cover from PNG data for" << song_filename;
|
||||
return AlbumCoverTagData(cover_data, cover_mimetype);
|
||||
}
|
||||
// Convert image to JPEG.
|
||||
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
|
||||
QImage cover_image;
|
||||
if (!cover_image.loadFromData(cover_data)) {
|
||||
qLog(Error) << "Failed to load image from cover data for" << song_filename;
|
||||
return AlbumCoverTagData();
|
||||
}
|
||||
cover_data.clear();
|
||||
QBuffer buffer(&cover_data);
|
||||
if (buffer.open(QIODevice::WriteOnly)) {
|
||||
cover_image.save(&buffer, "JPEG");
|
||||
buffer.close();
|
||||
}
|
||||
return AlbumCoverTagData(cover_data, u"image/jpeg"_s);
|
||||
}
|
||||
|
||||
return AlbumCoverTagData();
|
||||
|
||||
}
|
||||
61
src/tagreader/tagreaderbase.h
Normal file
61
src/tagreader/tagreaderbase.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERBASE_H
|
||||
#define TAGREADERBASE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreaderresult.h"
|
||||
#include "savetagsoptions.h"
|
||||
#include "savetagcoverdata.h"
|
||||
#include "albumcovertagdata.h"
|
||||
|
||||
class TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderBase();
|
||||
virtual ~TagReaderBase();
|
||||
|
||||
virtual TagReaderResult IsMediaFile(const QString &filename) const = 0;
|
||||
|
||||
virtual TagReaderResult ReadFile(const QString &filename, Song *song) const = 0;
|
||||
virtual TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const = 0;
|
||||
|
||||
virtual TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const = 0;
|
||||
virtual TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const = 0;
|
||||
|
||||
virtual TagReaderResult SaveSongPlaycount(const QString &filename, const uint playcount) const = 0;
|
||||
virtual TagReaderResult SaveSongRating(const QString &filename, const float rating) const = 0;
|
||||
|
||||
protected:
|
||||
static float ConvertPOPMRating(const int POPM_rating);
|
||||
static int ConvertToPOPMRating(const float rating);
|
||||
|
||||
static AlbumCoverTagData LoadAlbumCoverTagData(const QString &song_filename, const SaveTagCoverData &save_tag_cover_data);
|
||||
|
||||
Q_DISABLE_COPY(TagReaderBase)
|
||||
};
|
||||
|
||||
#endif // TAGREADERBASE_H
|
||||
423
src/tagreader/tagreaderclient.cpp
Normal file
423
src/tagreader/tagreaderclient.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreaderclient.h"
|
||||
#include "tagreadertaglib.h"
|
||||
#include "tagreaderresult.h"
|
||||
#include "tagreaderrequest.h"
|
||||
#include "tagreaderismediafilerequest.h"
|
||||
#include "tagreaderreadfilerequest.h"
|
||||
#include "tagreaderwritefilerequest.h"
|
||||
#include "tagreaderloadcoverdatarequest.h"
|
||||
#include "tagreaderloadcoverimagerequest.h"
|
||||
#include "tagreadersavecoverrequest.h"
|
||||
#include "tagreadersaveplaycountrequest.h"
|
||||
#include "tagreadersaveratingrequest.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderreadfilereply.h"
|
||||
#include "tagreaderloadcoverdatareply.h"
|
||||
#include "tagreaderloadcoverimagereply.h"
|
||||
|
||||
using std::dynamic_pointer_cast;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
TagReaderClient *TagReaderClient::sInstance = nullptr;
|
||||
|
||||
TagReaderClient::TagReaderClient(QObject *parent)
|
||||
: QObject(parent),
|
||||
original_thread_(thread()),
|
||||
abort_(false),
|
||||
processing_(false) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
sInstance = this;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ExitAsync() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
abort_ = true;
|
||||
QMetaObject::invokeMethod(this, &TagReaderClient::Exit, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::Exit() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
moveToThread(original_thread_);
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::HaveRequests() const {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_requests_);
|
||||
return !requests_.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::EnqueueRequest(TagReaderRequestPtr request) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_requests_);
|
||||
requests_.enqueue(request);
|
||||
}
|
||||
|
||||
if (!processing_.value()) {
|
||||
ProcessRequestsAsync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TagReaderRequestPtr TagReaderClient::DequeueRequest() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_requests_);
|
||||
if (requests_.isEmpty()) return TagReaderRequestPtr();
|
||||
return requests_.dequeue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ProcessRequestsAsync() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
QMetaObject::invokeMethod(this, &TagReaderClient::ProcessRequests, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ProcessRequests() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
processing_ = true;
|
||||
|
||||
const QScopeGuard scopeguard_processing = qScopeGuard([this]() {
|
||||
processing_ = false;
|
||||
});
|
||||
|
||||
while (HaveRequests()) {
|
||||
if (abort_.value()) return;
|
||||
ProcessRequest(DequeueRequest());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ProcessRequest(TagReaderRequestPtr request) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
TagReaderReplyPtr reply = request->reply;
|
||||
|
||||
TagReaderResult result;
|
||||
|
||||
if (TagReaderIsMediaFileRequestPtr is_media_file_request = std::dynamic_pointer_cast<TagReaderIsMediaFileRequest>(request)) {
|
||||
result = tagreader_.IsMediaFile(is_media_file_request->filename);
|
||||
if (result.error_code == TagReaderResult::ErrorCode::Unsupported) {
|
||||
result = gmereader_.IsMediaFile(is_media_file_request->filename);
|
||||
}
|
||||
}
|
||||
else if (TagReaderReadFileRequestPtr read_file_request = std::dynamic_pointer_cast<TagReaderReadFileRequest>(request)) {
|
||||
Song song;
|
||||
result = ReadFileBlocking(read_file_request->filename, &song);
|
||||
if (result.error_code == TagReaderResult::ErrorCode::Unsupported) {
|
||||
result = gmereader_.ReadFile(read_file_request->filename, &song);
|
||||
}
|
||||
if (result.success()) {
|
||||
if (TagReaderReadFileReplyPtr read_file_reply = std::dynamic_pointer_cast<TagReaderReadFileReply>(reply)) {
|
||||
read_file_reply->set_song(song);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TagReaderWriteFileRequestPtr write_file_request = dynamic_pointer_cast<TagReaderWriteFileRequest>(request)) {
|
||||
result = WriteFileBlocking(write_file_request->filename, write_file_request->song, write_file_request->save_tags_options, write_file_request->save_tag_cover_data);
|
||||
}
|
||||
else if (TagReaderLoadCoverDataRequestPtr load_cover_data_request = dynamic_pointer_cast<TagReaderLoadCoverDataRequest>(request)) {
|
||||
QByteArray cover_data;
|
||||
result = LoadCoverDataBlocking(load_cover_data_request->filename, cover_data);
|
||||
if (result.success()) {
|
||||
if (TagReaderLoadCoverDataReplyPtr load_cover_data_reply = std::dynamic_pointer_cast<TagReaderLoadCoverDataReply>(reply)) {
|
||||
load_cover_data_reply->set_data(cover_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TagReaderLoadCoverImageRequestPtr load_cover_image_request = dynamic_pointer_cast<TagReaderLoadCoverImageRequest>(request)) {
|
||||
QImage cover_image;
|
||||
result = LoadCoverImageBlocking(load_cover_image_request->filename, cover_image);
|
||||
if (result.success()) {
|
||||
if (TagReaderLoadCoverImageReplyPtr load_cover_image_reply = std::dynamic_pointer_cast<TagReaderLoadCoverImageReply>(reply)) {
|
||||
load_cover_image_reply->set_image(cover_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TagReaderSaveCoverRequestPtr save_cover_request = dynamic_pointer_cast<TagReaderSaveCoverRequest>(request)) {
|
||||
result = SaveCoverBlocking(save_cover_request->filename, save_cover_request->save_tag_cover_data);
|
||||
}
|
||||
else if (TagReaderSavePlaycountRequestPtr save_playcount_request = dynamic_pointer_cast<TagReaderSavePlaycountRequest>(request)) {
|
||||
result = SaveSongPlaycountBlocking(save_playcount_request->filename, save_playcount_request->playcount);
|
||||
}
|
||||
else if (TagReaderSaveRatingRequestPtr save_rating_request = dynamic_pointer_cast<TagReaderSaveRatingRequest>(request)) {
|
||||
result = SaveSongRatingBlocking(save_rating_request->filename, save_rating_request->rating);
|
||||
}
|
||||
|
||||
reply->set_result(result);
|
||||
|
||||
reply->Finish();
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) const {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
return tagreader_.IsMediaFile(filename).success();
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::IsMediaFileAsync(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderIsMediaFileRequestPtr request = TagReaderIsMediaFileRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
|
||||
|
||||
return tagreader_.ReadFile(filename, song);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReadFileReplyPtr TagReaderClient::ReadFileAsync(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReadFileReplyPtr reply = TagReaderReply::Create<TagReaderReadFileReply>(filename);
|
||||
|
||||
TagReaderReadFileRequestPtr request = TagReaderReadFileRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderWriteFileRequestPtr request = TagReaderWriteFileRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
request->song = song;
|
||||
request->save_tags_options = save_tags_options;
|
||||
request->save_tag_cover_data = save_tag_cover_data;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::LoadCoverDataBlocking(const QString &filename, QByteArray &data) {
|
||||
|
||||
return tagreader_.LoadEmbeddedCover(filename, data);
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::LoadCoverImageBlocking(const QString &filename, QImage &image) {
|
||||
|
||||
QByteArray data;
|
||||
TagReaderResult result = LoadCoverDataBlocking(filename, data);
|
||||
if (result.error_code == TagReaderResult::ErrorCode::Success && !image.loadFromData(data)) {
|
||||
result.error_code = TagReaderResult::ErrorCode::Unsupported;
|
||||
result.error_text = QObject::tr("Failed to load image from data for %1").arg(filename);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderLoadCoverDataReplyPtr TagReaderClient::LoadCoverDataAsync(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderLoadCoverDataReplyPtr reply = TagReaderReply::Create<TagReaderLoadCoverDataReply>(filename);
|
||||
|
||||
TagReaderLoadCoverDataRequestPtr request = TagReaderLoadCoverDataRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderLoadCoverImageReplyPtr TagReaderClient::LoadCoverImageAsync(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderLoadCoverImageReplyPtr reply = TagReaderReply::Create<TagReaderLoadCoverImageReply>(filename);
|
||||
|
||||
TagReaderLoadCoverImageRequestPtr request = TagReaderLoadCoverImageRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::SaveCoverBlocking(const QString &filename, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
return tagreader_.SaveEmbeddedCover(filename, save_tag_cover_data);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::SaveCoverAsync(const QString &filename, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderSaveCoverRequestPtr request = TagReaderSaveCoverRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
request->save_tag_cover_data = save_tag_cover_data;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::SaveSongPlaycountAsync(const QString &filename, const uint playcount) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderSavePlaycountRequestPtr request = TagReaderSavePlaycountRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
request->playcount = playcount;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) {
|
||||
|
||||
return tagreader_.SaveSongPlaycount(filename, playcount);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::SaveSongsPlaycountAsync(const SongList &songs) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReplyPtr reply = SaveSongPlaycountAsync(song.url().toLocalFile(), song.playcount());
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, &*reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) {
|
||||
|
||||
return tagreader_.SaveSongRating(filename, rating);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::SaveSongRatingAsync(const QString &filename, const float rating) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderSaveRatingRequestPtr request = TagReaderSaveRatingRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
request->rating = rating;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::SaveSongsRatingAsync(const SongList &songs) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReplyPtr reply = SaveSongRatingAsync(song.url().toLocalFile(), song.rating());
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, &*reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
117
src/tagreader/tagreaderclient.h
Normal file
117
src/tagreader/tagreaderclient.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERCLIENT_H
|
||||
#define TAGREADERCLIENT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/mutex_protected.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreadertaglib.h"
|
||||
#include "tagreadergme.h"
|
||||
#include "tagreaderrequest.h"
|
||||
#include "tagreaderresult.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderreadfilereply.h"
|
||||
#include "tagreaderloadcoverdatareply.h"
|
||||
#include "tagreaderloadcoverimagereply.h"
|
||||
#include "savetagsoptions.h"
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
class QThread;
|
||||
class Song;
|
||||
|
||||
class TagReaderClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderClient(QObject *parent = nullptr);
|
||||
|
||||
static TagReaderClient *Instance() { return sInstance; }
|
||||
|
||||
void Start();
|
||||
void ExitAsync();
|
||||
|
||||
using SaveOption = SaveTagsOption;
|
||||
using SaveOptions = SaveTagsOptions;
|
||||
|
||||
bool IsMediaFileBlocking(const QString &filename) const;
|
||||
TagReaderReplyPtr IsMediaFileAsync(const QString &filename);
|
||||
|
||||
TagReaderResult ReadFileBlocking(const QString &filename, Song *song);
|
||||
TagReaderReadFileReplyPtr ReadFileAsync(const QString &filename);
|
||||
|
||||
TagReaderResult WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
|
||||
TagReaderReplyPtr WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
|
||||
|
||||
TagReaderResult LoadCoverDataBlocking(const QString &filename, QByteArray &data);
|
||||
TagReaderResult LoadCoverImageBlocking(const QString &filename, QImage &image);
|
||||
TagReaderLoadCoverDataReplyPtr LoadCoverDataAsync(const QString &filename);
|
||||
TagReaderLoadCoverImageReplyPtr LoadCoverImageAsync(const QString &filename);
|
||||
|
||||
TagReaderResult SaveCoverBlocking(const QString &filename, const SaveTagCoverData &save_tag_cover_data);
|
||||
TagReaderReplyPtr SaveCoverAsync(const QString &filename, const SaveTagCoverData &save_tag_cover_data);
|
||||
|
||||
TagReaderReplyPtr SaveSongPlaycountAsync(const QString &filename, const uint playcount);
|
||||
TagReaderResult SaveSongPlaycountBlocking(const QString &filename, const uint playcount);
|
||||
|
||||
TagReaderReplyPtr SaveSongRatingAsync(const QString &filename, const float rating);
|
||||
TagReaderResult SaveSongRatingBlocking(const QString &filename, const float rating);
|
||||
|
||||
private:
|
||||
bool HaveRequests() const;
|
||||
void EnqueueRequest(TagReaderRequestPtr request);
|
||||
TagReaderRequestPtr DequeueRequest();
|
||||
void ProcessRequestsAsync();
|
||||
void ProcessRequest(TagReaderRequestPtr request);
|
||||
|
||||
Q_SIGNALS:
|
||||
void ExitFinished();
|
||||
|
||||
private Q_SLOTS:
|
||||
void Exit();
|
||||
void ProcessRequests();
|
||||
|
||||
public Q_SLOTS:
|
||||
void SaveSongsPlaycountAsync(const SongList &songs);
|
||||
void SaveSongsRatingAsync(const SongList &songs);
|
||||
|
||||
private:
|
||||
static TagReaderClient *sInstance;
|
||||
|
||||
QThread *original_thread_;
|
||||
QQueue<TagReaderRequestPtr> requests_;
|
||||
mutable QMutex mutex_requests_;
|
||||
TagReaderTagLib tagreader_;
|
||||
TagReaderGME gmereader_;
|
||||
mutex_protected<bool> abort_;
|
||||
mutex_protected<bool> processing_;
|
||||
};
|
||||
|
||||
#endif // TAGREADERCLIENT_H
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/messagehandler.h"
|
||||
#include "tagreaderbase.h"
|
||||
#include "tagreadertaglib.h"
|
||||
|
||||
@@ -43,7 +42,7 @@ bool GME::IsSupportedFormat(const QFileInfo &fileinfo) {
|
||||
return fileinfo.exists() && (fileinfo.completeSuffix().endsWith("spc"_L1, Qt::CaseInsensitive) || fileinfo.completeSuffix().endsWith("vgm"_L1), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
TagReaderBase::Result GME::ReadFile(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) {
|
||||
TagReaderResult GME::ReadFile(const QFileInfo &fileinfo, Song *song) {
|
||||
|
||||
if (fileinfo.completeSuffix().endsWith("spc"_L1), Qt::CaseInsensitive) {
|
||||
return SPC::Read(fileinfo, song);
|
||||
@@ -52,7 +51,7 @@ TagReaderBase::Result GME::ReadFile(const QFileInfo &fileinfo, spb::tagreader::S
|
||||
return VGM::Read(fileinfo, song);
|
||||
}
|
||||
|
||||
return TagReaderBase::Result::ErrorCode::Unsupported;
|
||||
return TagReaderResult::ErrorCode::Unsupported;
|
||||
|
||||
}
|
||||
|
||||
@@ -69,18 +68,18 @@ quint32 GME::UnpackBytes32(const char *const bytes, size_t length) {
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result GME::SPC::Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) {
|
||||
TagReaderResult GME::SPC::Read(const QFileInfo &fileinfo, Song *song) {
|
||||
|
||||
QFile file(fileinfo.filePath());
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return TagReaderBase::Result(TagReaderBase::Result::ErrorCode::FileOpenError, file.errorString());
|
||||
return TagReaderResult(TagReaderResult::ErrorCode::FileOpenError, file.errorString());
|
||||
}
|
||||
|
||||
qLog(Debug) << "Reading tags from SPC file" << fileinfo.fileName();
|
||||
|
||||
// Check for header -- more reliable than file name alone.
|
||||
if (!file.read(33).startsWith(QStringLiteral("SNES-SPC700").toLatin1())) {
|
||||
return TagReaderBase::Result::ErrorCode::Unsupported;
|
||||
if (!file.read(33).startsWith("SNES-SPC700")) {
|
||||
return TagReaderResult::ErrorCode::Unsupported;
|
||||
}
|
||||
|
||||
// First order of business -- get any tag values that exist within the core file information.
|
||||
@@ -113,7 +112,7 @@ TagReaderBase::Result GME::SPC::Read(const QFileInfo &fileinfo, spb::tagreader::
|
||||
}
|
||||
|
||||
if (length_in_sec < 0x1FFF) {
|
||||
song->set_length_nanosec(length_in_sec * kNsecPerSec);
|
||||
song->set_length_nanosec(static_cast<qint64>(length_in_sec * kNsecPerSec));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +130,7 @@ TagReaderBase::Result GME::SPC::Read(const QFileInfo &fileinfo, spb::tagreader::
|
||||
// Check for XID6 data -- this is infrequently used, but being able to fill in data from this is ideal before trying to rely on APETAG values.
|
||||
// XID6 format follows EA's binary file format standard named "IFF"
|
||||
file.seek(XID6_OFFSET);
|
||||
if (has_id6 && file.read(4) == QStringLiteral("xid6").toLatin1()) {
|
||||
if (has_id6 && file.read(4) == "xid6") {
|
||||
QByteArray xid6_head_data = file.read(4);
|
||||
if (xid6_head_data.size() >= 4) {
|
||||
qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3];
|
||||
@@ -162,21 +161,21 @@ TagReaderBase::Result GME::SPC::Read(const QFileInfo &fileinfo, spb::tagreader::
|
||||
if (ape.hasAPETag()) {
|
||||
TagLib::Tag *tag = ape.tag();
|
||||
if (!tag) {
|
||||
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||
return TagReaderResult::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
TagReaderTagLib::AssignTagLibStringToStdString(tag->artist(), song->mutable_artist());
|
||||
TagReaderTagLib::AssignTagLibStringToStdString(tag->album(), song->mutable_album());
|
||||
TagReaderTagLib::AssignTagLibStringToStdString(tag->title(), song->mutable_title());
|
||||
TagReaderTagLib::AssignTagLibStringToStdString(tag->genre(), song->mutable_genre());
|
||||
song->set_artist(tag->artist());
|
||||
song->set_album(tag->album());
|
||||
song->set_title(tag->title());
|
||||
song->set_genre(tag->genre());
|
||||
song->set_track(static_cast<std::int32_t>(tag->track()));
|
||||
song->set_year(static_cast<std::int32_t>(tag->year()));
|
||||
}
|
||||
|
||||
song->set_valid(true);
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType_SPC);
|
||||
song->set_filetype(Song::FileType::SPC);
|
||||
|
||||
return TagReaderBase::Result::ErrorCode::Success;
|
||||
return TagReaderResult::ErrorCode::Success;
|
||||
|
||||
}
|
||||
|
||||
@@ -198,23 +197,23 @@ quint64 GME::SPC::ConvertSPCStringToNum(const QByteArray &arr) {
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result GME::VGM::Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) {
|
||||
TagReaderResult GME::VGM::Read(const QFileInfo &fileinfo, Song *song) {
|
||||
|
||||
QFile file(fileinfo.filePath());
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return TagReaderBase::Result(TagReaderBase::Result::ErrorCode::FileOpenError, file.errorString());
|
||||
return TagReaderResult(TagReaderResult::ErrorCode::FileOpenError, file.errorString());
|
||||
}
|
||||
|
||||
qLog(Debug) << "Reading tags from VGM file" << fileinfo.filePath();
|
||||
|
||||
if (!file.read(4).startsWith(QStringLiteral("Vgm ").toLatin1())) {
|
||||
return TagReaderBase::Result::ErrorCode::Unsupported;
|
||||
if (!file.read(4).startsWith("Vgm ")) {
|
||||
return TagReaderResult::ErrorCode::Unsupported;
|
||||
}
|
||||
|
||||
file.seek(GD3_TAG_PTR);
|
||||
QByteArray gd3_head = file.read(4);
|
||||
if (gd3_head.size() < 4) {
|
||||
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||
return TagReaderResult::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
quint64 pt = GME::UnpackBytes32(gd3_head.constData(), gd3_head.size());
|
||||
@@ -226,7 +225,7 @@ TagReaderBase::Result GME::VGM::Read(const QFileInfo &fileinfo, spb::tagreader::
|
||||
quint64 length = 0;
|
||||
|
||||
if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) {
|
||||
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||
return TagReaderResult::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
file.seek(static_cast<qint64>(GD3_TAG_PTR + pt));
|
||||
@@ -243,7 +242,7 @@ TagReaderBase::Result GME::VGM::Read(const QFileInfo &fileinfo, spb::tagreader::
|
||||
fileTagStream.setEncoding(QStringConverter::Utf16);
|
||||
QStringList strings = fileTagStream.readLine(0).split(u'\0');
|
||||
if (strings.count() < 10) {
|
||||
return TagReaderBase::Result::ErrorCode::FileParseError;
|
||||
return TagReaderResult::ErrorCode::FileParseError;
|
||||
}
|
||||
|
||||
// VGM standard dictates string tag data exist in specific order.
|
||||
@@ -253,11 +252,11 @@ TagReaderBase::Result GME::VGM::Read(const QFileInfo &fileinfo, spb::tagreader::
|
||||
song->set_album(strings[2].toStdString());
|
||||
song->set_artist(strings[6].toStdString());
|
||||
song->set_year(strings[8].left(4).toInt());
|
||||
song->set_length_nanosec(length * kNsecPerMsec);
|
||||
song->set_length_nanosec(static_cast<qint64>(length * kNsecPerMsec));
|
||||
song->set_valid(true);
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType_VGM);
|
||||
song->set_filetype(Song::FileType::VGM);
|
||||
|
||||
return TagReaderBase::Result::ErrorCode::Success;
|
||||
return TagReaderResult::ErrorCode::Success;
|
||||
|
||||
}
|
||||
|
||||
@@ -288,61 +287,63 @@ bool GME::VGM::GetPlaybackLength(const QByteArray &sample_count_bytes, const QBy
|
||||
TagReaderGME::TagReaderGME() = default;
|
||||
|
||||
|
||||
bool TagReaderGME::IsMediaFile(const QString &filename) const {
|
||||
TagReaderResult TagReaderGME::IsMediaFile(const QString &filename) const {
|
||||
|
||||
QFileInfo fileinfo(filename);
|
||||
return GME::IsSupportedFormat(fileinfo);
|
||||
return GME::IsSupportedFormat(fileinfo) ? TagReaderResult::ErrorCode::Success : TagReaderResult::ErrorCode::Unsupported;
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
||||
TagReaderResult TagReaderGME::ReadFile(const QString &filename, Song *song) const {
|
||||
|
||||
QFileInfo fileinfo(filename);
|
||||
return GME::ReadFile(fileinfo, song);
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderGME::WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const {
|
||||
TagReaderResult TagReaderGME::WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const {
|
||||
|
||||
Q_UNUSED(filename);
|
||||
Q_UNUSED(request);
|
||||
Q_UNUSED(song);
|
||||
Q_UNUSED(save_tags_options);
|
||||
Q_UNUSED(save_tag_cover_data);
|
||||
|
||||
return Result::ErrorCode::Unsupported;
|
||||
return TagReaderResult::ErrorCode::Unsupported;
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderGME::LoadEmbeddedArt(const QString &filename, QByteArray &data) const {
|
||||
TagReaderResult TagReaderGME::LoadEmbeddedCover(const QString &filename, QByteArray &data) const {
|
||||
|
||||
Q_UNUSED(filename);
|
||||
Q_UNUSED(data);
|
||||
|
||||
return Result::ErrorCode::Unsupported;
|
||||
return TagReaderResult::ErrorCode::Unsupported;
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderGME::SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const {
|
||||
TagReaderResult TagReaderGME::SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const {
|
||||
|
||||
Q_UNUSED(filename);
|
||||
Q_UNUSED(request);
|
||||
Q_UNUSED(save_tag_cover_data);
|
||||
|
||||
return Result::ErrorCode::Unsupported;
|
||||
return TagReaderResult::ErrorCode::Unsupported;
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderGME::SaveSongPlaycountToFile(const QString &filename, const uint playcount) const {
|
||||
TagReaderResult TagReaderGME::SaveSongPlaycount(const QString &filename, const uint playcount) const {
|
||||
|
||||
Q_UNUSED(filename);
|
||||
Q_UNUSED(playcount);
|
||||
|
||||
return Result::ErrorCode::Unsupported;
|
||||
return TagReaderResult::ErrorCode::Unsupported;
|
||||
|
||||
}
|
||||
|
||||
TagReaderBase::Result TagReaderGME::SaveSongRatingToFile(const QString &filename, const float rating) const {
|
||||
TagReaderResult TagReaderGME::SaveSongRating(const QString &filename, const float rating) const {
|
||||
|
||||
Q_UNUSED(filename);
|
||||
Q_UNUSED(rating);
|
||||
|
||||
return Result::ErrorCode::Unsupported;
|
||||
return TagReaderResult::ErrorCode::Unsupported;
|
||||
|
||||
}
|
||||
@@ -25,11 +25,10 @@
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "tagreaderbase.h"
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
namespace GME {
|
||||
bool IsSupportedFormat(const QFileInfo &fileinfo);
|
||||
TagReaderBase::Result ReadFile(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song);
|
||||
TagReaderResult ReadFile(const QFileInfo &fileinfo, Song *song);
|
||||
|
||||
uint32_t UnpackBytes32(const char *const bytes, size_t length);
|
||||
|
||||
@@ -69,7 +68,7 @@ enum class xID6_TYPE {
|
||||
Integer = 0x4
|
||||
};
|
||||
|
||||
TagReaderBase::Result Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song);
|
||||
TagReaderResult Read(const QFileInfo &fileinfo, Song *song);
|
||||
qint16 GetNextMemAddressAlign32bit(qint16 input);
|
||||
quint64 ConvertSPCStringToNum(const QByteArray &arr);
|
||||
} // namespace SPC
|
||||
@@ -85,7 +84,7 @@ constexpr int LOOP_SAMPLE_COUNT = 0x20;
|
||||
constexpr int SAMPLE_TIMEBASE = 44100;
|
||||
constexpr int GST_GME_LOOP_TIME_MS = 8000;
|
||||
|
||||
TagReaderBase::Result Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song);
|
||||
TagReaderResult Read(const QFileInfo &fileinfo, Song *song);
|
||||
// Takes in two QByteArrays, expected to be 4 bytes long. Desired length is returned via output parameter out_length. Returns false on error.
|
||||
bool GetPlaybackLength(const QByteArray &sample_count_bytes, const QByteArray &loop_count_bytes, quint64 &out_length);
|
||||
|
||||
@@ -100,16 +99,16 @@ class TagReaderGME : public TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderGME();
|
||||
|
||||
bool IsMediaFile(const QString &filename) const override;
|
||||
TagReaderResult IsMediaFile(const QString &filename) const override;
|
||||
|
||||
Result ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||
Result WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const override;
|
||||
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
|
||||
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||
|
||||
Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override;
|
||||
Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
||||
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||
|
||||
Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override;
|
||||
Result SaveSongRatingToFile(const QString &filename, const float rating) const override;
|
||||
TagReaderResult SaveSongPlaycount(const QString &filename, const uint playcount) const override;
|
||||
TagReaderResult SaveSongRating(const QString &filename, const float rating) const override;
|
||||
};
|
||||
|
||||
#endif // TAGREADERGME_H
|
||||
24
src/tagreader/tagreaderismediafilerequest.cpp
Normal file
24
src/tagreader/tagreaderismediafilerequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderismediafilerequest.h"
|
||||
|
||||
TagReaderIsMediaFileRequest::TagReaderIsMediaFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
38
src/tagreader/tagreaderismediafilerequest.h
Normal file
38
src/tagreader/tagreaderismediafilerequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERISMEDIAFILEREQUEST_H
|
||||
#define TAGREADERISMEDIAFILEREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderIsMediaFileRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderIsMediaFileRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderIsMediaFileRequest> Create(const QString &filename) { return make_shared<TagReaderIsMediaFileRequest>(filename); }
|
||||
};
|
||||
|
||||
using TagReaderIsMediaFileRequestPtr = std::shared_ptr<TagReaderIsMediaFileRequest>;
|
||||
|
||||
#endif // TAGREADERISMEDIAFILEREQUEST_H
|
||||
40
src/tagreader/tagreaderloadcoverdatareply.cpp
Normal file
40
src/tagreader/tagreaderloadcoverdatareply.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderloadcoverdatareply.h"
|
||||
|
||||
TagReaderLoadCoverDataReply::TagReaderLoadCoverDataReply(const QString &_filename, QObject *parent)
|
||||
: TagReaderReply(_filename, parent) {}
|
||||
|
||||
void TagReaderLoadCoverDataReply::Finish() {
|
||||
|
||||
qLog(Debug) << "Finishing tagreader reply for" << filename_;
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Q_EMIT TagReaderReply::Finished(filename_, result_);
|
||||
Q_EMIT TagReaderLoadCoverDataReply::Finished(filename_, data_, result_);
|
||||
|
||||
QObject::disconnect(this, &TagReaderReply::Finished, nullptr, nullptr);
|
||||
QObject::disconnect(this, &TagReaderLoadCoverDataReply::Finished, nullptr, nullptr);
|
||||
|
||||
}
|
||||
51
src/tagreader/tagreaderloadcoverdatareply.h
Normal file
51
src/tagreader/tagreaderloadcoverdatareply.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERLOADCOVERDATAREPLY_H
|
||||
#define TAGREADERLOADCOVERDATAREPLY_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
class TagReaderLoadCoverDataReply : public TagReaderReply {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderLoadCoverDataReply(const QString &_filename, QObject *parent = nullptr);
|
||||
|
||||
void Finish() override;
|
||||
|
||||
QByteArray data() const { return data_; }
|
||||
void set_data(const QByteArray &data) { data_ = data; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const QString &filename, const QByteArray &data, const TagReaderResult &result);
|
||||
|
||||
private:
|
||||
QByteArray data_;
|
||||
};
|
||||
|
||||
using TagReaderLoadCoverDataReplyPtr = SharedPtr<TagReaderLoadCoverDataReply>;
|
||||
|
||||
#endif // TAGREADERLOADCOVERDATAREPLY_H
|
||||
24
src/tagreader/tagreaderloadcoverdatarequest.cpp
Normal file
24
src/tagreader/tagreaderloadcoverdatarequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderloadcoverdatarequest.h"
|
||||
|
||||
TagReaderLoadCoverDataRequest::TagReaderLoadCoverDataRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
38
src/tagreader/tagreaderloadcoverdatarequest.h
Normal file
38
src/tagreader/tagreaderloadcoverdatarequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERLOADCOVERDATAREQUEST_H
|
||||
#define TAGREADERLOADCOVERDATAREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderLoadCoverDataRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderLoadCoverDataRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderLoadCoverDataRequest> Create(const QString &filename) { return make_shared<TagReaderLoadCoverDataRequest>(filename); }
|
||||
};
|
||||
|
||||
using TagReaderLoadCoverDataRequestPtr = SharedPtr<TagReaderLoadCoverDataRequest>;
|
||||
|
||||
#endif // TAGREADERLOADCOVERDATAREQUEST_H
|
||||
39
src/tagreader/tagreaderloadcoverimagereply.cpp
Normal file
39
src/tagreader/tagreaderloadcoverimagereply.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderloadcoverimagereply.h"
|
||||
|
||||
TagReaderLoadCoverImageReply::TagReaderLoadCoverImageReply(const QString &_filename, QObject *parent)
|
||||
: TagReaderReply(_filename, parent) {}
|
||||
|
||||
void TagReaderLoadCoverImageReply::Finish() {
|
||||
|
||||
qLog(Debug) << "Finishing tagreader reply for" << filename_;
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Q_EMIT TagReaderReply::Finished(filename_, result_);
|
||||
Q_EMIT TagReaderLoadCoverImageReply::Finished(filename_, image_, result_);
|
||||
|
||||
QObject::disconnect(this, &TagReaderLoadCoverImageReply::Finished, nullptr, nullptr);
|
||||
|
||||
}
|
||||
51
src/tagreader/tagreaderloadcoverimagereply.h
Normal file
51
src/tagreader/tagreaderloadcoverimagereply.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERLOADCOVERIMAGEREPLY_H
|
||||
#define TAGREADERLOADCOVERIMAGEREPLY_H
|
||||
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
class TagReaderLoadCoverImageReply : public TagReaderReply {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderLoadCoverImageReply(const QString &_filename, QObject *parent = nullptr);
|
||||
|
||||
void Finish() override;
|
||||
|
||||
QImage image() const { return image_; }
|
||||
void set_image(const QImage &image) { image_ = image; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const QString &filename, const QImage &image, const TagReaderResult &result);
|
||||
|
||||
private:
|
||||
QImage image_;
|
||||
};
|
||||
|
||||
using TagReaderLoadCoverImageReplyPtr = SharedPtr<TagReaderLoadCoverImageReply>;
|
||||
|
||||
#endif // TAGREADERLOADCOVERIMAGEREPLY_H
|
||||
24
src/tagreader/tagreaderloadcoverimagerequest.cpp
Normal file
24
src/tagreader/tagreaderloadcoverimagerequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderloadcoverimagerequest.h"
|
||||
|
||||
TagReaderLoadCoverImageRequest::TagReaderLoadCoverImageRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
38
src/tagreader/tagreaderloadcoverimagerequest.h
Normal file
38
src/tagreader/tagreaderloadcoverimagerequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERLOADCOVERIMAGEREQUEST_H
|
||||
#define TAGREADERLOADCOVERIMAGEREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderLoadCoverImageRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderLoadCoverImageRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderLoadCoverImageRequest> Create(const QString &filename) { return make_shared<TagReaderLoadCoverImageRequest>(filename); }
|
||||
};
|
||||
|
||||
using TagReaderLoadCoverImageRequestPtr = SharedPtr<TagReaderLoadCoverImageRequest>;
|
||||
|
||||
#endif // TAGREADERLOADEMBEDDEDCOVERASIMAGEREQUEST_H
|
||||
39
src/tagreader/tagreaderreadfilereply.cpp
Normal file
39
src/tagreader/tagreaderreadfilereply.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderreadfilereply.h"
|
||||
|
||||
TagReaderReadFileReply::TagReaderReadFileReply(const QString &_filename, QObject *parent)
|
||||
: TagReaderReply(_filename, parent) {}
|
||||
|
||||
void TagReaderReadFileReply::Finish() {
|
||||
|
||||
qLog(Debug) << "Finishing tagreader reply for" << filename_;
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Q_EMIT TagReaderReply::Finished(filename_, result_);
|
||||
Q_EMIT TagReaderReadFileReply::Finished(filename_, song_, result_);
|
||||
|
||||
QObject::disconnect(this, &TagReaderReadFileReply::Finished, nullptr, nullptr);
|
||||
|
||||
}
|
||||
50
src/tagreader/tagreaderreadfilereply.h
Normal file
50
src/tagreader/tagreaderreadfilereply.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERREADFILEREPLY_H
|
||||
#define TAGREADERREADFILEREPLY_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
class TagReaderReadFileReply : public TagReaderReply {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderReadFileReply(const QString &_filename, QObject *parent = nullptr);
|
||||
|
||||
void Finish() override;
|
||||
|
||||
Song song() const { return song_; }
|
||||
void set_song(const Song &song) { song_ = song; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const QString &filename, const Song &song, const TagReaderResult &result);
|
||||
|
||||
private:
|
||||
Song song_;
|
||||
};
|
||||
|
||||
using TagReaderReadFileReplyPtr = SharedPtr<TagReaderReadFileReply>;
|
||||
|
||||
#endif // TAGREADERREADFILEREPLY_H
|
||||
24
src/tagreader/tagreaderreadfilerequest.cpp
Normal file
24
src/tagreader/tagreaderreadfilerequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderreadfilerequest.h"
|
||||
|
||||
TagReaderReadFileRequest::TagReaderReadFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
38
src/tagreader/tagreaderreadfilerequest.h
Normal file
38
src/tagreader/tagreaderreadfilerequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERREADFILEREQUEST_H
|
||||
#define TAGREADERREADFILEREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderReadFileRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderReadFileRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderReadFileRequest> Create(const QString &filename) { return make_shared<TagReaderReadFileRequest>(filename); }
|
||||
};
|
||||
|
||||
using TagReaderReadFileRequestPtr = SharedPtr<TagReaderReadFileRequest>;
|
||||
|
||||
#endif // TAGREADERREADFILEREQUEST_H
|
||||
51
src/tagreader/tagreaderreply.cpp
Normal file
51
src/tagreader/tagreaderreply.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "tagreaderreply.h"
|
||||
|
||||
TagReaderReply::TagReaderReply(const QString &filename, QObject *parent)
|
||||
: QObject(parent),
|
||||
filename_(filename),
|
||||
finished_(false) {
|
||||
|
||||
qLog(Debug) << "New tagreader reply for" << filename_;
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply::~TagReaderReply() {
|
||||
|
||||
qLog(Debug) << "Deleting tagreader reply for" << filename_;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderReply::Finish() {
|
||||
|
||||
qLog(Debug) << "Finishing tagreader reply for" << filename_;
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Q_EMIT Finished(filename_, result_);
|
||||
|
||||
QObject::disconnect(this, &TagReaderReply::Finished, nullptr, nullptr);
|
||||
|
||||
}
|
||||
67
src/tagreader/tagreaderreply.h
Normal file
67
src/tagreader/tagreaderreply.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERREPLY_H
|
||||
#define TAGREADERREPLY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
class TagReaderReply : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderReply(const QString &filename, QObject *parent = nullptr);
|
||||
virtual ~TagReaderReply() override;
|
||||
|
||||
template<typename T>
|
||||
static SharedPtr<T> Create(const QString &filename) {
|
||||
|
||||
SharedPtr<T> reply;
|
||||
reply.reset(new T(filename), [](QObject *obj) { obj->deleteLater(); });
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
QString filename() const { return filename_; }
|
||||
|
||||
TagReaderResult result() const { return result_; }
|
||||
void set_result(const TagReaderResult &result) { result_ = result; }
|
||||
|
||||
bool finished() const { return finished_; }
|
||||
bool success() const { return result_.success(); }
|
||||
QString error() const { return result_.error_string(); }
|
||||
|
||||
virtual void Finish();
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const QString &filename, const TagReaderResult &result);
|
||||
|
||||
protected:
|
||||
const QString filename_;
|
||||
bool finished_;
|
||||
TagReaderResult result_;
|
||||
};
|
||||
|
||||
using TagReaderReplyPtr = SharedPtr<TagReaderReply>;
|
||||
|
||||
#endif // TAGREADERREPLY_H
|
||||
34
src/tagreader/tagreaderrequest.cpp
Normal file
34
src/tagreader/tagreaderrequest.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
TagReaderRequest::TagReaderRequest(const QString &_filename) : filename(_filename) {
|
||||
|
||||
qLog(Debug) << "New tagreader request for" << filename;
|
||||
|
||||
}
|
||||
|
||||
TagReaderRequest::~TagReaderRequest() {
|
||||
|
||||
qLog(Debug) << "Deleting tagreader request for" << filename;
|
||||
|
||||
}
|
||||
38
src/tagreader/tagreaderrequest.h
Normal file
38
src/tagreader/tagreaderrequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERREQUEST_H
|
||||
#define TAGREADERREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderreply.h"
|
||||
|
||||
class TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderRequest(const QString &_filename);
|
||||
virtual ~TagReaderRequest();
|
||||
QString filename;
|
||||
TagReaderReplyPtr reply;
|
||||
};
|
||||
|
||||
using TagReaderRequestPtr = SharedPtr<TagReaderRequest>;
|
||||
|
||||
#endif // TAGREADERREQUEST_H
|
||||
47
src/tagreader/tagreaderresult.cpp
Normal file
47
src/tagreader/tagreaderresult.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
QString TagReaderResult::error_string() const {
|
||||
|
||||
switch (error_code) {
|
||||
case ErrorCode::Success:
|
||||
return QObject::tr("Success");
|
||||
case ErrorCode::Unsupported:
|
||||
return QObject::tr("File is unsupported");
|
||||
case ErrorCode::FilenameMissing:
|
||||
return QObject::tr("Filename is missing");
|
||||
case ErrorCode::FileDoesNotExist:
|
||||
return QObject::tr("File does not exist");
|
||||
case ErrorCode::FileOpenError:
|
||||
return QObject::tr("File could not be opened");
|
||||
case ErrorCode::FileParseError:
|
||||
return QObject::tr("Could not parse file");
|
||||
case ErrorCode::FileSaveError:
|
||||
return QObject::tr("Could save file");
|
||||
case ErrorCode::CustomError:
|
||||
return error_text;
|
||||
}
|
||||
|
||||
return QObject::tr("Unknown error");
|
||||
|
||||
}
|
||||
44
src/tagreader/tagreaderresult.h
Normal file
44
src/tagreader/tagreaderresult.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERRESULT_H
|
||||
#define TAGREADERRESULT_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class TagReaderResult {
|
||||
public:
|
||||
enum class ErrorCode {
|
||||
Success,
|
||||
Unsupported,
|
||||
FilenameMissing,
|
||||
FileDoesNotExist,
|
||||
FileOpenError,
|
||||
FileParseError,
|
||||
FileSaveError,
|
||||
CustomError,
|
||||
};
|
||||
TagReaderResult(const ErrorCode _error_code = ErrorCode::Unsupported, const QString &_error_text = QString()) : error_code(_error_code), error_text(_error_text) {}
|
||||
ErrorCode error_code;
|
||||
QString error_text;
|
||||
bool success() const { return error_code == ErrorCode::Success; }
|
||||
QString error_string() const;
|
||||
};
|
||||
|
||||
#endif // TAGREADERRESULT_H
|
||||
24
src/tagreader/tagreadersavecoverrequest.cpp
Normal file
24
src/tagreader/tagreadersavecoverrequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreadersavecoverrequest.h"
|
||||
|
||||
TagReaderSaveCoverRequest::TagReaderSaveCoverRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
43
src/tagreader/tagreadersavecoverrequest.h
Normal file
43
src/tagreader/tagreadersavecoverrequest.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERSAVECOVERREQUEST_H
|
||||
#define TAGREADERSAVECOVERREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderSaveCoverRequest : public TagReaderRequest {
|
||||
public:
|
||||
|
||||
explicit TagReaderSaveCoverRequest(const QString &_filename);
|
||||
|
||||
static SharedPtr<TagReaderSaveCoverRequest> Create(const QString &filename) { return make_shared<TagReaderSaveCoverRequest>(filename); }
|
||||
|
||||
SaveTagCoverData save_tag_cover_data;
|
||||
};
|
||||
|
||||
using TagReaderSaveCoverRequestPtr = SharedPtr<TagReaderSaveCoverRequest>;
|
||||
|
||||
#endif // TAGREADERSAVECOVERREQUEST_H
|
||||
24
src/tagreader/tagreadersaveplaycountrequest.cpp
Normal file
24
src/tagreader/tagreadersaveplaycountrequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreadersaveplaycountrequest.h"
|
||||
|
||||
TagReaderSavePlaycountRequest::TagReaderSavePlaycountRequest(const QString &_filename) : TagReaderRequest(_filename), playcount(0) {}
|
||||
39
src/tagreader/tagreadersaveplaycountrequest.h
Normal file
39
src/tagreader/tagreadersaveplaycountrequest.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERSAVEPLAYCOUNTREQUEST_H
|
||||
#define TAGREADERSAVEPLAYCOUNTREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderSavePlaycountRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderSavePlaycountRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderSavePlaycountRequest> Create(const QString &filename) { return make_shared<TagReaderSavePlaycountRequest>(filename); }
|
||||
uint playcount;
|
||||
};
|
||||
|
||||
using TagReaderSavePlaycountRequestPtr = SharedPtr<TagReaderSavePlaycountRequest>;
|
||||
|
||||
#endif // TAGREADERSAVEPLAYCOUNTREQUEST_H
|
||||
24
src/tagreader/tagreadersaveratingrequest.cpp
Normal file
24
src/tagreader/tagreadersaveratingrequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreadersaveratingrequest.h"
|
||||
|
||||
TagReaderSaveRatingRequest::TagReaderSaveRatingRequest(const QString &_filename) : TagReaderRequest(_filename), rating(0.0F) {}
|
||||
39
src/tagreader/tagreadersaveratingrequest.h
Normal file
39
src/tagreader/tagreadersaveratingrequest.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERSAVERATINGREQUEST_H
|
||||
#define TAGREADERSAVERATINGREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderSaveRatingRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderSaveRatingRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderSaveRatingRequest> Create(const QString &filename) { return make_shared<TagReaderSaveRatingRequest>(filename); }
|
||||
float rating;
|
||||
};
|
||||
|
||||
using TagReaderSaveRatingRequestPtr = SharedPtr<TagReaderSaveRatingRequest>;
|
||||
|
||||
#endif // TAGREADERSAVERATINGREQUEST_H
|
||||
File diff suppressed because it is too large
Load Diff
127
src/tagreader/tagreadertaglib.h
Normal file
127
src/tagreader/tagreadertaglib.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2013, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERTAGLIB_H
|
||||
#define TAGREADERTAGLIB_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include <taglib/tstring.h>
|
||||
#include <taglib/fileref.h>
|
||||
#include <taglib/xiphcomment.h>
|
||||
#include <taglib/flacfile.h>
|
||||
#include <taglib/mpegfile.h>
|
||||
#include <taglib/mp4file.h>
|
||||
#include <taglib/apetag.h>
|
||||
#include <taglib/apefile.h>
|
||||
#include <taglib/asffile.h>
|
||||
#include <taglib/id3v2tag.h>
|
||||
#include <taglib/popularimeterframe.h>
|
||||
#include <taglib/mp4tag.h>
|
||||
#include <taglib/asftag.h>
|
||||
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreaderbase.h"
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
#undef TStringToQString
|
||||
#undef QStringToTString
|
||||
|
||||
class FileRefFactory;
|
||||
|
||||
class TagReaderTagLib : public TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderTagLib();
|
||||
~TagReaderTagLib() override;
|
||||
|
||||
static inline TagLib::String QStringToTagLibString(const QString &s) {
|
||||
return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8);
|
||||
}
|
||||
|
||||
static inline QString TagLibStringToQString(const TagLib::String &s) {
|
||||
return QString::fromUtf8((s).toCString(true));
|
||||
}
|
||||
|
||||
TagReaderResult IsMediaFile(const QString &filename) const override;
|
||||
|
||||
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
|
||||
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||
|
||||
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
||||
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||
|
||||
TagReaderResult SaveSongPlaycount(const QString &filename, const uint playcount) const override;
|
||||
TagReaderResult SaveSongRating(const QString &filename, const float rating) const override;
|
||||
|
||||
private:
|
||||
Song::FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||
|
||||
void ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseVorbisComments(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseAPETags(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseMP4Tags(TagLib::MP4::Tag *tag, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseASFTags(TagLib::ASF::Tag *tag, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseASFAttribute(const TagLib::ASF::AttributeListMap &attributes_map, const char *attribute, QString *str) const;
|
||||
|
||||
void SetID3v2Tag(TagLib::ID3v2::Tag *tag, const Song &song) const;
|
||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUnsyncLyricsFrame(const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
|
||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const Song &song) const;
|
||||
void SetAPETag(TagLib::APE::Tag *tag, const Song &song) const;
|
||||
void SetASFTag(TagLib::ASF::Tag *tag, const Song &song) const;
|
||||
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const QString &value) const;
|
||||
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const int value) const;
|
||||
|
||||
QByteArray LoadEmbeddedAPECover(const TagLib::APE::ItemListMap &map) const;
|
||||
|
||||
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
||||
|
||||
void SetPlaycount(TagLib::Ogg::XiphComment *vorbis_comment, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::APE::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::ID3v2::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::MP4::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::ASF::Tag *tag, const uint playcount) const;
|
||||
|
||||
void SetRating(TagLib::Ogg::XiphComment *vorbis_comment, const float rating) const;
|
||||
void SetRating(TagLib::APE::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::ID3v2::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::MP4::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::ASF::Tag *tag, const float rating) const;
|
||||
|
||||
void SetEmbeddedCover(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mimetype) const;
|
||||
void SetEmbeddedCover(TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mimetype) const;
|
||||
void SetEmbeddedCover(TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mimetype) const;
|
||||
void SetEmbeddedCover(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mimetype) const;
|
||||
|
||||
static TagLib::String TagLibStringListToSlashSeparatedString(const TagLib::StringList &taglib_string_list);
|
||||
|
||||
private:
|
||||
FileRefFactory *factory_;
|
||||
|
||||
Q_DISABLE_COPY(TagReaderTagLib)
|
||||
};
|
||||
|
||||
#endif // TAGREADERTAGLIB_H
|
||||
24
src/tagreader/tagreaderwritefilerequest.cpp
Normal file
24
src/tagreader/tagreaderwritefilerequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderwritefilerequest.h"
|
||||
|
||||
TagReaderWriteFileRequest::TagReaderWriteFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
46
src/tagreader/tagreaderwritefilerequest.h
Normal file
46
src/tagreader/tagreaderwritefilerequest.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERWRITEFILEREQUEST_H
|
||||
#define TAGREADERWRITEFILEREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "tagreaderrequest.h"
|
||||
#include "savetagsoptions.h"
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderWriteFileRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderWriteFileRequest(const QString &_filename);
|
||||
|
||||
static SharedPtr<TagReaderWriteFileRequest> Create(const QString &filename) { return make_shared<TagReaderWriteFileRequest>(filename); }
|
||||
|
||||
SaveTagsOptions save_tags_options;
|
||||
Song song;
|
||||
SaveTagCoverData save_tag_cover_data;
|
||||
};
|
||||
|
||||
using TagReaderWriteFileRequestPtr = SharedPtr<TagReaderWriteFileRequest>;
|
||||
|
||||
#endif // TAGREADERWRITEFILEREQUEST_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user