Remove external tagreader

This commit is contained in:
Jonas Kvinge
2024-09-15 23:17:14 +02:00
parent 3cb0f60900
commit daaacf4663
104 changed files with 3524 additions and 4634 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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)

View File

@@ -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>

View File

@@ -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>.

View File

@@ -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

View File

@@ -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"

View File

@@ -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()

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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) {}

View File

@@ -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

View File

@@ -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)

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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();
}

View File

@@ -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());
}
}
}
}
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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_);

View File

@@ -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);

View File

@@ -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

View File

@@ -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]() {

View File

@@ -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);

View File

@@ -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();

View File

@@ -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());

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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_;

View File

@@ -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;

View File

@@ -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(), &copy);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), &copy);
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()) {

View File

@@ -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 {

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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()));
}
}
}

View File

@@ -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();

View File

@@ -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_);

View File

@@ -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();
}
}

View 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) {}

View 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

View 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) {}

View 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

View 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

View 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();
}

View 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

View 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);
}
}

View 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

View File

@@ -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;
}

View File

@@ -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

View 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) {}

View 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

View 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);
}

View 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

View 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) {}

View 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

View 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);
}

View 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

View 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) {}

View 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

View 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);
}

View 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

View 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) {}

View 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

View 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);
}

View 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

View 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;
}

View 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

View 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");
}

View 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

View 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) {}

View 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

View 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) {}

View 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

View 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) {}

View 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

View 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

View 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) {}

View 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