diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cfe3f7cb..0d3ce6d40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,16 +271,22 @@ if(USE_TAGLIB) else() pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1) endif() + set(HAVE_TAGLIB 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) endif() pkg_check_modules(LIBEBUR128 IMPORTED_TARGET libebur128) -if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND) +if(NOT HAVE_TAGLIB AND NOT HAVE_TAGPARSER) message(FATAL_ERROR "You need either TagLib or TagParser!") endif() @@ -535,6 +541,6 @@ if(NOT CMAKE_CROSSCOMPILING) endif() endif() -if(USE_TAGLIB AND TAGLIB_FOUND AND NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12) +if(HAVE_TAGLIB AND TAGLIB_FOUND AND NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12) message(WARNING "There is a critical bug in TagLib (1.11.1) that can result in corrupt Ogg files, see: https://github.com/taglib/taglib/issues/864, please consider updating TagLib to the newest version.") endif() diff --git a/ext/libstrawberry-common/core/messagehandler.h b/ext/libstrawberry-common/core/messagehandler.h index 0791dee41..e0bc3e2b6 100644 --- a/ext/libstrawberry-common/core/messagehandler.h +++ b/ext/libstrawberry-common/core/messagehandler.h @@ -120,13 +120,13 @@ template void AbstractMessageHandler::SendMessage(const MessageType &message) { Q_ASSERT(QThread::currentThread() == thread()); - std::string data = message.SerializeAsString(); + const std::string data = message.SerializeAsString(); WriteMessage(QByteArray(data.data(), data.size())); } template void AbstractMessageHandler::SendMessageAsync(const MessageType &message) { - std::string data = message.SerializeAsString(); + const std::string data = message.SerializeAsString(); QMetaObject::invokeMethod(this, "WriteMessage", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray(data.data(), data.size()))); } diff --git a/ext/libstrawberry-tagreader/CMakeLists.txt b/ext/libstrawberry-tagreader/CMakeLists.txt index 482c2fac6..3dbd8ad59 100644 --- a/ext/libstrawberry-tagreader/CMakeLists.txt +++ b/ext/libstrawberry-tagreader/CMakeLists.txt @@ -11,11 +11,11 @@ endif() set(SOURCES tagreaderbase.cpp tagreadermessages.proto) -if(USE_TAGLIB AND TAGLIB_FOUND) +if(HAVE_TAGLIB) list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp) endif() -if(USE_TAGPARSER AND TAGPARSER_FOUND) +if(HAVE_TAGPARSER) list(APPEND SOURCES tagreadertagparser.cpp) endif() @@ -24,11 +24,11 @@ link_directories( ${PROTOBUF_LIBRARY_DIRS} ) -if(USE_TAGLIB AND TAGLIB_FOUND) +if(HAVE_TAGLIB) link_directories(${TAGLIB_LIBRARY_DIRS}) endif() -if(USE_TAGPARSER AND TAGPARSER_FOUND) +if(HAVE_TAGPARSER) link_directories(${TAGPARSER_LIBRARY_DIRS}) endif() @@ -56,12 +56,12 @@ target_link_libraries(libstrawberry-tagreader PRIVATE libstrawberry-common ) -if(USE_TAGLIB AND TAGLIB_FOUND) +if(HAVE_TAGLIB) target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS}) target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES}) endif() -if(USE_TAGPARSER AND TAGPARSER_FOUND) +if(HAVE_TAGPARSER) target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS}) target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES}) endif() diff --git a/ext/libstrawberry-tagreader/tagreaderbase.cpp b/ext/libstrawberry-tagreader/tagreaderbase.cpp index 65b261ee0..167c7b7ee 100644 --- a/ext/libstrawberry-tagreader/tagreaderbase.cpp +++ b/ext/libstrawberry-tagreader/tagreaderbase.cpp @@ -1,5 +1,5 @@ /* This file is part of Strawberry. - Copyright 2018-2023, Jonas Kvinge + Copyright 2018-2024, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #include +#include #include #include #include @@ -32,6 +33,28 @@ 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::FileOpenError: + return QObject::tr("File can 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) { @@ -57,16 +80,15 @@ int TagReaderBase::ConvertToPOPMRating(const float rating) { } -TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request) { +TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::WriteFileRequest &request) { if (!request.has_save_cover() || !request.save_cover()) { return Cover(); } - const QString song_filename = QString::fromUtf8(request.filename().data(), static_cast(request.filename().size())); QString cover_filename; if (request.has_cover_filename()) { - cover_filename = QString::fromUtf8(request.cover_filename().data(), static_cast(request.cover_filename().size())); + cover_filename = QString::fromStdString(request.cover_filename()); } QByteArray cover_data; if (request.has_cover_data()) { @@ -81,12 +103,11 @@ TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::S } -TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request) { +TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const spb::tagreader::SaveEmbeddedArtRequest &request) { - const QString song_filename = QString::fromUtf8(request.filename().data(), static_cast(request.filename().size())); QString cover_filename; if (request.has_cover_filename()) { - cover_filename = QString::fromUtf8(request.cover_filename().data(), static_cast(request.cover_filename().size())); + cover_filename = QString::fromStdString(request.cover_filename()); } QByteArray cover_data; if (request.has_cover_data()) { diff --git a/ext/libstrawberry-tagreader/tagreaderbase.h b/ext/libstrawberry-tagreader/tagreaderbase.h index 73efdbe73..d98c67de2 100644 --- a/ext/libstrawberry-tagreader/tagreaderbase.h +++ b/ext/libstrawberry-tagreader/tagreaderbase.h @@ -1,5 +1,5 @@ /* This file is part of Strawberry. - Copyright 2018-2023, Jonas Kvinge + Copyright 2018-2024, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,6 +36,23 @@ class TagReaderBase { explicit TagReaderBase(); ~TagReaderBase() = default; + class Result { + public: + enum class ErrorCode { + Success, + Unsupported, + FilenameMissing, + 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) {} @@ -44,22 +61,25 @@ class TagReaderBase { QString error; }; + static QString ErrorString(const Result &result); + virtual bool IsMediaFile(const QString &filename) const = 0; - virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0; - virtual bool SaveFile(const spb::tagreader::SaveFileRequest &request) 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 QByteArray LoadEmbeddedArt(const QString &filename) const = 0; - virtual bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &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 bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0; - virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0; + 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 spb::tagreader::SaveFileRequest &request); - static Cover LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request); + 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); diff --git a/ext/libstrawberry-tagreader/tagreadergme.cpp b/ext/libstrawberry-tagreader/tagreadergme.cpp index 53eef2f1d..c373dad97 100644 --- a/ext/libstrawberry-tagreader/tagreadergme.cpp +++ b/ext/libstrawberry-tagreader/tagreadergme.cpp @@ -35,22 +35,20 @@ #include "tagreaderbase.h" #include "tagreadertaglib.h" -bool GME::IsSupportedFormat(const QFileInfo &file_info) { - return file_info.exists() && (file_info.completeSuffix().endsWith(QLatin1String("spc"), Qt::CaseInsensitive) || file_info.completeSuffix().endsWith(QLatin1String("vgm")), Qt::CaseInsensitive); +bool GME::IsSupportedFormat(const QFileInfo &fileinfo) { + return fileinfo.exists() && (fileinfo.completeSuffix().endsWith(QLatin1String("spc"), Qt::CaseInsensitive) || fileinfo.completeSuffix().endsWith(QLatin1String("vgm")), Qt::CaseInsensitive); } -bool GME::ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) { +TagReaderBase::Result GME::ReadFile(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) { - if (file_info.completeSuffix().endsWith(QLatin1String("spc")), Qt::CaseInsensitive) { - SPC::Read(file_info, song_info); - return true; + if (fileinfo.completeSuffix().endsWith(QLatin1String("spc")), Qt::CaseInsensitive) { + return SPC::Read(fileinfo, song); } - if (file_info.completeSuffix().endsWith(QLatin1String("vgm"), Qt::CaseInsensitive)) { - VGM::Read(file_info, song_info); - return true; + if (fileinfo.completeSuffix().endsWith(QLatin1String("vgm"), Qt::CaseInsensitive)) { + return VGM::Read(fileinfo, song); } - return false; + return TagReaderBase::Result::ErrorCode::Unsupported; } @@ -67,15 +65,19 @@ quint32 GME::UnpackBytes32(const char *const bytes, size_t length) { } -void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) { +TagReaderBase::Result GME::SPC::Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) { - QFile file(file_info.filePath()); - if (!file.open(QIODevice::ReadOnly)) return; + QFile file(fileinfo.filePath()); + if (!file.open(QIODevice::ReadOnly)) { + return TagReaderBase::Result(TagReaderBase::Result::ErrorCode::FileOpenError, file.errorString()); + } - qLog(Debug) << "Reading tags from SPC file" << file_info.fileName(); + 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; + if (!file.read(33).startsWith(QStringLiteral("SNES-SPC700").toLatin1())) { + return TagReaderBase::Result::ErrorCode::Unsupported; + } // First order of business -- get any tag values that exist within the core file information. // These only allow for a certain number of bytes per field, @@ -88,13 +90,13 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so const bool has_id6 = id6_status.length() >= 1 && id6_status[0] == static_cast(xID6_STATUS::ON); file.seek(SONG_TITLE_OFFSET); - song_info->set_title(QString::fromLatin1(file.read(32)).toStdString()); + song->set_title(QString::fromLatin1(file.read(32)).toStdString()); file.seek(GAME_TITLE_OFFSET); - song_info->set_album(QString::fromLatin1(file.read(32)).toStdString()); + song->set_album(QString::fromLatin1(file.read(32)).toStdString()); file.seek(ARTIST_OFFSET); - song_info->set_artist(QString::fromLatin1(file.read(32)).toStdString()); + song->set_artist(QString::fromLatin1(file.read(32)).toStdString()); file.seek(INTRO_LENGTH_OFFSET); QByteArray length_bytes = file.read(INTRO_LENGTH_SIZE); @@ -107,7 +109,7 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so } if (length_in_sec < 0x1FFF) { - song_info->set_length_nanosec(length_in_sec * kNsecPerSec); + song->set_length_nanosec(length_in_sec * kNsecPerSec); } } @@ -131,7 +133,7 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3]; // This should be the size remaining for entire ID6 block, but it seems that most files treat this as the size of the remaining header space... - qLog(Debug) << file_info.fileName() << "has ID6 tag."; + qLog(Debug) << fileinfo.fileName() << "has ID6 tag."; while ((file.pos()) + 4 < XID6_OFFSET + xid6_size) { QByteArray arr = file.read(4); @@ -152,21 +154,25 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so // an APETAG entry at the bottom of the file instead of writing into the xid6 tagging space. // This is where a lot of the extra data for a file is stored, such as genre or replaygain data. // This data is currently supported by TagLib, so we will simply use that for the remaining values. - TagLib::APE::File ape(file_info.filePath().toStdString().data()); + TagLib::APE::File ape(fileinfo.filePath().toStdString().data()); if (ape.hasAPETag()) { TagLib::Tag *tag = ape.tag(); - if (!tag) return; + if (!tag) { + return TagReaderBase::Result::ErrorCode::FileParseError; + } - TagReaderTagLib::TStringToStdString(tag->artist(), song_info->mutable_artist()); - TagReaderTagLib::TStringToStdString(tag->album(), song_info->mutable_album()); - TagReaderTagLib::TStringToStdString(tag->title(), song_info->mutable_title()); - TagReaderTagLib::TStringToStdString(tag->genre(), song_info->mutable_genre()); - song_info->set_track(static_cast(tag->track())); - song_info->set_year(static_cast(tag->year())); + TagReaderTagLib::TStringToStdString(tag->artist(), song->mutable_artist()); + TagReaderTagLib::TStringToStdString(tag->album(), song->mutable_album()); + TagReaderTagLib::TStringToStdString(tag->title(), song->mutable_title()); + TagReaderTagLib::TStringToStdString(tag->genre(), song->mutable_genre()); + song->set_track(static_cast(tag->track())); + song->set_year(static_cast(tag->year())); } - song_info->set_valid(true); - song_info->set_filetype(spb::tagreader::SongMetadata_FileType_SPC); + song->set_valid(true); + song->set_filetype(spb::tagreader::SongMetadata_FileType_SPC); + + return TagReaderBase::Result::ErrorCode::Success; } @@ -188,18 +194,24 @@ quint64 GME::SPC::ConvertSPCStringToNum(const QByteArray &arr) { } -void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) { +TagReaderBase::Result GME::VGM::Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song) { - QFile file(file_info.filePath()); - if (!file.open(QIODevice::ReadOnly)) return; + QFile file(fileinfo.filePath()); + if (!file.open(QIODevice::ReadOnly)) { + return TagReaderBase::Result(TagReaderBase::Result::ErrorCode::FileOpenError, file.errorString()); + } - qLog(Debug) << "Reading tags from VGM file" << file_info.fileName(); + qLog(Debug) << "Reading tags from VGM file" << fileinfo.filePath(); - if (!file.read(4).startsWith(QStringLiteral("Vgm ").toLatin1())) return; + if (!file.read(4).startsWith(QStringLiteral("Vgm ").toLatin1())) { + return TagReaderBase::Result::ErrorCode::Unsupported; + } file.seek(GD3_TAG_PTR); QByteArray gd3_head = file.read(4); - if (gd3_head.size() < 4) return; + if (gd3_head.size() < 4) { + return TagReaderBase::Result::ErrorCode::FileParseError; + } quint64 pt = GME::UnpackBytes32(gd3_head.constData(), gd3_head.size()); @@ -209,7 +221,9 @@ void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so QByteArray loop_count_bytes = file.read(4); quint64 length = 0; - if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) return; + if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) { + return TagReaderBase::Result::ErrorCode::FileParseError; + } file.seek(static_cast(GD3_TAG_PTR + pt)); QByteArray gd3_version = file.read(4); @@ -227,18 +241,22 @@ void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so fileTagStream.setCodec("UTF-16"); #endif QStringList strings = fileTagStream.readLine(0).split(QLatin1Char('\0')); - if (strings.count() < 10) return; + if (strings.count() < 10) { + return TagReaderBase::Result::ErrorCode::FileParseError; + } // VGM standard dictates string tag data exist in specific order. // Order alternates between English and Japanese version of data. // Read GD3 tag standard for more details. - song_info->set_title(strings[0].toStdString()); - song_info->set_album(strings[2].toStdString()); - song_info->set_artist(strings[6].toStdString()); - song_info->set_year(strings[8].left(4).toInt()); - song_info->set_length_nanosec(length * kNsecPerMsec); - song_info->set_valid(true); - song_info->set_filetype(spb::tagreader::SongMetadata_FileType_VGM); + song->set_title(strings[0].toStdString()); + 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_valid(true); + song->set_filetype(spb::tagreader::SongMetadata_FileType_VGM); + + return TagReaderBase::Result::ErrorCode::Success; } @@ -270,31 +288,60 @@ TagReaderGME::TagReaderGME() = default; bool TagReaderGME::IsMediaFile(const QString &filename) const { + QFileInfo fileinfo(filename); return GME::IsSupportedFormat(fileinfo); + } -bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const { +TagReaderBase::Result TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const { + QFileInfo fileinfo(filename); return GME::ReadFile(fileinfo, song); + } -bool TagReaderGME::SaveFile(const spb::tagreader::SaveFileRequest&) const { - return false; +TagReaderBase::Result TagReaderGME::WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const { + + Q_UNUSED(filename); + Q_UNUSED(request); + + return Result::ErrorCode::Unsupported; + } -QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const { - return QByteArray(); +TagReaderBase::Result TagReaderGME::LoadEmbeddedArt(const QString &filename, QByteArray &data) const { + + Q_UNUSED(filename); + Q_UNUSED(data); + + return Result::ErrorCode::Unsupported; + } -bool TagReaderGME::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest&) const { - return false; +TagReaderBase::Result TagReaderGME::SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const { + + Q_UNUSED(filename); + Q_UNUSED(request); + + return Result::ErrorCode::Unsupported; + } -bool TagReaderGME::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { - return false; +TagReaderBase::Result TagReaderGME::SaveSongPlaycountToFile(const QString &filename, const uint playcount) const { + + Q_UNUSED(filename); + Q_UNUSED(playcount); + + return Result::ErrorCode::Unsupported; + } -bool TagReaderGME::SaveSongRatingToFile(const QString&, const spb::tagreader::SongMetadata&) const { - return false; +TagReaderBase::Result TagReaderGME::SaveSongRatingToFile(const QString &filename, const float rating) const { + + Q_UNUSED(filename); + Q_UNUSED(rating); + + return Result::ErrorCode::Unsupported; + } diff --git a/ext/libstrawberry-tagreader/tagreadergme.h b/ext/libstrawberry-tagreader/tagreadergme.h index a1d21b1a1..fe1d46471 100644 --- a/ext/libstrawberry-tagreader/tagreadergme.h +++ b/ext/libstrawberry-tagreader/tagreadergme.h @@ -31,8 +31,8 @@ namespace GME { -bool IsSupportedFormat(const QFileInfo &file_info); -bool ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info); +bool IsSupportedFormat(const QFileInfo &fileinfo); +TagReaderBase::Result ReadFile(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song); uint32_t UnpackBytes32(const char *const bytes, size_t length); @@ -72,7 +72,7 @@ enum class xID6_TYPE { Integer = 0x4 }; -void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info); +TagReaderBase::Result Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *song); qint16 GetNextMemAddressAlign32bit(qint16 input); quint64 ConvertSPCStringToNum(const QByteArray &arr); } // namespace SPC @@ -88,7 +88,7 @@ constexpr int LOOP_SAMPLE_COUNT = 0x20; constexpr int SAMPLE_TIMEBASE = 44100; constexpr int GST_GME_LOOP_TIME_MS = 8000; -void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info); +TagReaderBase::Result Read(const QFileInfo &fileinfo, spb::tagreader::SongMetadata *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); @@ -106,14 +106,14 @@ class TagReaderGME : public TagReaderBase { bool IsMediaFile(const QString &filename) const override; - bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override; - bool SaveFile(const spb::tagreader::SaveFileRequest &request) 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; - QByteArray LoadEmbeddedArt(const QString &filename) const override; - bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override; + Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override; + Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override; - bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override; - bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override; + Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override; + Result SaveSongRatingToFile(const QString &filename, const float rating) const override; }; #endif // TAGREADERGME_H diff --git a/ext/libstrawberry-tagreader/tagreadermessages.proto b/ext/libstrawberry-tagreader/tagreadermessages.proto index 80c03835e..de14d97f3 100644 --- a/ext/libstrawberry-tagreader/tagreadermessages.proto +++ b/ext/libstrawberry-tagreader/tagreadermessages.proto @@ -1,4 +1,4 @@ -syntax = "proto2"; +syntax = "proto3"; package spb.tagreader; @@ -97,7 +97,6 @@ message IsMediaFileRequest { message IsMediaFileResponse { optional bool success = 1; - optional string error = 2; } message ReadFileRequest { @@ -105,11 +104,12 @@ message ReadFileRequest { } message ReadFileResponse { - optional SongMetadata metadata = 1; - optional string error = 2; + optional bool success = 1; + optional SongMetadata metadata = 2; + optional string error = 3; } -message SaveFileRequest { +message WriteFileRequest { optional string filename = 1; optional bool save_tags = 2; optional bool save_playcount = 3; @@ -121,7 +121,7 @@ message SaveFileRequest { optional string cover_mime_type = 9; } -message SaveFileResponse { +message WriteFileResponse { optional bool success = 1; optional string error = 2; } @@ -131,8 +131,9 @@ message LoadEmbeddedArtRequest { } message LoadEmbeddedArtResponse { - optional bytes data = 1; - optional string error = 2; + optional bool success = 1; + optional bytes data = 2; + optional string error = 3; } message SaveEmbeddedArtRequest { @@ -149,7 +150,7 @@ message SaveEmbeddedArtResponse { message SaveSongPlaycountToFileRequest { optional string filename = 1; - optional SongMetadata metadata = 2; + optional uint32 playcount = 2; } message SaveSongPlaycountToFileResponse { @@ -159,7 +160,7 @@ message SaveSongPlaycountToFileResponse { message SaveSongRatingToFileRequest { optional string filename = 1; - optional SongMetadata metadata = 2; + optional float rating = 2; } message SaveSongRatingToFileResponse { @@ -173,8 +174,8 @@ message Message { optional ReadFileRequest read_file_request = 2; optional ReadFileResponse read_file_response = 3; - optional SaveFileRequest save_file_request = 4; - optional SaveFileResponse save_file_response = 5; + 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; diff --git a/ext/libstrawberry-tagreader/tagreadertaglib.cpp b/ext/libstrawberry-tagreader/tagreadertaglib.cpp index 058587bdf..5ab4b8c62 100644 --- a/ext/libstrawberry-tagreader/tagreadertaglib.cpp +++ b/ext/libstrawberry-tagreader/tagreadertaglib.cpp @@ -1,6 +1,6 @@ /* This file is part of Strawberry. Copyright 2013, David Sansome - Copyright 2018-2023, Jonas Kvinge + Copyright 2018-2024, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -229,7 +229,11 @@ spb::tagreader::SongMetadata_FileType TagReaderTagLib::GuessFileType(TagLib::Fil } -bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const { +TagReaderBase::Result TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const { + + if (filename.isEmpty()) { + return Result::ErrorCode::FilenameMissing; + } const QByteArray url(QUrl::fromLocalFile(filename).toEncoded()); const QFileInfo fileinfo(filename); @@ -252,8 +256,8 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta std::unique_ptr fileref(factory_->GetFileRef(filename)); if (fileref->isNull()) { - qLog(Info) << "TagLib hasn't been able to read" << filename << "file"; - return false; + qLog(Error) << "TagLib hasn't been able to read" << filename << "file"; + return Result::ErrorCode::FileOpenError; } song->set_filetype(GuessFileType(fileref.get())); @@ -552,8 +556,8 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta if (compilation.isEmpty()) { // well, it wasn't set, but if the artist is VA assume it's a compilation - const QString albumartist = QString::fromUtf8(song->albumartist().data(), static_cast(song->albumartist().size())); - const QString artist = QString::fromUtf8(song->artist().data(), static_cast(song->artist().size())); + const QString albumartist = QString::fromStdString(song->albumartist()); + const QString artist = QString::fromStdString(song->artist()); if (artist.compare(QLatin1String("various artists")) == 0 || albumartist.compare(QLatin1String("various artists")) == 0) { song->set_compilation(true); } @@ -575,7 +579,14 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta if (song->bitrate() <= 0) { song->set_bitrate(-1); } if (song->lastplayed() <= 0) { song->set_lastplayed(-1); } - return song->filetype() != spb::tagreader::SongMetadata_FileType_UNKNOWN; + if (song->filetype() == spb::tagreader::SongMetadata_FileType_UNKNOWN) { + qLog(Error) << "Unknown audio filetype reading" << filename; + return Result::ErrorCode::Unsupported; + } + + qLog(Debug) << "Got tags for" << filename; + + return Result::ErrorCode::Success; } @@ -846,12 +857,13 @@ void TagReaderTagLib::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment } -bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) const { +TagReaderBase::Result TagReaderTagLib::WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const { - if (request.filename().empty()) return false; + if (filename.isEmpty()) { + return Result::ErrorCode::FilenameMissing; + } - const QString filename = QString::fromUtf8(request.filename().data(), static_cast(request.filename().size())); - const spb::tagreader::SongMetadata song = request.metadata(); + 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(); @@ -873,10 +885,13 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c qLog(Debug) << "Saving" << save_tags_options.join(QLatin1String(", ")) << "to" << filename; - const Cover cover = LoadCoverFromRequest(request); + const Cover cover = LoadCoverFromRequest(filename, request); std::unique_ptr fileref(factory_->GetFileRef(filename)); - if (!fileref || fileref->isNull()) return false; + if (!fileref || fileref->isNull()) { + qLog(Error) << "TagLib hasn't been able to open" << filename << "file"; + return Result::ErrorCode::FileOpenError; + } if (save_tags) { fileref->tag()->setTitle(song.title().empty() ? TagLib::String() : StdStringToTaglibString(song.title())); @@ -892,115 +907,123 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c if (TagLib::FLAC::File *file_flac = dynamic_cast(fileref->file())) { is_flac = true; TagLib::Ogg::XiphComment *xiph_comment = file_flac->xiphComment(true); - if (!xiph_comment) return false; - if (save_tags) { - SetVorbisComments(xiph_comment, song); - } - if (save_playcount) { - SetPlaycount(xiph_comment, song); - } - if (save_rating) { - SetRating(xiph_comment, song); - } - if (save_cover) { - SetEmbeddedArt(file_flac, xiph_comment, cover.data, cover.mime_type); + if (xiph_comment) { + if (save_tags) { + SetVorbisComments(xiph_comment, song); + } + if (save_playcount) { + SetPlaycount(xiph_comment, song.playcount()); + } + if (save_rating) { + SetRating(xiph_comment, song.rating()); + } + if (save_cover) { + SetEmbeddedArt(file_flac, xiph_comment, cover.data, cover.mime_type); + } } } else if (TagLib::WavPack::File *file_wavpack = dynamic_cast(fileref->file())) { TagLib::APE::Tag *tag = file_wavpack->APETag(true); - if (!tag) return false; - if (save_tags) { - SaveAPETag(tag, song); - } - if (save_playcount) { - SetPlaycount(tag, song); - } - if (save_rating) { - SetRating(tag, song); + if (tag) { + if (save_tags) { + SaveAPETag(tag, song); + } + if (save_playcount) { + SetPlaycount(tag, song.playcount()); + } + if (save_rating) { + SetRating(tag, song.rating()); + } } } else if (TagLib::APE::File *file_ape = dynamic_cast(fileref->file())) { TagLib::APE::Tag *tag = file_ape->APETag(true); - if (!tag) return false; - if (save_tags) { - SaveAPETag(tag, song); - } - if (save_playcount) { - SetPlaycount(tag, song); - } - if (save_rating) { - SetRating(tag, song); + if (tag) { + if (save_tags) { + SaveAPETag(tag, song); + } + if (save_playcount) { + SetPlaycount(tag, song.playcount()); + } + if (save_rating) { + SetRating(tag, song.rating()); + } } } else if (TagLib::MPC::File *file_mpc = dynamic_cast(fileref->file())) { TagLib::APE::Tag *tag = file_mpc->APETag(true); - if (!tag) return false; - if (save_tags) { - SaveAPETag(tag, song); - } - if (save_playcount) { - SetPlaycount(tag, song); - } - if (save_rating) { - SetRating(tag, song); + if (tag) { + if (save_tags) { + SaveAPETag(tag, song); + } + if (save_playcount) { + SetPlaycount(tag, song.playcount()); + } + if (save_rating) { + SetRating(tag, song.rating()); + } } } else if (TagLib::MPEG::File *file_mpeg = dynamic_cast(fileref->file())) { TagLib::ID3v2::Tag *tag = file_mpeg->ID3v2Tag(true); - if (!tag) return false; - if (save_tags) { - SaveID3v2Tag(tag, song); - } - if (save_playcount) { - SetPlaycount(tag, song); - } - if (save_rating) { - SetRating(tag, song); - } - if (save_cover) { - SetEmbeddedArt(tag, cover.data, cover.mime_type); + if (tag) { + if (save_tags) { + SaveID3v2Tag(tag, song); + } + if (save_playcount) { + SetPlaycount(tag, song.playcount()); + } + if (save_rating) { + SetRating(tag, song.rating()); + } + if (save_cover) { + SetEmbeddedArt(tag, cover.data, cover.mime_type); + } } } else if (TagLib::MP4::File *file_mp4 = dynamic_cast(fileref->file())) { TagLib::MP4::Tag *tag = file_mp4->tag(); - if (!tag) return false; - if (save_tags) { - tag->setItem("disk", TagLib::MP4::Item(song.disc() <= 0 - 1 ? 0 : song.disc(), 0)); - tag->setItem("\251wrt", TagLib::StringList(TagLib::String(song.composer(), TagLib::String::UTF8))); - tag->setItem("\251grp", TagLib::StringList(TagLib::String(song.grouping(), TagLib::String::UTF8))); - tag->setItem("\251lyr", TagLib::StringList(TagLib::String(song.lyrics(), TagLib::String::UTF8))); - tag->setItem("aART", TagLib::StringList(TagLib::String(song.albumartist(), TagLib::String::UTF8))); - tag->setItem("cpil", TagLib::MP4::Item(song.compilation())); - } - if (save_playcount) { - SetPlaycount(tag, song); - } - if (save_rating) { - SetRating(tag, song); - } - if (save_cover) { - SetEmbeddedArt(file_mp4, tag, cover.data, cover.mime_type); + if (tag) { + if (save_tags) { + tag->setItem("disk", TagLib::MP4::Item(song.disc() <= 0 - 1 ? 0 : song.disc(), 0)); + tag->setItem("\251wrt", TagLib::StringList(TagLib::String(song.composer(), TagLib::String::UTF8))); + tag->setItem("\251grp", TagLib::StringList(TagLib::String(song.grouping(), TagLib::String::UTF8))); + tag->setItem("\251lyr", TagLib::StringList(TagLib::String(song.lyrics(), TagLib::String::UTF8))); + tag->setItem("aART", TagLib::StringList(TagLib::String(song.albumartist(), TagLib::String::UTF8))); + tag->setItem("cpil", TagLib::MP4::Item(song.compilation())); + } + if (save_playcount) { + SetPlaycount(tag, song.playcount()); + } + if (save_rating) { + SetRating(tag, song.rating()); + } + if (save_cover) { + SetEmbeddedArt(file_mp4, tag, cover.data, cover.mime_type); + } } } else if (TagLib::RIFF::WAV::File *file_wav = dynamic_cast(fileref->file())) { TagLib::ID3v2::Tag *tag = file_wav->ID3v2Tag(); - if (save_tags) { - SaveID3v2Tag(tag, song); - } - if (save_playcount) { - SetPlaycount(tag, song); - } - if (save_rating) { - SetRating(tag, song); - } - if (save_cover) { - SetEmbeddedArt(tag, cover.data, cover.mime_type); + if (tag) { + if (save_tags) { + SaveID3v2Tag(tag, song); + } + if (save_playcount) { + SetPlaycount(tag, song.playcount()); + } + if (save_rating) { + SetRating(tag, song.rating()); + } + if (save_cover) { + SetEmbeddedArt(tag, cover.data, cover.mime_type); + } } } @@ -1008,17 +1031,19 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c // apart, so we keep specific behavior for some formats by adding another "else if" block above. if (!is_flac) { if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast(fileref->file()->tag())) { - if (save_tags) { - SetVorbisComments(xiph_comment, song); - } - if (save_playcount) { - SetPlaycount(xiph_comment, song); - } - if (save_rating) { - SetRating(xiph_comment, song); - } - if (save_cover) { - SetEmbeddedArt(xiph_comment, cover.data, cover.mime_type); + if (xiph_comment) { + if (save_tags) { + SetVorbisComments(xiph_comment, song); + } + if (save_playcount) { + SetPlaycount(xiph_comment, song.playcount()); + } + if (save_rating) { + SetRating(xiph_comment, song.rating()); + } + if (save_cover) { + SetEmbeddedArt(xiph_comment, cover.data, cover.mime_type); + } } } } @@ -1031,7 +1056,7 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c } #endif // Q_OS_LINUX - return success; + return success ? Result(Result::ErrorCode::Success) : Result(Result::ErrorCode::FileSaveError); } @@ -1154,9 +1179,11 @@ void TagReaderTagLib::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3 } -QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { +TagReaderBase::Result TagReaderTagLib::LoadEmbeddedArt(const QString &filename, QByteArray &data) const { - if (filename.isEmpty()) return QByteArray(); + if (filename.isEmpty()) { + return Result::ErrorCode::FilenameMissing; + } qLog(Debug) << "Loading art from" << filename; @@ -1166,7 +1193,10 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { TagLib::FileRef fileref(QFile::encodeName(filename).constData()); #endif - if (fileref.isNull() || !fileref.file()) return QByteArray(); + if (fileref.isNull() || !fileref.file()) { + qLog(Error) << "TagLib hasn't been able to open file" << filename; + return Result::ErrorCode::FileOpenError; + } // FLAC if (TagLib::FLAC::File *flac_file = dynamic_cast(fileref.file())) { @@ -1175,9 +1205,9 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { if (!pictures.isEmpty()) { for (TagLib::FLAC::Picture *picture : pictures) { if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) { - QByteArray data(picture->data().data(), picture->data().size()); + data = QByteArray(picture->data().data(), picture->data().size()); if (!data.isEmpty()) { - return data; + return Result::ErrorCode::Success; } } } @@ -1188,21 +1218,30 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { // WavPack if (TagLib::WavPack::File *wavpack_file = dynamic_cast(fileref.file())) { if (wavpack_file->APETag()) { - return LoadEmbeddedAPEArt(wavpack_file->APETag()->itemListMap()); + data = LoadEmbeddedAPEArt(wavpack_file->APETag()->itemListMap()); + if (!data.isEmpty()) { + return Result::ErrorCode::Success; + } } } // APE if (TagLib::APE::File *ape_file = dynamic_cast(fileref.file())) { if (ape_file->APETag()) { - return LoadEmbeddedAPEArt(ape_file->APETag()->itemListMap()); + data = LoadEmbeddedAPEArt(ape_file->APETag()->itemListMap()); + if (!data.isEmpty()) { + return Result::ErrorCode::Success; + } } } // MPC if (TagLib::MPC::File *mpc_file = dynamic_cast(fileref.file())) { if (mpc_file->APETag()) { - return LoadEmbeddedAPEArt(mpc_file->APETag()->itemListMap()); + data = LoadEmbeddedAPEArt(mpc_file->APETag()->itemListMap()); + if (!data.isEmpty()) { + return Result::ErrorCode::Success; + } } } @@ -1214,9 +1253,9 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { if (!pictures.isEmpty()) { for (TagLib::FLAC::Picture *picture : pictures) { if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) { - QByteArray data(picture->data().data(), picture->data().size()); + data = QByteArray(picture->data().data(), picture->data().size()); if (!data.isEmpty()) { - return data; + return Result::ErrorCode::Success; } } } @@ -1224,10 +1263,12 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { // Ogg lacks a definitive standard for embedding cover art, but it seems b64 encoding a field called COVERART is the general convention if (map.contains("COVERART")) { - return QByteArray::fromBase64(map["COVERART"].toString().toCString()); + data = QByteArray::fromBase64(map["COVERART"].toString().toCString()); + if (!data.isEmpty()) { + return Result::ErrorCode::Success; + } } - return QByteArray(); } // MP3 @@ -1235,12 +1276,15 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { if (file_mp3->ID3v2Tag()) { TagLib::ID3v2::FrameList apic_frames = file_mp3->ID3v2Tag()->frameListMap()["APIC"]; if (apic_frames.isEmpty()) { - return QByteArray(); + return Result::ErrorCode::Success; } TagLib::ID3v2::AttachedPictureFrame *picture = static_cast(apic_frames.front()); - return QByteArray(reinterpret_cast(picture->picture().data()), picture->picture().size()); + data = QByteArray(reinterpret_cast(picture->picture().data()), picture->picture().size()); + if (!data.isEmpty()) { + return Result::ErrorCode::Success; + } } } @@ -1253,30 +1297,31 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { if (!art_list.isEmpty()) { // Just take the first one for now const TagLib::MP4::CoverArt &art = art_list.front(); - return QByteArray(art.data().data(), art.data().size()); + data = QByteArray(art.data().data(), art.data().size()); + if (!data.isEmpty()) { + return Result::ErrorCode::Success; + } } } } - return QByteArray(); + return Result::ErrorCode::Success; } QByteArray TagReaderTagLib::LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const { - QByteArray ret; - TagLib::APE::ItemListMap::ConstIterator it = map.find("COVER ART (FRONT)"); if (it != map.end()) { TagLib::ByteVector data = it->second.binaryData(); int pos = data.find('\0') + 1; if ((pos > 0) && (static_cast(pos) < data.size())) { - ret = QByteArray(data.data() + pos, data.size() - pos); + return QByteArray(data.data() + pos, data.size() - pos); } } - return ret; + return QByteArray(); } @@ -1356,15 +1401,15 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::T } -bool TagReaderTagLib::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const { +TagReaderBase::Result TagReaderTagLib::SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const { - if (request.filename().empty()) return false; - - const QString filename = QString::fromUtf8(request.filename().data(), static_cast(request.filename().size())); + if (filename.isEmpty()) { + return Result::ErrorCode::FilenameMissing; + } qLog(Debug) << "Saving art to" << filename; - const Cover cover = LoadCoverFromRequest(request); + const Cover cover = LoadCoverFromRequest(filename, request); #ifdef Q_OS_WIN32 TagLib::FileRef fileref(filename.toStdWString().c_str()); @@ -1372,13 +1417,17 @@ bool TagReaderTagLib::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtReque TagLib::FileRef fileref(QFile::encodeName(filename).constData()); #endif - if (fileref.isNull() || !fileref.file()) return false; + if (fileref.isNull() || !fileref.file()) { + qLog(Error) << "TagLib could not open file" << filename; + return Result::ErrorCode::FileOpenError; + } // FLAC if (TagLib::FLAC::File *flac_file = dynamic_cast(fileref.file())) { TagLib::Ogg::XiphComment *xiph_comment = flac_file->xiphComment(true); - if (!xiph_comment) return false; - SetEmbeddedArt(flac_file, xiph_comment, cover.data, cover.mime_type); + if (xiph_comment) { + SetEmbeddedArt(flac_file, xiph_comment, cover.data, cover.mime_type); + } } // Ogg Vorbis / Opus / Speex @@ -1389,19 +1438,24 @@ bool TagReaderTagLib::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtReque // MP3 else if (TagLib::MPEG::File *file_mp3 = dynamic_cast(fileref.file())) { TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag(); - if (!tag) return false; - SetEmbeddedArt(tag, cover.data, cover.mime_type); + if (tag) { + SetEmbeddedArt(tag, cover.data, cover.mime_type); + } } // MP4/AAC else if (TagLib::MP4::File *aac_file = dynamic_cast(fileref.file())) { TagLib::MP4::Tag *tag = aac_file->tag(); - if (!tag) return false; - SetEmbeddedArt(aac_file, tag, cover.data, cover.mime_type); + if (tag) { + SetEmbeddedArt(aac_file, tag, cover.data, cover.mime_type); + } } // Not supported. - else return false; + else { + qLog(Error) << "Saving embedded art is not supported for %1" << filename; + return Result::ErrorCode::Unsupported; + } const bool success = fileref.file()->save(); #ifdef Q_OS_LINUX @@ -1411,7 +1465,7 @@ bool TagReaderTagLib::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtReque } #endif // Q_OS_LINUX - return success; + return success ? Result::ErrorCode::Success : Result::ErrorCode::FileSaveError; } @@ -1433,10 +1487,10 @@ TagLib::ID3v2::PopularimeterFrame *TagReaderTagLib::GetPOPMFrameFromTag(TagLib:: } -void TagReaderTagLib::SetPlaycount(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const { +void TagReaderTagLib::SetPlaycount(TagLib::Ogg::XiphComment *xiph_comment, const uint playcount) const { - if (song.playcount() > 0) { - xiph_comment->addField("FMPS_PLAYCOUNT", TagLib::String::number(static_cast(song.playcount())), true); + if (playcount > 0) { + xiph_comment->addField("FMPS_PLAYCOUNT", TagLib::String::number(static_cast(playcount)), true); } else { xiph_comment->removeFields("FMPS_PLAYCOUNT"); @@ -1444,10 +1498,10 @@ void TagReaderTagLib::SetPlaycount(TagLib::Ogg::XiphComment *xiph_comment, const } -void TagReaderTagLib::SetPlaycount(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const { +void TagReaderTagLib::SetPlaycount(TagLib::APE::Tag *tag, const uint playcount) const { - if (song.playcount() > 0) { - tag->setItem("FMPS_Playcount", TagLib::APE::Item("FMPS_Playcount", TagLib::String::number(static_cast(song.playcount())))); + if (playcount > 0) { + tag->setItem("FMPS_Playcount", TagLib::APE::Item("FMPS_Playcount", TagLib::String::number(static_cast(playcount)))); } else { tag->removeItem("FMPS_Playcount"); @@ -1455,20 +1509,20 @@ void TagReaderTagLib::SetPlaycount(TagLib::APE::Tag *tag, const spb::tagreader:: } -void TagReaderTagLib::SetPlaycount(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const { +void TagReaderTagLib::SetPlaycount(TagLib::ID3v2::Tag *tag, const uint playcount) const { - SetUserTextFrame(QStringLiteral("FMPS_Playcount"), QString::number(song.playcount()), tag); + SetUserTextFrame(QStringLiteral("FMPS_Playcount"), QString::number(playcount), tag); TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag); if (frame) { - frame->setCounter(song.playcount()); + frame->setCounter(playcount); } } -void TagReaderTagLib::SetPlaycount(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const { +void TagReaderTagLib::SetPlaycount(TagLib::MP4::Tag *tag, const uint playcount) const { - if (song.playcount() > 0) { - tag->setItem(kMP4_FMPS_Playcount_ID, TagLib::MP4::Item(TagLib::String::number(static_cast(song.playcount())))); + if (playcount > 0) { + tag->setItem(kMP4_FMPS_Playcount_ID, TagLib::MP4::Item(TagLib::String::number(static_cast(playcount)))); } else { tag->removeItem(kMP4_FMPS_Playcount_ID); @@ -1476,10 +1530,10 @@ void TagReaderTagLib::SetPlaycount(TagLib::MP4::Tag *tag, const spb::tagreader:: } -void TagReaderTagLib::SetPlaycount(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const { +void TagReaderTagLib::SetPlaycount(TagLib::ASF::Tag *tag, const uint playcount) const { - if (song.playcount() > 0) { - tag->setAttribute("FMPS/Playcount", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.playcount())))); + if (playcount > 0) { + tag->setAttribute("FMPS/Playcount", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(playcount)))); } else { tag->removeItem("FMPS/Playcount"); @@ -1487,169 +1541,69 @@ void TagReaderTagLib::SetPlaycount(TagLib::ASF::Tag *tag, const spb::tagreader:: } -bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const { +TagReaderBase::Result TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const uint playcount) const { - if (filename.isEmpty()) return false; + if (filename.isEmpty()) { + return Result::ErrorCode::FilenameMissing; + } qLog(Debug) << "Saving song playcount to" << filename; std::unique_ptr fileref(factory_->GetFileRef(filename)); - if (!fileref || fileref->isNull()) return false; + if (!fileref || fileref->isNull()) { + qLog(Error) << "TagLib hasn't been able to open file" << filename; + return Result::ErrorCode::FileOpenError; + } if (TagLib::FLAC::File *flac_file = dynamic_cast(fileref->file())) { TagLib::Ogg::XiphComment *xiph_comment = flac_file->xiphComment(true); - if (!xiph_comment) return false; - SetPlaycount(xiph_comment, song); + if (xiph_comment) { + SetPlaycount(xiph_comment, playcount); + } } else if (TagLib::WavPack::File *wavpack_file = dynamic_cast(fileref->file())) { TagLib::APE::Tag *tag = wavpack_file->APETag(true); - if (!tag) return false; - SetPlaycount(tag, song); + if (tag) { + SetPlaycount(tag, playcount); + } } else if (TagLib::APE::File *ape_file = dynamic_cast(fileref->file())) { TagLib::APE::Tag *tag = ape_file->APETag(true); - if (!tag) return false; - SetPlaycount(tag, song); + if (tag) { + SetPlaycount(tag, playcount); + } } else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast(fileref->file()->tag())) { - SetPlaycount(xiph_comment, song); + if (xiph_comment) { + SetPlaycount(xiph_comment, playcount); + } } else if (TagLib::MPEG::File *mpeg_file = dynamic_cast(fileref->file())) { TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true); - if (!tag) return false; - SetPlaycount(tag, song); + if (tag) { + SetPlaycount(tag, playcount); + } } else if (TagLib::MP4::File *mp4_file = dynamic_cast(fileref->file())) { TagLib::MP4::Tag *tag = mp4_file->tag(); - if (!tag) return false; - SetPlaycount(tag, song); + if (tag) { + SetPlaycount(tag, playcount); + } } else if (TagLib::MPC::File *mpc_file = dynamic_cast(fileref->file())) { TagLib::APE::Tag *tag = mpc_file->APETag(true); - if (!tag) return false; - SetPlaycount(tag, song); + if (tag) { + SetPlaycount(tag, playcount); + } } else if (TagLib::ASF::File *asf_file = dynamic_cast(fileref->file())) { TagLib::ASF::Tag *tag = asf_file->tag(); - if (!tag) return false; - if (song.playcount() > 0) { - tag->addAttribute("FMPS/Playcount", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.playcount())))); + if (tag && playcount > 0) { + tag->addAttribute("FMPS/Playcount", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(playcount)))); } } else { - return true; - } - - bool success = fileref->save(); -#ifdef Q_OS_LINUX - if (success) { - // Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does) - utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0); - } -#endif // Q_OS_LINUX - - return success; - -} - -void TagReaderTagLib::SetRating(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const { - - if (song.rating() > 0.0F) { - xiph_comment->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())), true); - } - else { - xiph_comment->removeFields("FMPS_RATING"); - } - -} - -void TagReaderTagLib::SetRating(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const { - - if (song.rating() > 0.0F) { - tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating()))))); - } - else { - tag->removeItem("FMPS_Rating"); - } - -} - -void TagReaderTagLib::SetRating(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const { - - SetUserTextFrame(QStringLiteral("FMPS_Rating"), QString::number(song.rating()), tag); - TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag); - if (frame) { - frame->setRating(ConvertToPOPMRating(song.rating())); - } - -} - -void TagReaderTagLib::SetRating(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const { - - tag->setItem(kMP4_FMPS_Rating_ID, TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))); - -} - -void TagReaderTagLib::SetRating(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const { - - tag->addAttribute("FMPS/Rating", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.rating())))); - -} - -bool TagReaderTagLib::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const { - - if (filename.isNull()) return false; - - qLog(Debug) << "Saving song rating to" << filename; - - if (song.rating() < 0) { - return true; - } - - std::unique_ptr fileref(factory_->GetFileRef(filename)); - - if (!fileref || fileref->isNull()) return false; - - if (TagLib::FLAC::File *flac_file = dynamic_cast(fileref->file())) { - TagLib::Ogg::XiphComment *xiph_comment = flac_file->xiphComment(true); - if (!xiph_comment) return false; - SetRating(xiph_comment, song); - } - else if (TagLib::WavPack::File *wavpack_file = dynamic_cast(fileref->file())) { - TagLib::APE::Tag *tag = wavpack_file->APETag(true); - if (!tag) return false; - SetRating(tag, song); - } - else if (TagLib::APE::File *ape_file = dynamic_cast(fileref->file())) { - TagLib::APE::Tag *tag = ape_file->APETag(true); - if (!tag) return false; - SetRating(tag, song); - } - else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast(fileref->file()->tag())) { - SetRating(xiph_comment, song); - } - else if (TagLib::MPEG::File *mpeg_file = dynamic_cast(fileref->file())) { - TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true); - if (!tag) return false; - SetRating(tag, song); - } - else if (TagLib::MP4::File *mp4_file = dynamic_cast(fileref->file())) { - TagLib::MP4::Tag *tag = mp4_file->tag(); - if (!tag) return false; - SetRating(tag, song); - } - else if (TagLib::ASF::File *asf_file = dynamic_cast(fileref->file())) { - TagLib::ASF::Tag *tag = asf_file->tag(); - if (!tag) return false; - SetRating(tag, song); - } - else if (TagLib::MPC::File *mpc_file = dynamic_cast(fileref->file())) { - TagLib::APE::Tag *tag = mpc_file->APETag(true); - if (!tag) return false; - SetRating(tag, song); - } - else { - return true; + return Result::ErrorCode::Unsupported; } const bool success = fileref->save(); @@ -1660,6 +1614,135 @@ bool TagReaderTagLib::SaveSongRatingToFile(const QString &filename, const spb::t } #endif // Q_OS_LINUX - return success; + return success ? Result::ErrorCode::Success : Result::ErrorCode::FileSaveError; + +} + +void TagReaderTagLib::SetRating(TagLib::Ogg::XiphComment *xiph_comment, const float rating) const { + + if (rating > 0.0F) { + xiph_comment->addField("FMPS_RATING", QStringToTaglibString(QString::number(rating)), true); + } + else { + xiph_comment->removeFields("FMPS_RATING"); + } + +} + +void TagReaderTagLib::SetRating(TagLib::APE::Tag *tag, const float rating) const { + + if (rating > 0.0F) { + tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(rating))))); + } + else { + tag->removeItem("FMPS_Rating"); + } + +} + +void TagReaderTagLib::SetRating(TagLib::ID3v2::Tag *tag, const float rating) const { + + SetUserTextFrame(QStringLiteral("FMPS_Rating"), QString::number(rating), tag); + TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag); + if (frame) { + frame->setRating(ConvertToPOPMRating(rating)); + } + +} + +void TagReaderTagLib::SetRating(TagLib::MP4::Tag *tag, const float rating) const { + + tag->setItem(kMP4_FMPS_Rating_ID, TagLib::StringList(QStringToTaglibString(QString::number(rating)))); + +} + +void TagReaderTagLib::SetRating(TagLib::ASF::Tag *tag, const float rating) const { + + tag->addAttribute("FMPS/Rating", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(rating)))); + +} + +TagReaderBase::Result TagReaderTagLib::SaveSongRatingToFile(const QString &filename, const float rating) const { + + if (filename.isEmpty()) { + return Result::ErrorCode::FilenameMissing; + } + + qLog(Debug) << "Saving song rating to" << filename; + + if (rating < 0) { + return Result::ErrorCode::Success; + } + + std::unique_ptr fileref(factory_->GetFileRef(filename)); + + if (!fileref || fileref->isNull()) { + qLog(Error) << "TagLib hasn't been able to open file" << filename; + return Result::ErrorCode::FileOpenError; + } + + if (TagLib::FLAC::File *flac_file = dynamic_cast(fileref->file())) { + TagLib::Ogg::XiphComment *xiph_comment = flac_file->xiphComment(true); + if (xiph_comment) { + SetRating(xiph_comment, rating); + } + } + else if (TagLib::WavPack::File *wavpack_file = dynamic_cast(fileref->file())) { + TagLib::APE::Tag *tag = wavpack_file->APETag(true); + if (tag) { + SetRating(tag, rating); + } + } + else if (TagLib::APE::File *ape_file = dynamic_cast(fileref->file())) { + TagLib::APE::Tag *tag = ape_file->APETag(true); + if (tag) { + SetRating(tag, rating); + } + } + else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast(fileref->file()->tag())) { + SetRating(xiph_comment, rating); + } + else if (TagLib::MPEG::File *mpeg_file = dynamic_cast(fileref->file())) { + TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true); + if (tag) { + SetRating(tag, rating); + } + } + else if (TagLib::MP4::File *mp4_file = dynamic_cast(fileref->file())) { + TagLib::MP4::Tag *tag = mp4_file->tag(); + if (tag) { + SetRating(tag, rating); + } + } + else if (TagLib::ASF::File *asf_file = dynamic_cast(fileref->file())) { + TagLib::ASF::Tag *tag = asf_file->tag(); + if (tag) { + SetRating(tag, rating); + } + } + else if (TagLib::MPC::File *mpc_file = dynamic_cast(fileref->file())) { + TagLib::APE::Tag *tag = mpc_file->APETag(true); + if (tag) { + SetRating(tag, rating); + } + } + else { + qLog(Error) << "Unsupported file for saving rating for" << filename; + return Result::ErrorCode::Unsupported; + } + + const bool success = fileref->save(); +#ifdef Q_OS_LINUX + if (success) { + // Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does) + utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0); + } +#endif // Q_OS_LINUX + + if (!success) { + qLog(Error) << "TagLib hasn't been able to save file" << filename; + } + + return success ? Result::ErrorCode::Success : Result::ErrorCode::FileSaveError; } diff --git a/ext/libstrawberry-tagreader/tagreadertaglib.h b/ext/libstrawberry-tagreader/tagreadertaglib.h index 10fa26312..b22683c8f 100644 --- a/ext/libstrawberry-tagreader/tagreadertaglib.h +++ b/ext/libstrawberry-tagreader/tagreadertaglib.h @@ -1,6 +1,6 @@ /* This file is part of Strawberry. Copyright 2013, David Sansome - Copyright 2018-2023, Jonas Kvinge + Copyright 2018-2024, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -54,14 +54,14 @@ class TagReaderTagLib : public TagReaderBase { bool IsMediaFile(const QString &filename) const override; - bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override; - bool SaveFile(const spb::tagreader::SaveFileRequest &request) 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; - QByteArray LoadEmbeddedArt(const QString &filename) const override; - bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override; + Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const; + Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override; - bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override; - bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override; + Result SaveSongPlaycountToFile(const QString &filename, const uint playcount) const override; + Result SaveSongRatingToFile(const QString &filename, const float rating) const override; static void TStringToStdString(const TagLib::String &tag, std::string *output); @@ -86,17 +86,17 @@ class TagReaderTagLib : public TagReaderBase { static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag); - void SetPlaycount(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const; - void SetPlaycount(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const; - void SetPlaycount(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const; - void SetPlaycount(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const; - void SetPlaycount(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const; + void SetPlaycount(TagLib::Ogg::XiphComment *xiph_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 *xiph_comment, const spb::tagreader::SongMetadata &song) const; - void SetRating(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const; - void SetRating(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const; - void SetRating(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const; - void SetRating(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const; + void SetRating(TagLib::Ogg::XiphComment *xiph_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 *xiph_comment, const QByteArray &data, const QString &mime_type) const; void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const; diff --git a/ext/libstrawberry-tagreader/tagreadertagparser.cpp b/ext/libstrawberry-tagreader/tagreadertagparser.cpp index 51a783f0e..e8135b785 100644 --- a/ext/libstrawberry-tagreader/tagreadertagparser.cpp +++ b/ext/libstrawberry-tagreader/tagreadertagparser.cpp @@ -1,5 +1,5 @@ /* This file is part of Strawberry. - Copyright 2021, Jonas Kvinge + Copyright 2021-2024, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -53,7 +53,7 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const { qLog(Debug) << "Checking for valid file" << filename; QFileInfo fileinfo(filename); - if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false; + if (!fileinfo.exists() || fileinfo.suffix().compare(QLatin1String("bak"), Qt::CaseInsensitive) == 0) return false; try { TagParser::MediaFileInfo taginfo; @@ -94,13 +94,13 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const { } -bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const { +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("bak", Qt::CaseInsensitive) == 0) return false; + 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(); @@ -134,19 +134,19 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM taginfo.parseContainerFormat(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } taginfo.parseTracks(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } taginfo.parseTags(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } for (const TagParser::DiagMessage &msg : diag) { @@ -205,7 +205,7 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM if (song->filetype() == spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_UNKNOWN) { taginfo.close(); - return false; + return Result::ErrorCode::Unsupported; } for (TagParser::Tag *tag : taginfo.tags()) { @@ -246,21 +246,20 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM taginfo.close(); - return true; + return Result::ErrorCode::Success; } catch(...) { - return false; + return Result::ErrorCode::FileParseError; } } -bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request) const { +TagReaderBase::Result TagReaderTagParser::WriteFile(const QString &filename, const spb::tagreader::WriteFileRequest &request) const { - if (request.filename().empty()) return false; + if (request.filename().empty()) return Result::ErrorCode::FilenameMissing; - const QString filename = QString::fromUtf8(request.filename().data(), request.filename().size()); - const spb::tagreader::SongMetadata song = request.metadata(); + 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(); @@ -268,21 +267,21 @@ bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request QStringList save_tags_options; if (save_tags) { - save_tags_options << "tags"; + save_tags_options << QStringLiteral("tags"); } if (save_playcount) { - save_tags_options << "playcount"; + save_tags_options << QStringLiteral("playcount"); } if (save_rating) { - save_tags_options << "rating"; + save_tags_options << QStringLiteral("rating"); } if (save_cover) { - save_tags_options << "embedded cover"; + save_tags_options << QStringLiteral("embedded cover"); } - qLog(Debug) << "Saving" << save_tags_options.join(", ") << "to" << filename; + qLog(Debug) << "Saving" << save_tags_options.join(QLatin1String(", ")) << "to" << filename; - const QByteArray cover_data = LoadCoverDataFromRequest(request); + const Cover cover = LoadCoverFromRequest(filename, request); try { TagParser::MediaFileInfo taginfo; @@ -298,19 +297,19 @@ bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request taginfo.parseContainerFormat(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } taginfo.parseTracks(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } taginfo.parseTags(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } if (taginfo.tags().size() <= 0) { @@ -335,13 +334,13 @@ bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear())); } if (save_playcount) { - SaveSongPlaycountToFile(tag, song); + SaveSongPlaycountToFile(tag, song.playcount()); } if (save_rating) { - SaveSongRatingToFile(tag, song); + SaveSongRatingToFile(tag, song.rating()); } if (save_cover) { - SaveEmbeddedArt(tag, cover_data); + SaveEmbeddedArt(tag, cover.data); } } @@ -352,17 +351,17 @@ bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request qLog(Debug) << QString::fromStdString(msg.message()); } - return true; + return Result::ErrorCode::Success; } catch(...) {} - return false; + return Result::ErrorCode::FileParseError; } -QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const { +TagReaderBase::Result TagReaderTagParser::LoadEmbeddedArt(const QString &filename, QByteArray &data) const { - if (filename.isEmpty()) return QByteArray(); + if (filename.isEmpty()) return Result::ErrorCode::FilenameMissing; qLog(Debug) << "Loading art from" << filename; @@ -383,20 +382,20 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const { taginfo.parseContainerFormat(diag, progress); if (progress.isAborted()) { taginfo.close(); - return QByteArray(); + return Result::ErrorCode::FileParseError; } taginfo.parseTags(diag, progress); if (progress.isAborted()) { taginfo.close(); - return QByteArray(); + return Result::ErrorCode::FileParseError; } for (TagParser::Tag *tag : taginfo.tags()) { if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) { - QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize()); + data = QByteArray(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize()); taginfo.close(); - return data; + return Result::ErrorCode::Success; } } @@ -409,7 +408,7 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const { } catch(...) {} - return QByteArray(); + return Result::ErrorCode::FileParseError; } @@ -419,15 +418,13 @@ void TagReaderTagParser::SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray & } -bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const { +TagReaderBase::Result TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const { - if (request.filename().empty()) return false; - - const QString filename = QString::fromUtf8(request.filename().data(), request.filename().size()); + if (request.filename().empty()) return Result::ErrorCode::FilenameMissing; qLog(Debug) << "Saving art to" << filename; - const QByteArray cover_data = LoadCoverDataFromRequest(request); + const Cover cover = LoadCoverFromRequest(filename, request); try { @@ -446,13 +443,13 @@ bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRe taginfo.parseContainerFormat(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } taginfo.parseTags(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } if (taginfo.tags().size() <= 0) { @@ -460,7 +457,7 @@ bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRe } for (TagParser::Tag *tag : taginfo.tags()) { - SaveEmbeddedArt(tag, cover_data); + SaveEmbeddedArt(tag, cover.data); } taginfo.applyChanges(diag, progress); @@ -470,28 +467,40 @@ bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRe qLog(Debug) << QString::fromStdString(msg.message()); } - return true; + return Result::ErrorCode::Success; } catch(...) {} - return false; + return Result::ErrorCode::FileParseError; } -void TagReaderTagParser::SaveSongPlaycountToFile(TagParser::Tag*, const spb::tagreader::SongMetadata&) const {} +void TagReaderTagParser::SaveSongPlaycountToFile(TagParser::Tag *tag, const uint playcount) const { -bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; } - -void TagReaderTagParser::SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const { - - tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating()))); + Q_UNUSED(tag); + Q_UNUSED(playcount); } -bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const { +TagReaderBase::Result TagReaderTagParser::SaveSongPlaycountToFile(const QString &filename, const uint playcount) const { - if (filename.isEmpty()) return false; + 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; @@ -509,19 +518,19 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb taginfo.parseContainerFormat(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } taginfo.parseTracks(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } taginfo.parseTags(diag, progress); if (progress.isAborted()) { taginfo.close(); - return false; + return Result::ErrorCode::FileParseError; } if (taginfo.tags().size() <= 0) { @@ -529,7 +538,7 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb } for (TagParser::Tag *tag : taginfo.tags()) { - SaveSongRatingToFile(tag, song); + SaveSongRatingToFile(tag, rating); } taginfo.applyChanges(diag, progress); @@ -539,10 +548,10 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb qLog(Debug) << QString::fromStdString(msg.message()); } - return true; + return Result::ErrorCode::Success; } catch(...) {} - return false; + return Result::ErrorCode::FileParseError; } diff --git a/ext/libstrawberry-tagreader/tagreadertagparser.h b/ext/libstrawberry-tagreader/tagreadertagparser.h index 31a1c4fee..9266386a5 100644 --- a/ext/libstrawberry-tagreader/tagreadertagparser.h +++ b/ext/libstrawberry-tagreader/tagreadertagparser.h @@ -1,5 +1,5 @@ /* This file is part of Strawberry. - Copyright 2021, Jonas Kvinge + Copyright 2021-2024, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,18 +41,18 @@ class TagReaderTagParser : public TagReaderBase { bool IsMediaFile(const QString &filename) const override; - bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override; - bool SaveFile(const spb::tagreader::SaveFileRequest &request) 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; - QByteArray LoadEmbeddedArt(const QString &filename) const override; - bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override; + Result LoadEmbeddedArt(const QString &filename, QByteArray &data) const override; + Result SaveEmbeddedArt(const QString &filename, const spb::tagreader::SaveEmbeddedArtRequest &request) const override; - bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override; - bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override; + 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 spb::tagreader::SongMetadata &song) const; - void SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const; + 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: diff --git a/ext/strawberry-tagreader/CMakeLists.txt b/ext/strawberry-tagreader/CMakeLists.txt index 3fb41d783..cdb5e0919 100644 --- a/ext/strawberry-tagreader/CMakeLists.txt +++ b/ext/strawberry-tagreader/CMakeLists.txt @@ -9,11 +9,11 @@ qt_wrap_cpp(MOC ${HEADERS}) link_directories(${GLIB_LIBRARY_DIRS}) -if(USE_TAGLIB AND TAGLIB_FOUND) +if(HAVE_TAGLIB) link_directories(${TAGLIB_LIBRARY_DIRS}) endif() -if(USE_TAGPARSER AND TAGPARSER_FOUND) +if(HAVE_TAGPARSER) link_directories(${TAGPARSER_LIBRARY_DIRS}) endif() @@ -39,12 +39,12 @@ target_link_libraries(strawberry-tagreader PRIVATE libstrawberry-tagreader ) -if(USE_TAGLIB AND TAGLIB_FOUND) +if(HAVE_TAGLIB) target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS}) target_link_libraries(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES}) endif() -if(USE_TAGPARSER AND TAGPARSER_FOUND) +if(HAVE_TAGPARSER) target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS}) target_link_libraries(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES}) endif() diff --git a/ext/strawberry-tagreader/tagreaderworker.cpp b/ext/strawberry-tagreader/tagreaderworker.cpp index b88571564..cf33fb28b 100644 --- a/ext/strawberry-tagreader/tagreaderworker.cpp +++ b/ext/strawberry-tagreader/tagreaderworker.cpp @@ -1,6 +1,6 @@ /* This file is part of Strawberry. Copyright 2011, David Sansome - Copyright 2018-2021, Jonas Kvinge + Copyright 2018-2024, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ #include "config.h" +#include #include #include @@ -27,20 +28,37 @@ #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(socket, parent) {} + : AbstractMessageHandler(socket, parent) { + +#ifdef HAVE_TAGLIB + tagreaders_ << make_shared(); + tagreaders_ << make_shared(); +#endif + +#ifdef HAVE_TAGPARSER + tagreaders_ << make_shared(); +#endif + +} void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) { spb::tagreader::Message reply; - bool success = HandleMessage(message, reply, &tag_reader_); - if (!success) { -#if defined(USE_TAGLIB) - HandleMessage(message, reply, &tag_reader_gme_); -#endif - } - + HandleMessage(message, reply); SendReply(message, &reply); } @@ -53,48 +71,123 @@ void TagReaderWorker::DeviceClosed() { } -bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) { +void TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply) { - if (message.has_is_media_file_request()) { - const QString filename = QString::fromUtf8(message.is_media_file_request().filename().data(), static_cast(message.is_media_file_request().filename().size())); - bool success = reader->IsMediaFile(filename); - reply.mutable_is_media_file_response()->set_success(success); - return success; - } - if (message.has_read_file_request()) { - const QString filename = QString::fromUtf8(message.read_file_request().filename().data(), static_cast(message.read_file_request().filename().size())); - bool success = reader->ReadFile(filename, reply.mutable_read_file_response()->mutable_metadata()); - return success; - } - if (message.has_save_file_request()) { - bool success = reader->SaveFile(message.save_file_request()); - reply.mutable_save_file_response()->set_success(success); - return success; - } - if (message.has_load_embedded_art_request()) { - const QString filename = QString::fromUtf8(message.load_embedded_art_request().filename().data(), static_cast(message.load_embedded_art_request().filename().size())); - QByteArray data = reader->LoadEmbeddedArt(filename); - reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size()); - return true; - } - if (message.has_save_embedded_art_request()) { - bool success = reader->SaveEmbeddedArt(message.save_embedded_art_request()); - reply.mutable_save_embedded_art_response()->set_success(success); - return success; - } - if (message.has_save_song_playcount_to_file_request()) { - const QString filename = QString::fromUtf8(message.save_song_playcount_to_file_request().filename().data(), static_cast(message.save_song_playcount_to_file_request().filename().size())); - bool success = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().metadata()); - reply.mutable_save_song_playcount_to_file_response()->set_success(success); - return success; - } - if (message.has_save_song_rating_to_file_request()) { - const QString filename = QString::fromUtf8(message.save_song_rating_to_file_request().filename().data(), static_cast(message.save_song_rating_to_file_request().filename().size())); - bool success = reader->SaveSongRatingToFile(filename, message.save_song_rating_to_file_request().metadata()); - reply.mutable_save_song_rating_to_file_response()->set_success(success); - return success; - } + for (shared_ptr reader : tagreaders_) { - return false; + 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()); + } + } + } + + } } diff --git a/ext/strawberry-tagreader/tagreaderworker.h b/ext/strawberry-tagreader/tagreaderworker.h index cb0c12fec..441d78f1e 100644 --- a/ext/strawberry-tagreader/tagreaderworker.h +++ b/ext/strawberry-tagreader/tagreaderworker.h @@ -1,6 +1,6 @@ /* This file is part of Strawberry. Copyright 2011, David Sansome - Copyright 2018-2021, Jonas Kvinge + Copyright 2018-2024, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,19 +21,19 @@ #include "config.h" +#include + #include +#include #include "core/messagehandler.h" -#if defined(USE_TAGLIB) -# include "tagreadertaglib.h" -# include "tagreadergme.h" -#elif defined(USE_TAGPARSER) -# include "tagreadertagparser.h" -#endif #include "tagreadermessages.pb.h" class QIODevice; +class TagReaderBase; + +using std::shared_ptr; class TagReaderWorker : public AbstractMessageHandler { Q_OBJECT @@ -46,15 +46,9 @@ class TagReaderWorker : public AbstractMessageHandler { void DeviceClosed() override; private: - // Handle message using specific TagReaderBase implementation. Returns true on successful message handle. - bool HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase* reader); + void HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply); -#if defined(USE_TAGLIB) - TagReaderTagLib tag_reader_; - TagReaderGME tag_reader_gme_; -#elif defined(USE_TAGPARSER) - TagReaderTagParser tag_reader_; -#endif + QList> tagreaders_; }; #endif // TAGREADERWORKER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 68ab5f18b..6cf36f280 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1075,11 +1075,11 @@ if(HAVE_LIBMTP) link_directories(${LIBMTP_LIBRARY_DIRS}) endif() -if(USE_TAGLIB AND TAGLIB_FOUND) +if(HAVE_TAGLIB) link_directories(${TAGLIB_LIBRARY_DIRS}) endif() -if(USE_TAGPARSER AND TAGPARSER_FOUND) +if(HAVE_TAGPARSER) link_directories(${TAGPARSER_LIBRARY_DIRS}) endif() diff --git a/src/collection/collection.cpp b/src/collection/collection.cpp index 4b7916447..cda004092 100644 --- a/src/collection/collection.cpp +++ b/src/collection/collection.cpp @@ -206,8 +206,8 @@ void SCollection::SyncPlaycountAndRatingToFiles() { const qint64 nb_songs = songs.size(); int i = 0; for (const Song &song : songs) { - TagReaderClient::Instance()->UpdateSongPlaycountBlocking(song); - TagReaderClient::Instance()->UpdateSongRatingBlocking(song); + (void)TagReaderClient::Instance()->SaveSongPlaycountBlocking(song.url().toLocalFile(), song.playcount()); + (void)TagReaderClient::Instance()->SaveSongRatingBlocking(song.url().toLocalFile(), song.rating()); app_->task_manager()->SetTaskProgress(task_id, ++i, nb_songs); } app_->task_manager()->SetTaskFinished(task_id); @@ -217,7 +217,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()->UpdateSongsPlaycount(songs); + app_->tag_reader_client()->SaveSongsPlaycount(songs); } } @@ -225,7 +225,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()->UpdateSongsRating(songs); + app_->tag_reader_client()->SaveSongsRating(songs); } } diff --git a/src/collection/collectionplaylistitem.cpp b/src/collection/collectionplaylistitem.cpp index 90e612d34..754ae44f5 100644 --- a/src/collection/collectionplaylistitem.cpp +++ b/src/collection/collectionplaylistitem.cpp @@ -41,7 +41,11 @@ QUrl CollectionPlaylistItem::Url() const { return song_.url(); } void CollectionPlaylistItem::Reload() { - TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_); + const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_); + if (!result.success()) { + qLog(Error) << "Could not reload file" << song_.url() << result.error; + return; + } UpdateTemporaryMetadata(song_); } diff --git a/src/collection/collectionwatcher.cpp b/src/collection/collectionwatcher.cpp index 6f657948b..a3b7c75be 100644 --- a/src/collection/collectionwatcher.cpp +++ b/src/collection/collectionwatcher.cpp @@ -817,8 +817,8 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, } Song song_on_disk(source_); - TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk); - if (song_on_disk.is_valid()) { + const TagReaderClient::Result 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()); song_on_disk.set_id(matching_song.id()); @@ -870,8 +870,8 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path } else { // It's a normal media file Song song(source_); - TagReaderClient::Instance()->ReadFileBlocking(file, &song); - if (song.is_valid()) { + const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song); + if (result.success() && song.is_valid()) { song.set_source(source_); PerformEBUR128Analysis(song); song.set_fingerprint(fingerprint); diff --git a/src/config.h.in b/src/config.h.in index f558d5dbe..e309faea9 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -37,8 +37,10 @@ #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 @@ -51,9 +53,6 @@ #cmakedefine ENABLE_WIN32_CONSOLE -#cmakedefine USE_TAGLIB -#cmakedefine USE_TAGPARSER - #cmakedefine HAVE_QX11APPLICATION #cmakedefine HAVE_EBUR128 diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index 868ada020..5731247d3 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -2249,7 +2249,7 @@ void MainWindow::RenumberTracks() { Song song = item->OriginalMetadata(); if (song.IsEditable()) { song.set_track(track); - TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song); + TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(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); } @@ -2280,7 +2280,7 @@ 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()->SaveFile(song.url().toLocalFile(), song); + TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(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); } diff --git a/src/core/song.cpp b/src/core/song.cpp index 7986c2d52..51625f035 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -1302,27 +1302,27 @@ void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) { d->init_from_file_ = true; d->valid_ = pb.valid(); - set_title(QString::fromUtf8(pb.title().data(), static_cast(pb.title().size()))); - set_album(QString::fromUtf8(pb.album().data(), static_cast(pb.album().size()))); - set_artist(QString::fromUtf8(pb.artist().data(), static_cast(pb.artist().size()))); - set_albumartist(QString::fromUtf8(pb.albumartist().data(), static_cast(pb.albumartist().size()))); + 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::fromUtf8(pb.genre().data(), static_cast(pb.genre().size())); + d->genre_ = QString::fromStdString(pb.genre()); d->compilation_ = pb.compilation(); - d->composer_ = QString::fromUtf8(pb.composer().data(), static_cast(pb.composer().size())); - d->performer_ = QString::fromUtf8(pb.performer().data(), static_cast(pb.performer().size())); - d->grouping_ = QString::fromUtf8(pb.grouping().data(), static_cast(pb.grouping().size())); - d->comment_ = QString::fromUtf8(pb.comment().data(), static_cast(pb.comment().size())); - d->lyrics_ = QString::fromUtf8(pb.lyrics().data(), static_cast(pb.lyrics().size())); + 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(pb.length_nanosec())); d->bitrate_ = pb.bitrate(); d->samplerate_ = pb.samplerate(); d->bitdepth_ = pb.bitdepth(); - set_url(QUrl::fromEncoded(QByteArray(pb.url().data(), static_cast(pb.url().size())))); - d->basefilename_ = QString::fromUtf8(pb.basefilename().data(), static_cast(pb.basefilename().size())); + set_url(QUrl::fromEncoded(QString::fromStdString(pb.url()).toUtf8())); + d->basefilename_ = QString::fromStdString(pb.basefilename()); d->filetype_ = static_cast(pb.filetype()); d->filesize_ = pb.filesize(); d->mtime_ = pb.mtime(); @@ -1340,19 +1340,19 @@ void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) { d->art_embedded_ = pb.has_art_embedded(); - d->acoustid_id_ = QString::fromUtf8(pb.acoustid_id().data(), static_cast(pb.acoustid_id().size())); - d->acoustid_fingerprint_ = QString::fromUtf8(pb.acoustid_fingerprint().data(), static_cast(pb.acoustid_fingerprint().size())); + d->acoustid_id_ = QString::fromStdString(pb.acoustid_id()); + d->acoustid_fingerprint_ = QString::fromStdString(pb.acoustid_fingerprint()); - d->musicbrainz_album_artist_id_ = QString::fromUtf8(pb.musicbrainz_album_artist_id().data(), static_cast(pb.musicbrainz_album_artist_id().size())); - d->musicbrainz_artist_id_ = QString::fromUtf8(pb.musicbrainz_artist_id().data(), static_cast(pb.musicbrainz_artist_id().size())); - d->musicbrainz_original_artist_id_ = QString::fromUtf8(pb.musicbrainz_original_artist_id().data(), static_cast(pb.musicbrainz_original_artist_id().size())); - d->musicbrainz_album_id_ = QString::fromUtf8(pb.musicbrainz_album_id().data(), static_cast(pb.musicbrainz_album_id().size())); - d->musicbrainz_original_album_id_ = QString::fromUtf8(pb.musicbrainz_original_album_id().data(), static_cast(pb.musicbrainz_original_album_id().size())); - d->musicbrainz_recording_id_ = QString::fromUtf8(pb.musicbrainz_recording_id().data(), static_cast(pb.musicbrainz_recording_id().size())); - d->musicbrainz_track_id_ = QString::fromUtf8(pb.musicbrainz_track_id().data(), static_cast(pb.musicbrainz_track_id().size())); - d->musicbrainz_disc_id_ = QString::fromUtf8(pb.musicbrainz_disc_id().data(), static_cast(pb.musicbrainz_disc_id().size())); - d->musicbrainz_release_group_id_ = QString::fromUtf8(pb.musicbrainz_release_group_id().data(), static_cast(pb.musicbrainz_release_group_id().size())); - d->musicbrainz_work_id_ = QString::fromUtf8(pb.musicbrainz_work_id().data(), static_cast(pb.musicbrainz_work_id().size())); + 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(); diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp index 025e108ab..5d6977f41 100644 --- a/src/core/songloader.cpp +++ b/src/core/songloader.cpp @@ -354,8 +354,11 @@ void SongLoader::EffectiveSongLoad(Song *song) { } else { // It's a normal media file - QString filename = song->url().toLocalFile(); - TagReaderClient::Instance()->ReadFileBlocking(filename, song); + const QString filename = song->url().toLocalFile(); + const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song); + if (!result.success()) { + qLog(Error) << "Could not read file" << song->url() << result.error; + } } } diff --git a/src/core/tagreaderclient.cpp b/src/core/tagreaderclient.cpp index 18ec6c6be..d1628e194 100644 --- a/src/core/tagreaderclient.cpp +++ b/src/core/tagreaderclient.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2019-2023, Jonas Kvinge + * Copyright 2019-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -73,11 +73,7 @@ void TagReaderClient::WorkerFailedToStart() { TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) { spb::tagreader::Message message; - spb::tagreader::IsMediaFileRequest *request = message.mutable_is_media_file_request(); - - const QByteArray filename_data = filename.toUtf8(); - request->set_filename(filename_data.constData(), filename_data.length()); - + message.mutable_is_media_file_request()->set_filename(filename.toStdString()); return worker_pool_->SendMessageWithReply(&message); } @@ -85,39 +81,33 @@ TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) { TagReaderReply *TagReaderClient::ReadFile(const QString &filename) { spb::tagreader::Message message; - spb::tagreader::ReadFileRequest *request = message.mutable_read_file_request(); - - const QByteArray filename_data = filename.toUtf8(); - request->set_filename(filename_data.constData(), filename_data.length()); - + message.mutable_read_file_request()->set_filename(filename.toStdString()); return worker_pool_->SendMessageWithReply(&message); } -TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) { +TagReaderReply *TagReaderClient::WriteFile(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) { spb::tagreader::Message message; - spb::tagreader::SaveFileRequest *request = message.mutable_save_file_request(); + spb::tagreader::WriteFileRequest *request = message.mutable_write_file_request(); - const QByteArray filename_data = filename.toUtf8(); - request->set_filename(filename_data.constData(), filename_data.length()); + 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.length() > 0) { - const QByteArray cover_filename = save_cover_options.cover_filename.toUtf8(); - request->set_cover_filename(cover_filename.constData(), cover_filename.length()); + if (!save_cover_options.cover_filename.isEmpty()) { + request->set_cover_filename(save_cover_options.cover_filename.toStdString()); } - if (save_cover_options.cover_data.length() > 0) { - request->set_cover_data(save_cover_options.cover_data.constData(), save_cover_options.cover_data.length()); + if (!save_cover_options.cover_data.isEmpty()) { + request->set_cover_data(save_cover_options.cover_data.toStdString()); } - if (save_cover_options.mime_type.length() > 0) { - const QByteArray cover_mime_type = save_cover_options.mime_type.toUtf8(); - request->set_cover_mime_type(cover_mime_type.constData(), cover_mime_type.length()); + 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); @@ -131,8 +121,7 @@ TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) { spb::tagreader::Message message; spb::tagreader::LoadEmbeddedArtRequest *request = message.mutable_load_embedded_art_request(); - const QByteArray filename_data = filename.toUtf8(); - request->set_filename(filename_data.constData(), filename_data.length()); + request->set_filename(filename.toStdString()); return worker_pool_->SendMessageWithReply(&message); @@ -143,63 +132,59 @@ TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const spb::tagreader::Message message; spb::tagreader::SaveEmbeddedArtRequest *request = message.mutable_save_embedded_art_request(); - const QByteArray filename_data = filename.toUtf8(); - request->set_filename(filename_data.constData(), filename_data.length()); - if (save_cover_options.cover_filename.length() > 0) { - const QByteArray cover_filename = save_cover_options.cover_filename.toUtf8(); - request->set_cover_filename(cover_filename.constData(), cover_filename.length()); + 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.length() > 0) { - request->set_cover_data(save_cover_options.cover_data.constData(), save_cover_options.cover_data.length()); + if (!save_cover_options.cover_data.isEmpty()) { + request->set_cover_data(save_cover_options.cover_data.toStdString()); } - if (save_cover_options.mime_type.length() > 0) { - const QByteArray cover_mime_type = save_cover_options.mime_type.toUtf8(); - request->set_cover_mime_type(cover_mime_type.constData(), cover_mime_type.length()); + 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::UpdateSongPlaycount(const Song &metadata) { +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(); - const QByteArray filename_data = metadata.url().toLocalFile().toUtf8(); - request->set_filename(filename_data.constData(), filename_data.length()); - metadata.ToProtobuf(request->mutable_metadata()); + request->set_filename(filename.toStdString()); + request->set_playcount(playcount); return worker_pool_->SendMessageWithReply(&message); } -void TagReaderClient::UpdateSongsPlaycount(const SongList &songs) { +void TagReaderClient::SaveSongsPlaycount(const SongList &songs) { for (const Song &song : songs) { - TagReaderReply *reply = UpdateSongPlaycount(song); + TagReaderReply *reply = SaveSongPlaycount(song.url().toLocalFile(), song.playcount()); QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater); } } -TagReaderReply *TagReaderClient::UpdateSongRating(const Song &metadata) { +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(); - const QByteArray filename_data = metadata.url().toLocalFile().toUtf8(); - request->set_filename(filename_data.constData(), filename_data.length()); - metadata.ToProtobuf(request->mutable_metadata()); + request->set_filename(filename.toStdString()); + request->set_rating(rating); return worker_pool_->SendMessageWithReply(&message); } -void TagReaderClient::UpdateSongsRating(const SongList &songs) { +void TagReaderClient::SaveSongsRating(const SongList &songs) { for (const Song &song : songs) { - TagReaderReply *reply = UpdateSongRating(song); + TagReaderReply *reply = SaveSongRating(song.url().toLocalFile(), song.rating()); QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater); } @@ -209,124 +194,180 @@ bool TagReaderClient::IsMediaFileBlocking(const QString &filename) { Q_ASSERT(QThread::currentThread() != thread()); - bool ret = false; + bool success = false; TagReaderReply *reply = IsMediaFile(filename); if (reply->WaitForFinished()) { - ret = reply->message().is_media_file_response().success(); + const spb::tagreader::IsMediaFileResponse &response = reply->message().is_media_file_response(); + if (response.has_success()) { + success = response.success(); + } } reply->deleteLater(); - return ret; + return success; } -void TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) { +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()) { - song->InitFromProtobuf(reply->message().read_file_response().metadata()); + 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; + } -bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) { +TagReaderClient::Result TagReaderClient::WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) { Q_ASSERT(QThread::currentThread() != thread()); - bool ret = false; + Result result(Result::ErrorCode::Failure); - TagReaderReply *reply = SaveFile(filename, metadata, save_types, save_cover_options); + TagReaderReply *reply = WriteFile(filename, metadata, save_types, save_cover_options); if (reply->WaitForFinished()) { - ret = reply->message().save_file_response().success(); + 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 ret; + return result; } -QByteArray TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) { +TagReaderClient::Result TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data) { Q_ASSERT(QThread::currentThread() != thread()); - QByteArray ret; + Result result(Result::ErrorCode::Failure); TagReaderReply *reply = LoadEmbeddedArt(filename); if (reply->WaitForFinished()) { - const std::string &data_str = reply->message().load_embedded_art_response().data(); - ret = QByteArray(data_str.data(), static_cast(data_str.size())); + 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(response.data().size())); + } + } + else { + result.error_code = Result::ErrorCode::Failure; + if (response.has_error()) { + result.error = QString::fromStdString(response.error()); + } + } + } } reply->deleteLater(); - return ret; + return result; } -QImage TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename) { +TagReaderClient::Result TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image) { Q_ASSERT(QThread::currentThread() != thread()); - QImage ret; - - TagReaderReply *reply = LoadEmbeddedArt(filename); - if (reply->WaitForFinished()) { - const std::string &data_str = reply->message().load_embedded_art_response().data(); - ret.loadFromData(QByteArray(data_str.data(), static_cast(data_str.size()))); + 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); } - reply->deleteLater(); - return ret; + return result; } -bool TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options) { +TagReaderClient::Result TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options) { Q_ASSERT(QThread::currentThread() != thread()); - bool success = false; + Result result(Result::ErrorCode::Failure); TagReaderReply *reply = SaveEmbeddedArt(filename, save_cover_options); if (reply->WaitForFinished()) { - success = reply->message().save_embedded_art_response().success(); + 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 success; + return result; } -bool TagReaderClient::UpdateSongPlaycountBlocking(const Song &metadata) { +TagReaderClient::Result TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) { Q_ASSERT(QThread::currentThread() != thread()); - bool success = false; + Result result(Result::ErrorCode::Failure); - TagReaderReply *reply = UpdateSongPlaycount(metadata); + TagReaderReply *reply = SaveSongPlaycount(filename, playcount); if (reply->WaitForFinished()) { - success = reply->message().save_song_playcount_to_file_response().success(); + 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 success; + return result; } -bool TagReaderClient::UpdateSongRatingBlocking(const Song &metadata) { +TagReaderClient::Result TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) { Q_ASSERT(QThread::currentThread() != thread()); - bool success = false; + Result result(Result::ErrorCode::Failure); - TagReaderReply *reply = UpdateSongRating(metadata); + TagReaderReply *reply = SaveSongRating(filename, rating); if (reply->WaitForFinished()) { - success = reply->message().save_song_rating_to_file_response().success(); + 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 success; + return result; } diff --git a/src/core/tagreaderclient.h b/src/core/tagreaderclient.h index 643849aeb..4ba9a6b7d 100644 --- a/src/core/tagreaderclient.h +++ b/src/core/tagreaderclient.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2011, David Sansome - * Copyright 2019-2023, Jonas Kvinge + * Copyright 2019-2024, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -70,24 +70,45 @@ class TagReaderClient : public QObject { 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 *SaveFile(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions()); + 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 *UpdateSongPlaycount(const Song &metadata); - ReplyType *UpdateSongRating(const Song &metadata); + 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. - void ReadFileBlocking(const QString &filename, Song *song); - bool SaveFileBlocking(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions()); + 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); - QByteArray LoadEmbeddedArtBlocking(const QString &filename); - QImage LoadEmbeddedArtAsImageBlocking(const QString &filename); - bool SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options); - bool UpdateSongPlaycountBlocking(const Song &metadata); - bool UpdateSongRatingBlocking(const Song &metadata); + 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; } @@ -100,8 +121,8 @@ class TagReaderClient : public QObject { void WorkerFailedToStart(); public slots: - void UpdateSongsPlaycount(const SongList &songs); - void UpdateSongsRating(const SongList &songs); + void SaveSongsPlaycount(const SongList &songs); + void SaveSongsRating(const SongList &songs); private: static TagReaderClient *sInstance; diff --git a/src/covermanager/albumcoverchoicecontroller.cpp b/src/covermanager/albumcoverchoicecontroller.cpp index dc6e8acc8..ca2a34e33 100644 --- a/src/covermanager/albumcoverchoicecontroller.cpp +++ b/src/covermanager/albumcoverchoicecontroller.cpp @@ -440,8 +440,9 @@ 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()) { - const QImage image_embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song.url().toLocalFile()); - if (!image_embedded_cover.isNull()) { + QImage image_embedded_cover; + const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song.url().toLocalFile(), image_embedded_cover); + if (result.success() && !image_embedded_cover.isNull()) { QPixmap pixmap = QPixmap::fromImage(image_embedded_cover); if (!pixmap.isNull()) { pixmap.setDevicePixelRatio(devicePixelRatioF()); diff --git a/src/covermanager/albumcoverloader.cpp b/src/covermanager/albumcoverloader.cpp index a7160af13..f4dd18331 100644 --- a/src/covermanager/albumcoverloader.cpp +++ b/src/covermanager/albumcoverloader.cpp @@ -290,8 +290,8 @@ 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()) { - task->album_cover.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile()); - if (!task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) { + const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(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); } } diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp index 632dc11e0..30b4e5f5f 100644 --- a/src/covermanager/albumcovermanager.cpp +++ b/src/covermanager/albumcovermanager.cpp @@ -717,7 +717,10 @@ void AlbumCoverManager::SaveCoverToFile() { return; case AlbumCoverLoaderOptions::Type::Embedded: if (song.art_embedded()) { - result.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile()); + const TagReaderClient::Result tagreaderclient_result = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile(), result.image_data); + if (!tagreaderclient_result.success()) { + qLog(Error) << "Could not load embedded art from" << song.url() << tagreaderclient_result.error; + } } break; case AlbumCoverLoaderOptions::Type::Automatic: diff --git a/src/covermanager/coverexportrunnable.cpp b/src/covermanager/coverexportrunnable.cpp index 637ee0a9e..983bc7fd3 100644 --- a/src/covermanager/coverexportrunnable.cpp +++ b/src/covermanager/coverexportrunnable.cpp @@ -76,8 +76,8 @@ void CoverExportRunnable::ProcessAndExportCover() { break; case AlbumCoverLoaderOptions::Type::Embedded: if (song_.art_embedded() && dialog_result_.export_embedded_) { - image = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile()); - if (!image.isNull()) { + const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile(), image); + if (result.success() && !image.isNull()) { extension = QLatin1String("jpg"); } } @@ -168,8 +168,8 @@ void CoverExportRunnable::ExportCover() { break; case AlbumCoverLoaderOptions::Type::Embedded: if (song_.art_embedded() && dialog_result_.export_embedded_) { - image = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile()); - if (!image.isNull()) { + const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile(), image); + if (result.success() && !image.isNull()) { embedded_cover = true; extension = QLatin1String("jpg"); } diff --git a/src/dialogs/edittagdialog.cpp b/src/dialogs/edittagdialog.cpp index f84f03a4d..1c0f8ad37 100644 --- a/src/dialogs/edittagdialog.cpp +++ b/src/dialogs/edittagdialog.cpp @@ -399,8 +399,8 @@ QList EditTagDialog::LoadData(const SongList &songs) { if (song.IsEditable()) { // Try reloading the tags from file Song copy(song); - TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), ©); - if (copy.is_valid()) { + const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), ©); + if (result.success() && copy.is_valid()) { copy.MergeUserSetData(song, false, false); ret << Data(copy); } @@ -1295,7 +1295,7 @@ void EditTagDialog::SaveData() { if (save_embedded_cover) { save_types |= TagReaderClient::SaveType::Cover; } - TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(ref.current_.url().toLocalFile(), ref.current_, save_types, savecover_options); + 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); } // If the cover was changed, but no tags written, make sure to update the collection. @@ -1450,7 +1450,11 @@ void EditTagDialog::UpdateLyrics(const quint64 id, const QString &provider, cons void EditTagDialog::SongSaveTagsComplete(TagReaderReply *reply, const QString &filename, Song song, const UpdateCoverAction cover_action) { --save_tag_pending_; - const bool success = reply->message().save_file_response().success(); + 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(); if (success) { @@ -1483,7 +1487,12 @@ void EditTagDialog::SongSaveTagsComplete(TagReaderReply *reply, const QString &f } } else { - emit Error(tr("An error occurred writing metadata to '%1'").arg(filename)); + if (error.isEmpty()) { + emit Error(tr("Could not write metadata to %1").arg(filename)); + } + else { + emit Error(tr("Could not write metadata to %1: %2").arg(filename, error)); + } } if (save_tag_pending_ <= 0) SaveDataFinished(); diff --git a/src/dialogs/trackselectiondialog.cpp b/src/dialogs/trackselectiondialog.cpp index 1038989be..39d0817b4 100644 --- a/src/dialogs/trackselectiondialog.cpp +++ b/src/dialogs/trackselectiondialog.cpp @@ -278,8 +278,9 @@ void TrackSelectionDialog::SaveData(const QList &data) { copy.set_track(new_metadata.track()); copy.set_year(new_metadata.year()); - if (!TagReaderClient::Instance()->SaveFileBlocking(copy.url().toLocalFile(), copy)) { - qLog(Warning) << "Failed to write new auto-tags to" << copy.url().toLocalFile(); + const TagReaderClient::Result result = TagReaderClient::Instance()->WriteFileBlocking(copy.url().toLocalFile(), copy, TagReaderClient::SaveType::Tags, TagReaderClient::SaveCoverOptions()); + if (!result.success()) { + qLog(Error) << "Failed to write new auto-tags to" << copy.url().toLocalFile() << result.error; } } diff --git a/src/organize/organize.cpp b/src/organize/organize.cpp index 26536e0e9..42b2b39d1 100644 --- a/src/organize/organize.cpp +++ b/src/organize/organize.cpp @@ -245,7 +245,10 @@ void Organize::ProcessSomeFiles() { } } else if (destination_->source() == Song::Source::Device) { - job.cover_image_ = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(task.song_info_.song_.url().toLocalFile()); + const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(task.song_info_.song_.url().toLocalFile(), job.cover_image_); + if (!result.success()) { + qLog(Error) << "Could not save embedded art to" << task.song_info_.song_.url() << result.error; + } } if (!job.cover_source_.isEmpty()) { diff --git a/src/organize/organizedialog.cpp b/src/organize/organizedialog.cpp index 0e331025e..91b42c065 100644 --- a/src/organize/organizedialog.cpp +++ b/src/organize/organizedialog.cpp @@ -415,8 +415,13 @@ SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) { continue; } - TagReaderClient::Instance()->ReadFileBlocking(filename, &song); - if (song.is_valid()) songs << song; + const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, &song); + if (result.success() && song.is_valid()) { + songs << song; + } + else { + qLog(Error) << "Could not read file" << filename << result.error; + } } return songs; diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 7c845560f..4cead16bd 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -415,7 +415,7 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int if (!set_column_value(song, static_cast(idx.column()), value)) return false; if (song.url().isLocalFile()) { - TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song); + TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(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); } @@ -431,11 +431,16 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata) { if (reply->is_successful() && idx.isValid()) { - if (reply->message().save_file_response().success()) { + if (reply->message().write_file_response().success()) { ItemReload(idx, old_metadata, true); } else { - emit Error(tr("An error occurred writing metadata to '%1'").arg(QString::fromStdString(reply->request_message().save_file_request().filename()))); + if (reply->request_message().write_file_response().has_error()) { + 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()))); + } + else { + emit Error(tr("Could not write metadata to %1").arg(QString::fromStdString(reply->request_message().write_file_request().filename()))); + } } } diff --git a/src/playlist/songplaylistitem.cpp b/src/playlist/songplaylistitem.cpp index 21fd2f6a8..b52e60b3d 100644 --- a/src/playlist/songplaylistitem.cpp +++ b/src/playlist/songplaylistitem.cpp @@ -42,7 +42,12 @@ QUrl SongPlaylistItem::Url() const { return song_.url(); } void SongPlaylistItem::Reload() { if (!song_.url().isLocalFile()) return; - TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_); + + const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_); + if (!result.success()) { + qLog(Error) << "Could not reload file" << song_.url() << result.error; + } + UpdateTemporaryMetadata(song_); } diff --git a/src/playlistparsers/parserbase.cpp b/src/playlistparsers/parserbase.cpp index f9501aee3..4c6c01afb 100644 --- a/src/playlistparsers/parserbase.cpp +++ b/src/playlistparsers/parserbase.cpp @@ -102,7 +102,10 @@ void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning } } - TagReaderClient::Instance()->ReadFileBlocking(filename, song); + const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song); + if (!result.success()) { + qLog(Error) << "Could not read file" << filename << result.error; + } } diff --git a/src/playlistparsers/playlistparser.cpp b/src/playlistparsers/playlistparser.cpp index ea640ae38..d7e0c288c 100644 --- a/src/playlistparsers/playlistparser.cpp +++ b/src/playlistparsers/playlistparser.cpp @@ -188,12 +188,15 @@ SongList PlaylistParser::LoadFromFile(const QString &filename) const { // Open the file QFile file(filename); - if (!file.open(QIODevice::ReadOnly)) return SongList(); + if (!file.open(QIODevice::ReadOnly)) { + emit Error(tr("Could not open file %1").arg(filename)); + return SongList(); + } - SongList ret = parser->Load(&file, filename, fileinfo.absolutePath()); + const SongList songs = parser->Load(&file, filename, fileinfo.absolutePath(), true); file.close(); - return ret; + return songs; } diff --git a/tests/src/tagreader_test.cpp b/tests/src/tagreader_test.cpp index 532984da3..59ab1c759 100644 --- a/tests/src/tagreader_test.cpp +++ b/tests/src/tagreader_test.cpp @@ -29,9 +29,9 @@ #include "core/song.h" -#if defined(USE_TAGLIB) +#if defined(HAVE_TAGLIB) # include "tagreadertaglib.h" -#elif defined(USE_TAGPARSER) +#elif defined(HAVE_TAGPARSER) # include "tagreadertagparser.h" #endif @@ -45,15 +45,15 @@ class TagReaderTest : public ::testing::Test { protected: static void SetUpTestCase() { // Return something from uninteresting mock functions. -#if defined(USE_TAGLIB) +#if defined(HAVE_TAGLIB) testing::DefaultValue::Set("foobarbaz"); #endif } static Song ReadSongFromFile(const QString& filename) { -#if defined(USE_TAGLIB) +#if defined(HAVE_TAGLIB) TagReaderTagLib tag_reader; -#elif defined(USE_TAGPARSER) +#elif defined(HAVE_TAGPARSER) TagReaderTagParser tag_reader; #endif Song song; @@ -67,17 +67,16 @@ class TagReaderTest : public ::testing::Test { } static void WriteSongToFile(const Song &song, const QString &filename) { -#if defined(USE_TAGLIB) +#if defined(HAVE_TAGLIB) TagReaderTagLib tag_reader; -#elif defined(USE_TAGPARSER) +#elif defined(HAVE_TAGPARSER) TagReaderTagParser tag_reader; #endif - ::spb::tagreader::SaveFileRequest request; - const QByteArray filename_data = filename.toUtf8(); - request.set_filename(filename_data.constData(), filename_data.length()); + ::spb::tagreader::WriteFileRequest request; + request.set_filename(filename.toStdString()); request.set_save_tags(true); song.ToProtobuf(request.mutable_metadata()); - tag_reader.SaveFile(request); + tag_reader.WriteFile(filename, request); } static QString SHA256SUM(const QString &filename) { @@ -98,25 +97,25 @@ class TagReaderTest : public ::testing::Test { } static void WriteSongPlaycountToFile(const Song &song, const QString &filename) { -#if defined(USE_TAGLIB) +#if defined(HAVE_TAGLIB) TagReaderTagLib tag_reader; -#elif defined(USE_TAGPARSER) +#elif defined(HAVE_TAGPARSER) TagReaderTagParser tag_reader; #endif spb::tagreader::SongMetadata pb_song; song.ToProtobuf(&pb_song); - tag_reader.SaveSongPlaycountToFile(filename, pb_song); + tag_reader.SaveSongPlaycountToFile(filename, pb_song.playcount()); } static void WriteSongRatingToFile(const Song &song, const QString &filename) { -#if defined(USE_TAGLIB) +#if defined(HAVE_TAGLIB) TagReaderTagLib tag_reader; -#elif defined(USE_TAGPARSER) +#elif defined(HAVE_TAGPARSER) TagReaderTagParser tag_reader; #endif spb::tagreader::SongMetadata pb_song; song.ToProtobuf(&pb_song); - tag_reader.SaveSongRatingToFile(filename, pb_song); + tag_reader.SaveSongRatingToFile(filename, pb_song.rating()); } }; @@ -1940,7 +1939,7 @@ TEST_F(TagReaderTest, TestMP4AudioFileCompilation) { } -#ifndef USE_TAGPARSER +#ifndef HAVE_TAGPARSER TEST_F(TagReaderTest, TestFLACAudioFilePlaycount) { @@ -2095,7 +2094,7 @@ TEST_F(TagReaderTest, TestMP4AudioFilePlaycount) { } -#endif // USE_TAGPARSER +#endif // HAVE_TAGPARSER TEST_F(TagReaderTest, TestFLACAudioFileRating) {