Remove external tagreader

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

View File

@@ -5,6 +5,7 @@ if(HAVE_TRANSLATIONS)
endif()
set(SOURCES
core/logging.cpp
core/mainwindow.cpp
core/application.cpp
core/player.cpp
@@ -31,7 +32,6 @@ set(SOURCES
core/songloader.cpp
core/stylehelper.cpp
core/stylesheetloader.cpp
core/tagreaderclient.cpp
core/taskmanager.cpp
core/thread.cpp
core/urlhandler.cpp
@@ -63,6 +63,27 @@ set(SOURCES
utilities/screenutils.cpp
utilities/textencodingutils.cpp
tagreader/tagreaderclient.cpp
tagreader/tagreaderresult.cpp
tagreader/tagreaderbase.cpp
tagreader/tagreadertaglib.cpp
tagreader/tagreadergme.cpp
tagreader/tagreaderrequest.cpp
tagreader/tagreaderismediafilerequest.cpp
tagreader/tagreaderreadfilerequest.cpp
tagreader/tagreaderwritefilerequest.cpp
tagreader/tagreaderloadcoverdatarequest.cpp
tagreader/tagreaderloadcoverimagerequest.cpp
tagreader/tagreadersavecoverrequest.cpp
tagreader/tagreadersaveplaycountrequest.cpp
tagreader/tagreadersaveratingrequest.cpp
tagreader/albumcovertagdata.cpp
tagreader/savetagcoverdata.cpp
tagreader/tagreaderreply.cpp
tagreader/tagreaderreadfilereply.cpp
tagreader/tagreaderloadcoverdatareply.cpp
tagreader/tagreaderloadcoverimagereply.cpp
filterparser/filterparser.cpp
filterparser/filtertree.cpp
@@ -319,6 +340,7 @@ set(SOURCES
)
set(HEADERS
core/logging.h
core/mainwindow.h
core/application.h
core/player.h
@@ -333,7 +355,6 @@ set(HEADERS
core/qtfslistener.h
core/settings.h
core/songloader.h
core/tagreaderclient.h
core/taskmanager.h
core/thread.h
core/urlhandler.h
@@ -344,6 +365,12 @@ set(HEADERS
core/stylesheetloader.h
core/localredirectserver.h
tagreader/tagreaderclient.h
tagreader/tagreaderreply.h
tagreader/tagreaderreadfilereply.h
tagreader/tagreaderloadcoverdatareply.h
tagreader/tagreaderloadcoverimagereply.h
engine/enginebase.h
engine/devicefinders.h
@@ -1059,8 +1086,8 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
${GLIB_INCLUDE_DIRS}
${GOBJECT_INCLUDE_DIRS}
${SQLITE_INCLUDE_DIRS}
${PROTOBUF_INCLUDE_DIRS}
${ICU_INCLUDE_DIRS}
${TAGLIB_INCLUDE_DIRS}
)
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
@@ -1072,9 +1099,6 @@ target_include_directories(strawberry_lib PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader
${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader
${SINGLEAPPLICATION_INCLUDE_DIRS}
)
@@ -1083,9 +1107,9 @@ target_link_directories(strawberry_lib PUBLIC
${GLIB_LIBRARY_DIRS}
${GOBJECT_LIBRARY_DIRS}
${SQLITE_LIBRARY_DIRS}
${PROTOBUF_LIBRARY_DIRS}
${SINGLEAPPLICATION_LIBRARY_DIRS}
${ICU_LIBRARY_DIRS}
${TAGLIB_LIBRARY_DIRS}
)
target_link_libraries(strawberry_lib PUBLIC
@@ -1094,16 +1118,14 @@ target_link_libraries(strawberry_lib PUBLIC
${GOBJECT_LIBRARIES}
${SQLITE_LIBRARIES}
${ICU_LIBRARIES}
${TAGLIB_LIBRARIES}
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Concurrent
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Sql
${Protobuf_LIBRARIES}
${SINGLEAPPLICATION_LIBRARIES}
libstrawberry-common
libstrawberry-tagreader
)
if(HAVE_DBUS)
@@ -1268,9 +1290,6 @@ endif()
target_link_libraries(strawberry PRIVATE strawberry_lib)
# macdeploy.py relies on the blob being built first.
add_dependencies(strawberry strawberry-tagreader)
if(NOT APPLE)
install(TARGETS strawberry RUNTIME DESTINATION bin)
endif()

View File

@@ -33,11 +33,11 @@
#include "core/application.h"
#include "core/taskmanager.h"
#include "core/database.h"
#include "core/tagreaderclient.h"
#include "core/thread.h"
#include "core/song.h"
#include "core/logging.h"
#include "core/settings.h"
#include "tagreader/tagreaderclient.h"
#include "utilities/threadutils.h"
#include "collection.h"
#include "collectionwatcher.h"
@@ -216,7 +216,7 @@ void SCollection::SyncPlaycountAndRatingToFiles() {
void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
if (save_tags || save_playcounts_to_files_) {
app_->tag_reader_client()->SaveSongsPlaycount(songs);
app_->tag_reader_client()->SaveSongsPlaycountAsync(songs);
}
}
@@ -224,7 +224,7 @@ void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_t
void SCollection::SongsRatingChanged(const SongList &songs, const bool save_tags) {
if (save_tags || save_ratings_to_files_) {
app_->tag_reader_client()->SaveSongsRating(songs);
app_->tag_reader_client()->SaveSongsRatingAsync(songs);
}
}

View File

@@ -26,7 +26,7 @@
#include "core/logging.h"
#include "collectionplaylistitem.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
class SqlRow;
@@ -42,9 +42,9 @@ QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
void CollectionPlaylistItem::Reload() {
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
if (!result.success()) {
qLog(Error) << "Could not reload file" << song_.url() << result.error;
qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
return;
}
UpdateTemporaryMetadata(song_);

View File

@@ -47,11 +47,11 @@
#include "core/filesystemwatcherinterface.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/taskmanager.h"
#include "core/settings.h"
#include "utilities/imageutils.h"
#include "utilities/timeconstants.h"
#include "tagreader/tagreaderclient.h"
#include "collectiondirectory.h"
#include "collectionbackend.h"
#include "collectionwatcher.h"
@@ -866,7 +866,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
}
Song song_on_disk(source_);
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
if (result.success() && song_on_disk.is_valid()) {
song_on_disk.set_source(source_);
song_on_disk.set_directory_id(t->dir());
@@ -919,7 +919,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
}
else { // It's a normal media file
Song song(source_);
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(file, &song);
if (result.success() && song.is_valid()) {
song.set_source(source_);
PerformEBUR128Analysis(song);

View File

@@ -37,10 +37,8 @@
#cmakedefine HAVE_KEYSYMDEF_H
#cmakedefine HAVE_XF86KEYSYM_H
#cmakedefine HAVE_TAGLIB
#cmakedefine HAVE_TAGLIB_DSFFILE
#cmakedefine HAVE_TAGLIB_DSDIFFFILE
#cmakedefine HAVE_TAGPARSER
#cmakedefine USE_BUNDLE

View File

@@ -36,12 +36,11 @@
#include "shared_ptr.h"
#include "lazy.h"
#include "tagreaderclient.h"
#include "database.h"
#include "taskmanager.h"
#include "player.h"
#include "networkaccessmanager.h"
#include "tagreader/tagreaderclient.h"
#include "engine/devicefinders.h"
#ifndef Q_OS_WIN
# include "device/devicemanager.h"
@@ -118,7 +117,6 @@ class ApplicationImpl {
tag_reader_client_([app](){
TagReaderClient *client = new TagReaderClient();
app->MoveToNewThread(client);
client->Start();
return client;
}),
database_([app]() {

439
src/core/logging.cpp Normal file
View File

@@ -0,0 +1,439 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <QtGlobal>
#include "config.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <utility>
#include <memory>
#include <chrono>
#ifndef _MSC_VER
# include <cxxabi.h>
#endif
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wold-style-cast"
#endif
#include <glib.h>
#ifdef __clang__
# pragma clang diagnostic pop
#endif
#ifdef HAVE_BACKTRACE
# include <execinfo.h>
#endif
#include <QByteArray>
#include <QList>
#include <QMap>
#include <QString>
#include <QStringList>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QDateTime>
#include <QIODevice>
#include <QBuffer>
#include <QtMessageHandler>
#include <QMessageLogContext>
#include <QDebug>
#include "logging.h"
using namespace Qt::StringLiterals;
namespace logging {
static Level sDefaultLevel = Level_Debug;
static QMap<QString, Level> *sClassLevels = nullptr;
static QIODevice *sNullDevice = nullptr;
const char *kDefaultLogLevels = "*:3";
static constexpr char kMessageHandlerMagic[] = "__logging_message__";
static const size_t kMessageHandlerMagicLen = strlen(kMessageHandlerMagic);
static QtMessageHandler sOriginalMessageHandler = nullptr;
template<class T>
static T CreateLogger(Level level, const QString &class_name, int line, const char *category);
void GLog(const char *domain, int level, const char *message, void*) {
switch (level) {
case G_LOG_FLAG_RECURSION:
case G_LOG_FLAG_FATAL:
case G_LOG_LEVEL_ERROR:
case G_LOG_LEVEL_CRITICAL:
qLogCat(Error, domain) << message;
break;
case G_LOG_LEVEL_WARNING:
qLogCat(Warning, domain) << message;
break;
case G_LOG_LEVEL_MESSAGE:
case G_LOG_LEVEL_INFO:
qLogCat(Info, domain) << message;
break;
case G_LOG_LEVEL_DEBUG:
default:
qLogCat(Debug, domain) << message;
break;
}
}
template<class T>
class DebugBase : public QDebug {
public:
DebugBase() : QDebug(sNullDevice) {}
explicit DebugBase(QtMsgType t) : QDebug(t) {}
T &space() { return static_cast<T&>(QDebug::space()); }
T &nospace() { return static_cast<T&>(QDebug::nospace()); }
};
// Debug message will be stored in a buffer.
class BufferedDebug : public DebugBase<BufferedDebug> {
public:
BufferedDebug() = default;
explicit BufferedDebug(QtMsgType msg_type) : buf_(new QBuffer, later_deleter) {
Q_UNUSED(msg_type)
buf_->open(QIODevice::WriteOnly);
// QDebug doesn't have a method to set a new io device, but swap() allows the devices to be swapped between two instances.
QDebug other(buf_.get());
swap(other);
}
// Delete function for the buffer. Since a base class is holding a reference to the raw pointer,
// it shouldn't be deleted until after the deletion of this object is complete.
static void later_deleter(QBuffer *b) { b->deleteLater(); }
std::shared_ptr<QBuffer> buf_;
};
// Debug message will be logged immediately.
class LoggedDebug : public DebugBase<LoggedDebug> {
public:
LoggedDebug() = default;
explicit LoggedDebug(QtMsgType t) : DebugBase(t) { nospace() << kMessageHandlerMagic; }
};
static void MessageHandler(QtMsgType type, const QMessageLogContext &message_log_context, const QString &message) {
Q_UNUSED(message_log_context)
if (message.startsWith(QLatin1String(kMessageHandlerMagic))) {
QByteArray message_data = message.toUtf8();
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLen);
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
return;
}
Level level = Level_Debug;
switch (type) {
case QtFatalMsg:
case QtCriticalMsg:
level = Level_Error;
break;
case QtWarningMsg:
level = Level_Warning;
break;
case QtDebugMsg:
default:
level = Level_Debug;
break;
}
const QStringList lines = message.split(u'\n');
for (const QString &line : lines) {
BufferedDebug d = CreateLogger<BufferedDebug>(level, QStringLiteral("unknown"), -1, nullptr);
d << line.toLocal8Bit().constData();
if (d.buf_) {
d.buf_->close();
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().constData());
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
}
}
if (type == QtFatalMsg) {
abort();
}
}
void Init() {
delete sClassLevels;
delete sNullDevice;
sClassLevels = new QMap<QString, Level>();
sNullDevice = new NullDevice;
sNullDevice->open(QIODevice::ReadWrite);
// Catch other messages from Qt
if (!sOriginalMessageHandler) {
sOriginalMessageHandler = qInstallMessageHandler(MessageHandler);
}
}
void SetLevels(const QString &levels) {
if (!sClassLevels) return;
const QStringList items = levels.split(u',');
for (const QString &item : items) {
const QStringList class_level = item.split(u':');
QString class_name;
bool ok = false;
int level = Level_Error;
if (class_level.count() == 1) {
level = class_level.last().toInt(&ok);
}
else if (class_level.count() == 2) {
class_name = class_level.first();
level = class_level.last().toInt(&ok);
}
if (!ok || level < Level_Error || level > Level_Debug) {
continue;
}
if (class_name.isEmpty() || class_name == u'*') {
sDefaultLevel = static_cast<Level>(level);
}
else {
sClassLevels->insert(class_name, static_cast<Level>(level));
}
}
}
static QString ParsePrettyFunction(const char *pretty_function) {
// Get the class name out of the function name.
QString class_name = QLatin1String(pretty_function);
const qint64 paren = class_name.indexOf(u'(');
if (paren != -1) {
const qint64 colons = class_name.lastIndexOf("::"_L1, paren);
if (colons != -1) {
class_name = class_name.left(colons);
}
else {
class_name = class_name.left(paren);
}
}
const qint64 space = class_name.lastIndexOf(u' ');
if (space != -1) {
class_name = class_name.mid(space + 1);
}
return class_name;
}
template <class T>
static T CreateLogger(Level level, const QString &class_name, int line, const char *category) {
// Map the level to a string
const char *level_name = nullptr;
switch (level) {
case Level_Debug: level_name = " DEBUG "; break;
case Level_Info: level_name = " INFO "; break;
case Level_Warning: level_name = " WARN "; break;
case Level_Error: level_name = " ERROR "; break;
case Level_Fatal: level_name = " FATAL "; break;
}
QString filter_category = (category != nullptr) ? QLatin1String(category) : class_name;
// Check the settings to see if we're meant to show or hide this message.
Level threshold_level = sDefaultLevel;
if (sClassLevels && sClassLevels->contains(filter_category)) {
threshold_level = sClassLevels->value(filter_category);
}
if (level > threshold_level) {
return T();
}
QString function_line = class_name;
if (line != -1) {
function_line += QLatin1Char(':') + QString::number(line);
}
if (category) {
function_line += QLatin1Char('(') + QLatin1String(category) + QLatin1Char(')');
}
QtMsgType type = QtDebugMsg;
if (level == Level_Fatal) {
type = QtFatalMsg;
}
T ret(type);
ret.nospace() << QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss.zzz")).toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
return ret.space();
}
#ifdef Q_OS_UNIX
QString CXXDemangle(const QString &mangled_function);
QString CXXDemangle(const QString &mangled_function) {
int status = 0;
char *demangled_function = abi::__cxa_demangle(mangled_function.toLatin1().constData(), nullptr, nullptr, &status);
if (status == 0) {
QString ret = QString::fromLatin1(demangled_function);
free(demangled_function);
return ret;
}
return mangled_function; // Probably not a C++ function.
}
#endif // Q_OS_UNIX
#ifdef Q_OS_LINUX
QString LinuxDemangle(const QString &symbol);
QString LinuxDemangle(const QString &symbol) {
static const QRegularExpression regex_symbol(QStringLiteral("\\(([^+]+)"));
QRegularExpressionMatch match = regex_symbol.match(symbol);
if (!match.hasMatch()) {
return symbol;
}
QString mangled_function = match.captured(1);
return CXXDemangle(mangled_function);
}
#endif // Q_OS_LINUX
#ifdef Q_OS_MACOS
QString DarwinDemangle(const QString &symbol);
QString DarwinDemangle(const QString &symbol) {
const QStringList split = symbol.split(QLatin1Char(' '), Qt::SkipEmptyParts);
QString mangled_function = split[3];
return CXXDemangle(mangled_function);
}
#endif // Q_OS_MACOS
QString DemangleSymbol(const QString &symbol);
QString DemangleSymbol(const QString &symbol) {
#ifdef Q_OS_MACOS
return DarwinDemangle(symbol);
#elif defined(Q_OS_LINUX)
return LinuxDemangle(symbol);
#else
return symbol;
#endif
}
void DumpStackTrace() {
#ifdef HAVE_BACKTRACE
void *callstack[128];
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
char **symbols = backtrace_symbols(reinterpret_cast<void**>(&callstack), callstack_size);
// Start from 1 to skip ourself.
for (int i = 1; i < callstack_size; ++i) {
std::cerr << DemangleSymbol(QString::fromLatin1(symbols[i])).toStdString() << std::endl;
}
free(symbols);
#else
qLog(Debug) << "FIXME: Implement printing stack traces on this platform";
#endif
}
// These are the functions that create loggers for the rest of Strawberry.
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It doesn't override any behavior that should be needed after return.
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
QDebug CreateLoggerFatal(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Fatal); }
QDebug CreateLoggerError(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Error); }
#ifdef QT_NO_INFO_OUTPUT
QNoDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category) {
Q_UNUSED(line)
Q_UNUSED(pretty_function)
Q_UNUSED(category)
return QNoDebug();
}
#else
QDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
#endif // QT_NO_INFO_OUTPUT
#ifdef QT_NO_WARNING_OUTPUT
QNoDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category) {
Q_UNUSED(line)
Q_UNUSED(pretty_function)
Q_UNUSED(category)
return QNoDebug();
}
#else
QDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
#endif // QT_NO_WARNING_OUTPUT
#ifdef QT_NO_DEBUG_OUTPUT
QNoDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category) {
Q_UNUSED(line)
Q_UNUSED(pretty_function)
Q_UNUSED(category)
return QNoDebug();
}
#else
QDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
#endif // QT_NO_DEBUG_OUTPUT
} // namespace logging
namespace {
template<typename T>
QString print_duration(T duration, const std::string &unit) {
return QStringLiteral("%1%2").arg(duration.count()).arg(QString::fromStdString(unit));
}
} // namespace
QDebug operator<<(QDebug dbg, std::chrono::seconds secs) {
dbg.nospace() << print_duration(secs, "s");
return dbg.space();
}

104
src/core/logging.h Normal file
View File

@@ -0,0 +1,104 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef LOGGING_H
#define LOGGING_H
#include <chrono>
#include <QtGlobal>
#include <QIODevice>
#include <QString>
#include <QDebug>
#ifdef QT_NO_DEBUG_STREAM
# define qLog(level) while (false) QNoDebug()
# define qLogCat(level, category) while (false) QNoDebug()
#else
# ifdef _MSC_VER
# define qLog(level) logging::CreateLogger##level(__LINE__, __FUNCSIG__, nullptr)
# else
# define qLog(level) logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__, nullptr)
# endif // _MSC_VER
// This macro specifies a separate category for message filtering.
// The default qLog will use the class name extracted from the function name for this purpose.
// The category is also printed in the message along with the class name.
# ifdef _MSC_VER
# define qLogCat(level, category) logging::CreateLogger##level(__LINE__, __FUNCSIG__, category)
# else
# define qLogCat(level, category) logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__, category)
# endif // _MSC_VER
#endif // QT_NO_DEBUG_STREAM
namespace logging {
class NullDevice : public QIODevice {
Q_OBJECT
public:
NullDevice(QObject *parent = nullptr) : QIODevice(parent) {}
protected:
qint64 readData(char*, qint64) override { return -1; }
qint64 writeData(const char*, qint64 len) override { return len; }
};
enum Level {
Level_Fatal = -1,
Level_Error = 0,
Level_Warning,
Level_Info,
Level_Debug,
};
void Init();
void SetLevels(const QString &levels);
void DumpStackTrace();
QDebug CreateLoggerFatal(const int line, const char *pretty_function, const char *category);
QDebug CreateLoggerError(const int line, const char *pretty_function, const char *category);
#ifdef QT_NO_INFO_OUTPUT
QNoDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category);
#else
QDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category);
#endif // QT_NO_INFO_OUTPUT
#ifdef QT_NO_WARNING_OUTPUT
QNoDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category);
#else
QDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category);
#endif // QT_NO_WARNING_OUTPUT
#ifdef QT_NO_DEBUG_OUTPUT
QNoDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category);
#else
QDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category);
#endif // QT_NO_DEBUG_OUTPUT
void GLog(const char *domain, int level, const char *message, void *user_data);
extern const char *kDefaultLogLevels;
} // namespace logging
QDebug operator<<(QDebug dbg, std::chrono::seconds secs);
#endif // LOGGING_H

View File

@@ -2177,21 +2177,20 @@ void MainWindow::RenumberTracks() {
Song song = item->OriginalMetadata();
if (song.IsEditable()) {
song.set_track(track);
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
}
++track;
}
}
void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx) {
void MainWindow::SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx) {
if (reply->is_successful() && idx.isValid()) {
if (reply->success() && idx.isValid()) {
app_->playlist_manager()->current()->ReloadItems(QList<int>() << idx.row());
}
reply->deleteLater();
}
@@ -2209,9 +2208,9 @@ void MainWindow::SelectionSetValue() {
Song song = item->OriginalMetadata();
if (!song.is_valid()) continue;
if (song.url().isLocalFile() && Playlist::set_column_value(song, column, column_value)) {
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
}
else if (song.source() == Song::Source::Stream) {
app_->playlist_manager()->current()->setData(source_index, column_value, 0);

View File

@@ -51,8 +51,8 @@
#include "lazy.h"
#include "platforminterface.h"
#include "song.h"
#include "tagreaderclient.h"
#include "settings.h"
#include "tagreader/tagreaderclient.h"
#include "engine/enginebase.h"
#include "osd/osdbase.h"
#include "playlist/playlist.h"
@@ -218,7 +218,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void PlayingWidgetPositionChanged(const bool above_status_bar);
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx);
void SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx);
void ShowCoverManager();
void ShowEqualizer();

View File

@@ -48,6 +48,8 @@
#include <QStandardPaths>
#include <QSqlRecord>
#include <taglib/tstring.h>
#include "core/iconloader.h"
#include "engine/enginemetadata.h"
#include "utilities/strutils.h"
@@ -55,13 +57,13 @@
#include "utilities/coverutils.h"
#include "utilities/timeconstants.h"
#include "utilities/sqlhelper.h"
#include "song.h"
#include "sqlquery.h"
#include "sqlrow.h"
#ifdef HAVE_DBUS
# include "mpris_common.h"
#endif
#include "tagreadermessages.pb.h"
using namespace Qt::StringLiterals;
@@ -482,6 +484,29 @@ const QString &Song::musicbrainz_work_id() const { return d->musicbrainz_work_id
std::optional<double> Song::ebur128_integrated_loudness_lufs() const { return d->ebur128_integrated_loudness_lufs_; }
std::optional<double> Song::ebur128_loudness_range_lu() const { return d->ebur128_loudness_range_lu_; }
QString *Song::mutable_title() { return &d->title_; }
QString *Song::mutable_album() { return &d->album_; }
QString *Song::mutable_artist() { return &d->artist_; }
QString *Song::mutable_albumartist() { return &d->albumartist_; }
QString *Song::mutable_genre() { return &d->genre_; }
QString *Song::mutable_composer() { return &d->composer_; }
QString *Song::mutable_performer() { return &d->performer_; }
QString *Song::mutable_grouping() { return &d->grouping_; }
QString *Song::mutable_comment() { return &d->comment_; }
QString *Song::mutable_lyrics() { return &d->lyrics_; }
QString *Song::mutable_acoustid_id() { return &d->acoustid_id_; }
QString *Song::mutable_acoustid_fingerprint() { return &d->acoustid_fingerprint_; }
QString *Song::mutable_musicbrainz_album_artist_id() { return &d->musicbrainz_album_artist_id_; }
QString *Song::mutable_musicbrainz_artist_id() { return &d->musicbrainz_artist_id_; }
QString *Song::mutable_musicbrainz_original_artist_id() { return &d->musicbrainz_original_artist_id_; }
QString *Song::mutable_musicbrainz_album_id() { return &d->musicbrainz_album_id_; }
QString *Song::mutable_musicbrainz_original_album_id() { return &d->musicbrainz_original_album_id_; }
QString *Song::mutable_musicbrainz_recording_id() { return &d->musicbrainz_recording_id_; }
QString *Song::mutable_musicbrainz_track_id() { return &d->musicbrainz_track_id_; }
QString *Song::mutable_musicbrainz_disc_id() { return &d->musicbrainz_disc_id_; }
QString *Song::mutable_musicbrainz_release_group_id() { return &d->musicbrainz_release_group_id_; }
QString *Song::mutable_musicbrainz_work_id() { return &d->musicbrainz_work_id_; }
bool Song::init_from_file() const { return d->init_from_file_; }
const QString &Song::title_sortable() const { return d->title_sortable_; }
@@ -503,7 +528,7 @@ void Song::set_disc(const int v) { d->disc_ = v; }
void Song::set_year(const int v) { d->year_ = v; }
void Song::set_originalyear(const int v) { d->originalyear_ = v; }
void Song::set_genre(const QString &v) { d->genre_ = v; }
void Song::set_compilation(bool v) { d->compilation_ = v; }
void Song::set_compilation(const bool v) { d->compilation_ = v; }
void Song::set_composer(const QString &v) { d->composer_ = v; }
void Song::set_performer(const QString &v) { d->performer_ = v; }
void Song::set_grouping(const QString &v) { d->grouping_ = v; }
@@ -571,6 +596,59 @@ void Song::set_ebur128_loudness_range_lu(const std::optional<double> v) { d->ebu
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
void Song::set_title(const TagLib::String &v) {
const QString title = TagLibStringToQString(v);
d->title_sortable_ = sortable(title);
d->title_ = title;
}
void Song::set_album(const TagLib::String &v) {
const QString album = TagLibStringToQString(v);
d->album_sortable_ = sortable(album);
d->album_ = album;
}
void Song::set_artist(const TagLib::String &v) {
const QString artist = TagLibStringToQString(v);
d->artist_sortable_ = sortable(artist);
d->artist_ = artist;
}
void Song::set_albumartist(const TagLib::String &v) {
const QString albumartist = TagLibStringToQString(v);
d->albumartist_sortable_ = sortable(albumartist);
d->albumartist_ = albumartist;
}
void Song::set_genre(const TagLib::String &v) { d->genre_ = TagLibStringToQString(v); }
void Song::set_composer(const TagLib::String &v) { d->composer_ = TagLibStringToQString(v); }
void Song::set_performer(const TagLib::String &v) { d->performer_ = TagLibStringToQString(v); }
void Song::set_grouping(const TagLib::String &v) { d->grouping_ = TagLibStringToQString(v); }
void Song::set_comment(const TagLib::String &v) { d->comment_ = TagLibStringToQString(v); }
void Song::set_lyrics(const TagLib::String &v) { d->lyrics_ = TagLibStringToQString(v); }
void Song::set_artist_id(const TagLib::String &v) { d->artist_id_ = TagLibStringToQString(v); }
void Song::set_album_id(const TagLib::String &v) { d->album_id_ = TagLibStringToQString(v); }
void Song::set_song_id(const TagLib::String &v) { d->song_id_ = TagLibStringToQString(v); }
void Song::set_acoustid_id(const TagLib::String &v) { d->acoustid_id_ = TagLibStringToQString(v); }
void Song::set_acoustid_fingerprint(const TagLib::String &v) { d->acoustid_fingerprint_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_album_artist_id(const TagLib::String &v) { d->musicbrainz_album_artist_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_artist_id(const TagLib::String &v) { d->musicbrainz_artist_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_original_artist_id(const TagLib::String &v) { d->musicbrainz_original_artist_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_album_id(const TagLib::String &v) { d->musicbrainz_album_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_original_album_id(const TagLib::String &v) { d->musicbrainz_original_album_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_recording_id(const TagLib::String &v) { d->musicbrainz_recording_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_track_id(const TagLib::String &v) { d->musicbrainz_track_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_disc_id(const TagLib::String &v) { d->musicbrainz_disc_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_release_group_id(const TagLib::String &v) { d->musicbrainz_release_group_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_work_id(const TagLib::String &v) { d->musicbrainz_work_id_ = TagLibStringToQString(v); }
const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
@@ -1312,125 +1390,6 @@ void Song::Init(const QString &title, const QString &artist, const QString &albu
}
void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
if (d->source_ == Source::Unknown) d->source_ = Source::LocalFile;
d->init_from_file_ = true;
d->valid_ = pb.valid();
set_title(QString::fromStdString(pb.title()));
set_album(QString::fromStdString(pb.album()));
set_artist(QString::fromStdString(pb.artist()));
set_albumartist(QString::fromStdString(pb.albumartist()));
d->track_ = pb.track();
d->disc_ = pb.disc();
d->year_ = pb.year();
d->originalyear_ = pb.originalyear();
d->genre_ = QString::fromStdString(pb.genre());
d->compilation_ = pb.compilation();
d->composer_ = QString::fromStdString(pb.composer());
d->performer_ = QString::fromStdString(pb.performer());
d->grouping_ = QString::fromStdString(pb.grouping());
d->comment_ = QString::fromStdString(pb.comment());
d->lyrics_ = QString::fromStdString(pb.lyrics());
set_length_nanosec(static_cast<qint64>(pb.length_nanosec()));
d->bitrate_ = pb.bitrate();
d->samplerate_ = pb.samplerate();
d->bitdepth_ = pb.bitdepth();
set_url(QUrl::fromEncoded(QString::fromStdString(pb.url()).toUtf8()));
d->basefilename_ = QString::fromStdString(pb.basefilename());
d->filetype_ = static_cast<FileType>(pb.filetype());
d->filesize_ = pb.filesize();
d->mtime_ = pb.mtime();
d->ctime_ = pb.ctime();
d->skipcount_ = pb.skipcount();
d->lastplayed_ = pb.lastplayed();
d->lastseen_ = pb.lastseen();
if (pb.has_playcount()) {
d->playcount_ = pb.playcount();
}
if (pb.has_rating()) {
d->rating_ = pb.rating();
}
d->art_embedded_ = pb.has_art_embedded();
d->acoustid_id_ = QString::fromStdString(pb.acoustid_id());
d->acoustid_fingerprint_ = QString::fromStdString(pb.acoustid_fingerprint());
d->musicbrainz_album_artist_id_ = QString::fromStdString(pb.musicbrainz_album_artist_id());
d->musicbrainz_artist_id_ = QString::fromStdString(pb.musicbrainz_artist_id().data());
d->musicbrainz_original_artist_id_ = QString::fromStdString(pb.musicbrainz_original_artist_id());
d->musicbrainz_album_id_ = QString::fromStdString(pb.musicbrainz_album_id());
d->musicbrainz_original_album_id_ = QString::fromStdString(pb.musicbrainz_original_album_id());
d->musicbrainz_recording_id_ = QString::fromStdString(pb.musicbrainz_recording_id());
d->musicbrainz_track_id_ = QString::fromStdString(pb.musicbrainz_track_id());
d->musicbrainz_disc_id_ = QString::fromStdString(pb.musicbrainz_disc_id());
d->musicbrainz_release_group_id_ = QString::fromStdString(pb.musicbrainz_release_group_id());
d->musicbrainz_work_id_ = QString::fromStdString(pb.musicbrainz_work_id());
d->suspicious_tags_ = pb.suspicious_tags();
InitArtManual();
}
void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
const QByteArray url(d->url_.toEncoded());
pb->set_valid(d->valid_);
pb->set_title(d->title_.toStdString());
pb->set_album(d->album_.toStdString());
pb->set_artist(d->artist_.toStdString());
pb->set_albumartist(d->albumartist_.toStdString());
pb->set_track(d->track_);
pb->set_disc(d->disc_);
pb->set_year(d->year_);
pb->set_originalyear(d->originalyear_);
pb->set_genre(d->genre_.toStdString());
pb->set_compilation(d->compilation_);
pb->set_composer(d->composer_.toStdString());
pb->set_performer(d->performer_.toStdString());
pb->set_grouping(d->grouping_.toStdString());
pb->set_comment(d->comment_.toStdString());
pb->set_lyrics(d->lyrics_.toStdString());
pb->set_length_nanosec(length_nanosec());
pb->set_bitrate(d->bitrate_);
pb->set_samplerate(d->samplerate_);
pb->set_bitdepth(d->bitdepth_);
pb->set_url(url.constData(), url.size());
pb->set_basefilename(d->basefilename_.toStdString());
pb->set_filetype(static_cast<spb::tagreader::SongMetadata_FileType>(d->filetype_));
pb->set_filesize(d->filesize_);
pb->set_mtime(d->mtime_);
pb->set_ctime(d->ctime_);
pb->set_playcount(d->playcount_);
pb->set_skipcount(d->skipcount_);
pb->set_lastplayed(d->lastplayed_);
pb->set_lastseen(d->lastseen_);
pb->set_art_embedded(d->art_embedded_);
pb->set_rating(d->rating_);
pb->set_acoustid_id(d->acoustid_id_.toStdString());
pb->set_acoustid_fingerprint(d->acoustid_fingerprint_.toStdString());
pb->set_musicbrainz_album_artist_id(d->musicbrainz_album_artist_id_.toStdString());
pb->set_musicbrainz_artist_id(d->musicbrainz_artist_id_.toStdString());
pb->set_musicbrainz_original_artist_id(d->musicbrainz_original_artist_id_.toStdString());
pb->set_musicbrainz_album_id(d->musicbrainz_album_id_.toStdString());
pb->set_musicbrainz_original_album_id(d->musicbrainz_original_album_id_.toStdString());
pb->set_musicbrainz_recording_id(d->musicbrainz_recording_id_.toStdString());
pb->set_musicbrainz_track_id(d->musicbrainz_track_id_.toStdString());
pb->set_musicbrainz_disc_id(d->musicbrainz_disc_id_.toStdString());
pb->set_musicbrainz_release_group_id(d->musicbrainz_release_group_id_.toStdString());
pb->set_musicbrainz_work_id(d->musicbrainz_work_id_.toStdString());
pb->set_suspicious_tags(d->suspicious_tags_);
}
void Song::InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col) {
Q_ASSERT(kRowIdColumns.count() + col <= r.count());

View File

@@ -41,17 +41,15 @@
#include <QFileInfo>
#include <QIcon>
#include <taglib/tstring.h>
#undef TStringToQString
#undef QStringToTString
class SqlQuery;
class QSqlRecord;
class EngineMetadata;
namespace spb {
namespace tagreader {
class SongMetadata;
} // namespace tagreader
} // namespace spb
#ifdef HAVE_LIBGPOD
struct _Itdb_Track;
#endif
@@ -81,9 +79,6 @@ class Song {
Spotify = 11
};
// Don't change these values - they're stored in the database, and defined in the tag reader protobuf.
// If a new lossless file is added, also add it to IsFileLossless().
enum class FileType {
Unknown = 0,
WAV = 1,
@@ -227,6 +222,29 @@ class Song {
std::optional<double> ebur128_integrated_loudness_lufs() const;
std::optional<double> ebur128_loudness_range_lu() const;
QString *mutable_title();
QString *mutable_album();
QString *mutable_artist();
QString *mutable_albumartist();
QString *mutable_genre();
QString *mutable_composer();
QString *mutable_performer();
QString *mutable_grouping();
QString *mutable_comment();
QString *mutable_lyrics();
QString *mutable_acoustid_id();
QString *mutable_acoustid_fingerprint();
QString *mutable_musicbrainz_album_artist_id();
QString *mutable_musicbrainz_artist_id();
QString *mutable_musicbrainz_original_artist_id();
QString *mutable_musicbrainz_album_id();
QString *mutable_musicbrainz_original_album_id();
QString *mutable_musicbrainz_recording_id();
QString *mutable_musicbrainz_track_id();
QString *mutable_musicbrainz_disc_id();
QString *mutable_musicbrainz_release_group_id();
QString *mutable_musicbrainz_work_id();
bool init_from_file() const;
const QString &title_sortable() const;
@@ -317,6 +335,32 @@ class Song {
void set_stream_url(const QUrl &v);
void set_title(const TagLib::String &v);
void set_album(const TagLib::String &v);
void set_artist(const TagLib::String &v);
void set_albumartist(const TagLib::String &v);
void set_genre(const TagLib::String &v);
void set_composer(const TagLib::String &v);
void set_performer(const TagLib::String &v);
void set_grouping(const TagLib::String &v);
void set_comment(const TagLib::String &v);
void set_lyrics(const TagLib::String &v);
void set_artist_id(const TagLib::String &v);
void set_album_id(const TagLib::String &v);
void set_song_id(const TagLib::String &v);
void set_acoustid_id(const TagLib::String &v);
void set_acoustid_fingerprint(const TagLib::String &v);
void set_musicbrainz_album_artist_id(const TagLib::String &v);
void set_musicbrainz_artist_id(const TagLib::String &v);
void set_musicbrainz_original_artist_id(const TagLib::String &v);
void set_musicbrainz_album_id(const TagLib::String &v);
void set_musicbrainz_original_album_id(const TagLib::String &v);
void set_musicbrainz_recording_id(const TagLib::String &v);
void set_musicbrainz_track_id(const TagLib::String &v);
void set_musicbrainz_disc_id(const TagLib::String &v);
void set_musicbrainz_release_group_id(const TagLib::String &v);
void set_musicbrainz_work_id(const TagLib::String &v);
const QUrl &effective_stream_url() const;
const QString &effective_albumartist() const;
const QString &effective_albumartist_sortable() const;
@@ -422,7 +466,6 @@ class Song {
// Constructors
void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec);
void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end);
void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
void InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col = 0);
void InitFromQuery(const SqlQuery &query, const bool reliable_metadata, const int col = 0);
void InitFromQuery(const SqlRow &row, const bool reliable_metadata, const int col = 0);
@@ -445,7 +488,6 @@ class Song {
#ifdef HAVE_DBUS
void ToXesam(QVariantMap *map) const;
#endif
void ToProtobuf(spb::tagreader::SongMetadata *pb) const;
bool MergeFromEngineMetadata(const EngineMetadata &engine_metadata);
@@ -465,6 +507,10 @@ class Song {
static QString AlbumRemoveDiscMisc(const QString &album);
static QString TitleRemoveMisc(const QString &title);
static inline QString TagLibStringToQString(const TagLib::String &s) {
return QString::fromUtf8((s).toCString(true));
}
private:
struct Private;

View File

@@ -48,10 +48,10 @@
#include "player.h"
#include "song.h"
#include "songloader.h"
#include "tagreaderclient.h"
#include "database.h"
#include "sqlrow.h"
#include "engine/enginebase.h"
#include "tagreader/tagreaderclient.h"
#include "collection/collectionbackend.h"
#include "collection/collectionquery.h"
#include "playlistparsers/cueparser.h"
@@ -362,9 +362,9 @@ void SongLoader::EffectiveSongLoad(Song *song) {
else {
// It's a normal media file
const QString filename = song->url().toLocalFile();
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
if (!result.success()) {
qLog(Error) << "Could not read file" << song->url() << result.error;
qLog(Error) << "Could not read file" << song->url() << result.error_string();
}
}

View File

@@ -1,377 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <string>
#include <QtGlobal>
#include <QObject>
#include <QThread>
#include <QByteArray>
#include <QString>
#include <QImage>
#include "core/logging.h"
#include "core/workerpool.h"
#include "song.h"
#include "tagreaderclient.h"
using namespace Qt::StringLiterals;
namespace {
constexpr char kWorkerExecutableName[] = "strawberry-tagreader";
}
TagReaderClient *TagReaderClient::sInstance = nullptr;
TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool<HandlerType>(this)) {
setObjectName(QLatin1String(metaObject()->className()));
sInstance = this;
original_thread_ = thread();
worker_pool_->SetExecutableName(QLatin1String(kWorkerExecutableName));
QObject::connect(worker_pool_, &WorkerPool<HandlerType>::WorkerFailedToStart, this, &TagReaderClient::WorkerFailedToStart);
}
void TagReaderClient::Start() { worker_pool_->Start(); }
void TagReaderClient::ExitAsync() {
QMetaObject::invokeMethod(this, &TagReaderClient::Exit, Qt::QueuedConnection);
}
void TagReaderClient::Exit() {
Q_ASSERT(QThread::currentThread() == thread());
moveToThread(original_thread_);
Q_EMIT ExitFinished();
}
void TagReaderClient::WorkerFailedToStart() {
qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it.";
}
TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) {
spb::tagreader::Message message;
message.mutable_is_media_file_request()->set_filename(filename.toStdString());
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
spb::tagreader::Message message;
message.mutable_read_file_request()->set_filename(filename.toStdString());
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::WriteFile(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
spb::tagreader::Message message;
spb::tagreader::WriteFileRequest *request = message.mutable_write_file_request();
request->set_filename(filename.toStdString());
request->set_save_tags(save_types.testFlag(SaveType::Tags));
request->set_save_playcount(save_types.testFlag(SaveType::PlayCount));
request->set_save_rating(save_types.testFlag(SaveType::Rating));
request->set_save_cover(save_types.testFlag(SaveType::Cover));
if (!save_cover_options.cover_filename.isEmpty()) {
request->set_cover_filename(save_cover_options.cover_filename.toStdString());
}
if (!save_cover_options.cover_data.isEmpty()) {
request->set_cover_data(save_cover_options.cover_data.toStdString());
}
if (!save_cover_options.mime_type.isEmpty()) {
request->set_cover_mime_type(save_cover_options.mime_type.toStdString());
}
metadata.ToProtobuf(request->mutable_metadata());
ReplyType *reply = worker_pool_->SendMessageWithReply(&message);
return reply;
}
TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) {
spb::tagreader::Message message;
spb::tagreader::LoadEmbeddedArtRequest *request = message.mutable_load_embedded_art_request();
request->set_filename(filename.toStdString());
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options) {
spb::tagreader::Message message;
spb::tagreader::SaveEmbeddedArtRequest *request = message.mutable_save_embedded_art_request();
request->set_filename(filename.toStdString());
if (!save_cover_options.cover_filename.isEmpty()) {
request->set_cover_filename(save_cover_options.cover_filename.toStdString());
}
if (!save_cover_options.cover_data.isEmpty()) {
request->set_cover_data(save_cover_options.cover_data.toStdString());
}
if (!save_cover_options.mime_type.isEmpty()) {
request->set_cover_mime_type(save_cover_options.mime_type.toStdString());
}
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::SaveSongPlaycount(const QString &filename, const uint playcount) {
spb::tagreader::Message message;
spb::tagreader::SaveSongPlaycountToFileRequest *request = message.mutable_save_song_playcount_to_file_request();
request->set_filename(filename.toStdString());
request->set_playcount(playcount);
return worker_pool_->SendMessageWithReply(&message);
}
void TagReaderClient::SaveSongsPlaycount(const SongList &songs) {
for (const Song &song : songs) {
TagReaderReply *reply = SaveSongPlaycount(song.url().toLocalFile(), song.playcount());
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
}
}
TagReaderReply *TagReaderClient::SaveSongRating(const QString &filename, const float rating) {
spb::tagreader::Message message;
spb::tagreader::SaveSongRatingToFileRequest *request = message.mutable_save_song_rating_to_file_request();
request->set_filename(filename.toStdString());
request->set_rating(rating);
return worker_pool_->SendMessageWithReply(&message);
}
void TagReaderClient::SaveSongsRating(const SongList &songs) {
for (const Song &song : songs) {
TagReaderReply *reply = SaveSongRating(song.url().toLocalFile(), song.rating());
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
}
}
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
bool success = false;
TagReaderReply *reply = IsMediaFile(filename);
if (reply->WaitForFinished()) {
const spb::tagreader::IsMediaFileResponse &response = reply->message().is_media_file_response();
if (response.has_success()) {
success = response.success();
}
}
reply->deleteLater();
return success;
}
TagReaderClient::Result TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = ReadFile(filename);
if (reply->WaitForFinished()) {
const spb::tagreader::ReadFileResponse &response = reply->message().read_file_response();
if (response.has_success()) {
if (response.success()) {
result.error_code = Result::ErrorCode::Success;
if (response.has_metadata()) {
song->InitFromProtobuf(response.metadata());
}
}
else {
result.error_code = Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = WriteFile(filename, metadata, save_types, save_cover_options);
if (reply->WaitForFinished()) {
const spb::tagreader::WriteFileResponse &response = reply->message().write_file_response();
if (response.has_success()) {
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = LoadEmbeddedArt(filename);
if (reply->WaitForFinished()) {
const spb::tagreader::LoadEmbeddedArtResponse &response = reply->message().load_embedded_art_response();
if (response.has_success()) {
if (response.success()) {
result.error_code = Result::ErrorCode::Success;
if (response.has_data()) {
data = QByteArray(response.data().data(), static_cast<qint64>(response.data().size()));
}
}
else {
result.error_code = Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image) {
Q_ASSERT(QThread::currentThread() != thread());
QByteArray data;
Result result = LoadEmbeddedArtBlocking(filename, data);
if (result.error_code == Result::ErrorCode::Success && !image.loadFromData(data)) {
result.error_code = Result::ErrorCode::Failure;
result.error = QObject::tr("Failed to load image from data for %1").arg(filename);
}
return result;
}
TagReaderClient::Result TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = SaveEmbeddedArt(filename, save_cover_options);
if (reply->WaitForFinished()) {
const spb::tagreader::SaveEmbeddedArtResponse &response = reply->message().save_embedded_art_response();
if (response.has_success()) {
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = SaveSongPlaycount(filename, playcount);
if (reply->WaitForFinished()) {
const spb::tagreader::SaveSongPlaycountToFileResponse &response = reply->message().save_song_playcount_to_file_response();
if (response.has_success()) {
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = SaveSongRating(filename, rating);
if (reply->WaitForFinished()) {
const spb::tagreader::SaveSongRatingToFileResponse &response = reply->message().save_song_rating_to_file_response();
if (response.has_success()) {
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
reply->deleteLater();
return result;
}

View File

@@ -1,137 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERCLIENT_H
#define TAGREADERCLIENT_H
#include "config.h"
#include <QObject>
#include <QList>
#include <QString>
#include <QImage>
#include "core/messagehandler.h"
#include "core/workerpool.h"
#include "song.h"
#include "tagreadermessages.pb.h"
class QThread;
class Song;
template<typename HandlerType> class WorkerPool;
class TagReaderClient : public QObject {
Q_OBJECT
public:
explicit TagReaderClient(QObject *parent = nullptr);
using HandlerType = AbstractMessageHandler<spb::tagreader::Message>;
using ReplyType = HandlerType::ReplyType;
void Start();
void ExitAsync();
enum class SaveType {
NoType = 0,
Tags = 1,
PlayCount = 2,
Rating = 4,
Cover = 8
};
Q_DECLARE_FLAGS(SaveTypes, SaveType)
class SaveCoverOptions {
public:
explicit SaveCoverOptions(const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray(), const QString &_mime_type = QString()) : cover_filename(_cover_filename), cover_data(_cover_data), mime_type(_mime_type) {}
explicit SaveCoverOptions(const QString &_cover_filename, const QString &_mime_type = QString()) : cover_filename(_cover_filename), mime_type(_mime_type) {}
explicit SaveCoverOptions(const QByteArray &_cover_data, const QString &_mime_type = QString()) : cover_data(_cover_data), mime_type(_mime_type) {}
QString cover_filename;
QByteArray cover_data;
QString mime_type;
};
class Result {
public:
enum class ErrorCode {
Success,
Unsupported,
Failure,
};
Result(const ErrorCode _error_code, const QString &_error = QString()) : error_code(_error_code), error(_error) {}
ErrorCode error_code;
QString error;
bool success() const { return error_code == TagReaderClient::Result::ErrorCode::Success; }
};
class Cover {
public:
explicit Cover(const QByteArray &_data = QByteArray(), const QString &_mime_type = QString()) : data(_data), mime_type(_mime_type) {}
QByteArray data;
QString mime_type;
QString error;
};
ReplyType *IsMediaFile(const QString &filename);
ReplyType *ReadFile(const QString &filename);
ReplyType *WriteFile(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
ReplyType *LoadEmbeddedArt(const QString &filename);
ReplyType *SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options);
ReplyType *SaveSongPlaycount(const QString &filename, const uint playcount);
ReplyType *SaveSongRating(const QString &filename, const float rating);
// Convenience functions that call the above functions and wait for a response.
// These block the calling thread with a semaphore, and must NOT be called from the TagReaderClient's thread.
Result ReadFileBlocking(const QString &filename, Song *song);
Result WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
bool IsMediaFileBlocking(const QString &filename);
Result LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data);
Result LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image);
Result SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options);
Result SaveSongPlaycountBlocking(const QString &filename, const uint playcount);
Result SaveSongRatingBlocking(const QString &filename, const float rating);
// TODO: Make this not a singleton
static TagReaderClient *Instance() { return sInstance; }
Q_SIGNALS:
void ExitFinished();
private Q_SLOTS:
void Exit();
void WorkerFailedToStart();
public Q_SLOTS:
void SaveSongsPlaycount(const SongList &songs);
void SaveSongsRating(const SongList &songs);
private:
static TagReaderClient *sInstance;
WorkerPool<HandlerType> *worker_pool_;
QList<spb::tagreader::Message> message_queue_;
QThread *original_thread_;
};
using TagReaderReply = TagReaderClient::ReplyType;
#endif // TAGREADERCLIENT_H

View File

@@ -63,9 +63,8 @@
#include "core/application.h"
#include "core/song.h"
#include "core/iconloader.h"
#include "core/tagreaderclient.h"
#include "core/settings.h"
#include "tagreader/tagreaderclient.h"
#include "collection/collectionfilteroptions.h"
#include "collection/collectionbackend.h"
#include "settings/coverssettingspage.h"
@@ -447,7 +446,7 @@ void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image
case AlbumCoverLoaderOptions::Type::Embedded:{
if (song.art_embedded() && !song.url().isEmpty() && song.url().isValid() && song.url().isLocalFile()) {
QImage image_embedded_cover;
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song.url().toLocalFile(), image_embedded_cover);
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song.url().toLocalFile(), image_embedded_cover);
if (result.success() && !image_embedded_cover.isNull()) {
QPixmap pixmap = QPixmap::fromImage(image_embedded_cover);
if (!pixmap.isNull()) {
@@ -737,8 +736,8 @@ void AlbumCoverChoiceController::SaveCoverEmbeddedToSong(const Song &song, const
QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.append(song);
const bool art_embedded = !image_data.isNull();
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(song.url().toLocalFile(), TagReaderClient::SaveCoverOptions(cover_filename, image_data, mime_type));
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, song, art_embedded]() { SaveEmbeddedCoverFinished(reply, song, art_embedded); });
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(song.url().toLocalFile(), SaveTagCoverData(cover_filename, image_data, mime_type));
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, song, art_embedded]() { SaveEmbeddedCoverFinished(reply, song, art_embedded); });
}
@@ -815,12 +814,12 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
}
void AlbumCoverChoiceController::SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded) {
void AlbumCoverChoiceController::SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, Song song, const bool art_embedded) {
if (!cover_save_tasks_.contains(song)) return;
cover_save_tasks_.removeAll(song);
if (reply->is_successful()) {
if (reply->success()) {
SaveArtEmbeddedToSong(&song, art_embedded);
}
else {

View File

@@ -38,7 +38,7 @@
#include <QMutex>
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
#include "utilities/coveroptions.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverimageresult.h"
@@ -160,7 +160,7 @@ class AlbumCoverChoiceController : public QWidget {
private Q_SLOTS:
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
void SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded);
void SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, Song song, const bool art_embedded);
Q_SIGNALS:
void Error(const QString &error);

View File

@@ -39,9 +39,9 @@
#include "core/logging.h"
#include "core/networkaccessmanager.h"
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "utilities/mimeutils.h"
#include "utilities/imageutils.h"
#include "tagreader/tagreaderclient.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
@@ -318,7 +318,7 @@ AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, cons
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadEmbeddedImage(TaskPtr task) {
if (task->art_embedded && task->song_url.isValid() && task->song_url.isLocalFile()) {
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile(), task->album_cover.image_data);
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverDataBlocking(task->song_url.toLocalFile(), task->album_cover.image_data);
if (result.success() && !task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
return LoadImageResult(AlbumCoverLoaderResult::Type::Embedded, LoadImageResult::Status::Success);
}

View File

@@ -66,7 +66,6 @@
#include "core/logging.h"
#include "core/application.h"
#include "core/iconloader.h"
#include "core/tagreaderclient.h"
#include "core/database.h"
#include "core/sqlrow.h"
#include "core/settings.h"
@@ -77,6 +76,7 @@
#include "utilities/screenutils.h"
#include "widgets/forcescrollperpixel.h"
#include "widgets/searchfield.h"
#include "tagreader/tagreaderclient.h"
#include "collection/collectionbackend.h"
#include "collection/collectionquery.h"
#include "playlist/songmimedata.h"
@@ -758,9 +758,9 @@ void AlbumCoverManager::SaveCoverToFile() {
return;
case AlbumCoverLoaderOptions::Type::Embedded:
if (song.art_embedded()) {
const TagReaderClient::Result tagreaderclient_result = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile(), result.image_data);
const TagReaderResult tagreaderclient_result = TagReaderClient::Instance()->LoadCoverDataBlocking(song.url().toLocalFile(), result.image_data);
if (!tagreaderclient_result.success()) {
qLog(Error) << "Could not load embedded art from" << song.url() << tagreaderclient_result.error;
qLog(Error) << "Could not load embedded art from" << song.url() << tagreaderclient_result.error_string();
}
}
break;
@@ -845,8 +845,8 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
case CoverOptions::CoverType::Embedded:{
for (const QUrl &url : std::as_const(album_item->urls)) {
const bool art_embedded = !result.image_data.isEmpty();
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.image_data, result.mime_type));
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(url.toLocalFile(), SaveTagCoverData(result.image_data, result.mime_type));
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
});
cover_save_tasks_.insert(album_item, url);
@@ -995,8 +995,8 @@ void AlbumCoverManager::SaveAndSetCover(AlbumItem *album_item, const AlbumCoverI
if (album_cover_choice_controller_->get_save_album_cover_type() == CoverOptions::CoverType::Embedded && Song::save_embedded_cover_supported(filetype) && !has_cue) {
for (const QUrl &url : urls) {
const bool art_embedded = !result.image_data.isEmpty();
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.cover_url.isValid() ? result.cover_url.toLocalFile() : QString(), result.image_data, result.mime_type));
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(url.toLocalFile(), SaveTagCoverData(result.cover_url.isValid() ? result.cover_url.toLocalFile() : QString(), result.image_data, result.mime_type));
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
});
cover_save_tasks_.insert(album_item, url);
@@ -1094,13 +1094,13 @@ bool AlbumCoverManager::ItemHasCover(const AlbumItem &album_item) const {
return album_item.icon().cacheKey() != icon_nocover_item_.cacheKey();
}
void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded) {
void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded) {
if (cover_save_tasks_.contains(album_item, url)) {
cover_save_tasks_.remove(album_item, url);
}
if (!reply->is_successful()) {
if (!reply->success()) {
Q_EMIT Error(tr("Could not save cover to file %1.").arg(url.toLocalFile()));
return;
}

View File

@@ -39,7 +39,7 @@
#include "core/shared_ptr.h"
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverchoicecontroller.h"
@@ -185,7 +185,7 @@ class AlbumCoverManager : public QMainWindow {
void UpdateCoverInList(AlbumItem *album_item, const QUrl &cover);
void UpdateExportStatus(const int exported, const int skipped, const int max);
void SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded);
void SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded);
private:
Ui_CoverManager *ui_;

View File

@@ -29,7 +29,7 @@
#include <QImage>
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h"
#include "coverexportrunnable.h"
@@ -78,7 +78,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
break;
case AlbumCoverLoaderOptions::Type::Embedded:
if (song_.art_embedded() && dialog_result_.export_embedded_) {
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile(), image);
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song_.url().toLocalFile(), image);
if (result.success() && !image.isNull()) {
extension = "jpg"_L1;
}
@@ -170,7 +170,7 @@ void CoverExportRunnable::ExportCover() {
break;
case AlbumCoverLoaderOptions::Type::Embedded:
if (song_.art_embedded() && dialog_result_.export_embedded_) {
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile(), image);
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song_.url().toLocalFile(), image);
if (result.success() && !image.isNull()) {
embedded_cover = true;
extension = "jpg"_L1;

View File

@@ -73,13 +73,13 @@
#include "core/application.h"
#include "core/iconloader.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/settings.h"
#include "utilities/strutils.h"
#include "utilities/timeutils.h"
#include "utilities/imageutils.h"
#include "utilities/coverutils.h"
#include "utilities/coveroptions.h"
#include "tagreader/tagreaderclient.h"
#include "widgets/busyindicator.h"
#include "widgets/lineedit.h"
#include "collection/collectionbackend.h"
@@ -99,7 +99,6 @@
#include "covermanager/albumcoverimageresult.h"
#include "edittagdialog.h"
#include "ui_edittagdialog.h"
#include "tagreadermessages.pb.h"
using namespace Qt::StringLiterals;
@@ -401,7 +400,7 @@ QList<EditTagDialog::Data> EditTagDialog::LoadData(const SongList &songs) {
if (song.IsEditable()) {
// Try reloading the tags from file
Song copy(song);
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), &copy);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), &copy);
if (result.success() && copy.is_valid()) {
copy.MergeUserSetData(song, false, false);
ret << Data(copy);
@@ -1276,31 +1275,31 @@ void EditTagDialog::SaveData() {
if (ref.current_.originalyear() <= 0) { ref.current_.set_originalyear(-1); }
if (ref.current_.lastplayed() <= 0) { ref.current_.set_lastplayed(-1); }
++save_tag_pending_;
TagReaderClient::SaveCoverOptions savecover_options;
SaveTagCoverData save_tag_cover_data;
if (save_embedded_cover && ref.cover_action_ == UpdateCoverAction::New) {
if (!ref.cover_result_.image.isNull()) {
savecover_options.mime_type = ref.cover_result_.mime_type;
save_tag_cover_data.cover_mimetype = ref.cover_result_.mime_type;
}
else if (!embedded_cover_from_file.isEmpty()) {
savecover_options.cover_filename = embedded_cover_from_file;
save_tag_cover_data.cover_filename = embedded_cover_from_file;
}
savecover_options.cover_data = ref.cover_result_.image_data;
save_tag_cover_data.cover_data = ref.cover_result_.image_data;
}
TagReaderClient::SaveTypes save_types;
TagReaderClient::SaveOptions save_tags_options;
if (save_tags) {
save_types |= TagReaderClient::SaveType::Tags;
save_tags_options |= TagReaderClient::SaveOption::Tags;
}
if (save_playcount) {
save_types |= TagReaderClient::SaveType::PlayCount;
save_tags_options |= TagReaderClient::SaveOption::Playcount;
}
if (save_rating) {
save_types |= TagReaderClient::SaveType::Rating;
save_tags_options |= TagReaderClient::SaveOption::Rating;
}
if (save_embedded_cover) {
save_types |= TagReaderClient::SaveType::Cover;
save_tags_options |= TagReaderClient::SaveOption::Cover;
}
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(ref.current_.url().toLocalFile(), ref.current_, save_types, savecover_options);
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_); }, Qt::QueuedConnection);
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(ref.current_.url().toLocalFile(), ref.current_, save_tags_options, save_tag_cover_data);
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_); }, Qt::QueuedConnection);
}
// If the cover was changed, but no tags written, make sure to update the collection.
else if (ref.cover_action_ != UpdateCoverAction::None && !ref.current_.effective_albumartist().isEmpty() && !ref.current_.album().isEmpty()) {
@@ -1451,15 +1450,12 @@ void EditTagDialog::UpdateLyrics(const quint64 id, const QString &provider, cons
}
void EditTagDialog::SongSaveTagsComplete(TagReaderReply *reply, const QString &filename, Song song, const UpdateCoverAction cover_action) {
void EditTagDialog::SongSaveTagsComplete(TagReaderReplyPtr reply, const QString &filename, Song song, const UpdateCoverAction cover_action) {
--save_tag_pending_;
const bool success = reply->message().write_file_response().success();
QString error;
if (!success && reply->message().write_file_response().has_error()) {
error = QString::fromStdString(reply->message().write_file_response().error());
}
reply->deleteLater();
const bool success = reply->success();
const QString error = reply->error();
if (success) {
if (song.is_collection_song()) {

View File

@@ -36,7 +36,7 @@
#include <QImage>
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
#include "playlist/playlistitem.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "covermanager/albumcoverloaderresult.h"
@@ -133,7 +133,7 @@ class EditTagDialog : public QDialog {
void PreviousSong();
void NextSong();
void SongSaveTagsComplete(TagReaderReply *reply, const QString &filename, Song song, const UpdateCoverAction cover_action);
void SongSaveTagsComplete(TagReaderReplyPtr reply, const QString &filename, Song song, const UpdateCoverAction cover_action);
private:
struct FieldData {

View File

@@ -48,7 +48,7 @@
#include "core/iconloader.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
#include "widgets/busyindicator.h"
#include "trackselectiondialog.h"
#include "ui_trackselectiondialog.h"
@@ -278,9 +278,9 @@ void TrackSelectionDialog::SaveData(const QList<Data> &data) {
copy.set_track(new_metadata.track());
copy.set_year(new_metadata.year());
const TagReaderClient::Result result = TagReaderClient::Instance()->WriteFileBlocking(copy.url().toLocalFile(), copy, TagReaderClient::SaveType::Tags, TagReaderClient::SaveCoverOptions());
const TagReaderResult result = TagReaderClient::Instance()->WriteFileBlocking(copy.url().toLocalFile(), copy, TagReaderClient::SaveOption::Tags, SaveTagCoverData());
if (!result.success()) {
qLog(Error) << "Failed to write new auto-tags to" << copy.url().toLocalFile() << result.error;
qLog(Error) << "Failed to write new auto-tags to" << copy.url().toLocalFile() << result.error_string();
}
}

View File

@@ -39,9 +39,9 @@
#include "core/shared_ptr.h"
#include "core/taskmanager.h"
#include "core/musicstorage.h"
#include "core/tagreaderclient.h"
#include "core/song.h"
#include "utilities/strutils.h"
#include "tagreader/tagreaderclient.h"
#include "organize.h"
#ifdef HAVE_GSTREAMER
# include "transcoder/transcoder.h"
@@ -245,9 +245,9 @@ void Organize::ProcessSomeFiles() {
}
}
else if (destination_->source() == Song::Source::Device) {
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(task.song_info_.song_.url().toLocalFile(), job.cover_image_);
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(task.song_info_.song_.url().toLocalFile(), job.cover_image_);
if (!result.success()) {
qLog(Error) << "Could not load embedded art from" << task.song_info_.song_.url() << result.error;
qLog(Error) << "Could not load embedded art from" << task.song_info_.song_.url() << result.error_string();
}
}

View File

@@ -59,12 +59,12 @@
#include "core/shared_ptr.h"
#include "core/iconloader.h"
#include "core/musicstorage.h"
#include "core/tagreaderclient.h"
#include "core/settings.h"
#include "utilities/strutils.h"
#include "utilities/screenutils.h"
#include "widgets/freespacebar.h"
#include "widgets/linetextedit.h"
#include "tagreader/tagreaderclient.h"
#include "collection/collectionbackend.h"
#include "organize.h"
#include "organizeformat.h"
@@ -423,12 +423,12 @@ SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) {
continue;
}
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
if (result.success() && song.is_valid()) {
songs << song;
}
else {
qLog(Error) << "Could not read file" << filename << result.error;
qLog(Error) << "Could not read file" << filename << result.error_string();
}
}

View File

@@ -62,10 +62,10 @@
#include "core/application.h"
#include "core/logging.h"
#include "core/mimedata.h"
#include "core/tagreaderclient.h"
#include "core/song.h"
#include "core/settings.h"
#include "utilities/timeconstants.h"
#include "tagreader/tagreaderclient.h"
#include "collection/collection.h"
#include "collection/collectionbackend.h"
#include "collection/collectionplaylistitem.h"
@@ -418,9 +418,9 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
if (!set_column_value(song, static_cast<Column>(idx.column()), value)) return false;
if (song.url().isLocalFile()) {
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
QPersistentModelIndex persistent_index = QPersistentModelIndex(idx);
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index, item]() { SongSaveComplete(reply, persistent_index, item->OriginalMetadata()); }, Qt::QueuedConnection);
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index, item]() { SongSaveComplete(reply, persistent_index, item->OriginalMetadata()); }, Qt::QueuedConnection);
}
else if (song.is_radio()) {
item->SetMetadata(song);
@@ -431,18 +431,18 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
}
void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata) {
void Playlist::SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx, const Song &old_metadata) {
if (reply->is_successful() && idx.isValid()) {
if (reply->message().write_file_response().success()) {
if (reply->success() && idx.isValid()) {
if (reply->success()) {
ItemReload(idx, old_metadata, true);
}
else {
if (reply->request_message().write_file_response().has_error()) {
Q_EMIT Error(tr("Could not write metadata to %1: %2").arg(QString::fromStdString(reply->request_message().write_file_request().filename()), QString::fromStdString(reply->request_message().write_file_response().error())));
if (reply->error().isEmpty()) {
Q_EMIT Error(tr("Could not write metadata to %1").arg(reply->filename()));
}
else {
Q_EMIT Error(tr("Could not write metadata to %1").arg(QString::fromStdString(reply->request_message().write_file_request().filename())));
Q_EMIT Error(tr("Could not write metadata to %1: %2").arg(reply->filename(), reply->error()));
}
}
}

View File

@@ -43,7 +43,7 @@
#include "core/shared_ptr.h"
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
#include "covermanager/albumcoverloaderresult.h"
#include "playlistitem.h"
#include "playlistsequence.h"
@@ -350,7 +350,7 @@ class Playlist : public QAbstractListModel {
void TracksDequeued();
void TracksEnqueued(const QModelIndex &parent_idx, const int begin, const int end);
void QueueLayoutChanged();
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata);
void SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx, const Song &old_metadata);
void ItemReloadComplete(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit);
void ItemsLoaded();
void ScheduleSave();

View File

@@ -26,7 +26,7 @@
#include "core/logging.h"
#include "core/song.h"
#include "core/sqlrow.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
#include "playlistitem.h"
#include "songplaylistitem.h"
@@ -44,9 +44,9 @@ void SongPlaylistItem::Reload() {
if (!song_.url().isLocalFile()) return;
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
if (!result.success()) {
qLog(Error) << "Could not reload file" << song_.url() << result.error;
qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
}
UpdateTemporaryMetadata(song_);

View File

@@ -29,7 +29,7 @@
#include "core/shared_ptr.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "tagreader/tagreaderclient.h"
#include "collection/collectionbackend.h"
#include "settings/playlistsettingspage.h"
#include "parserbase.h"
@@ -105,9 +105,9 @@ void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning
}
}
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
if (!result.success()) {
qLog(Error) << "Could not read file" << filename << result.error;
qLog(Error) << "Could not read file" << filename << result.error_string();
}
}

View File

@@ -0,0 +1,27 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QByteArray>
#include <QString>
#include "albumcovertagdata.h"
AlbumCoverTagData::AlbumCoverTagData(const QByteArray &_data, const QString &_mimetype)
: data(_data),
mimetype(_mimetype) {}

View File

@@ -0,0 +1,34 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ALBUMCOVERTAGDATA_H
#define ALBUMCOVERTAGDATA_H
#include <QByteArray>
#include <QString>
class AlbumCoverTagData {
public:
explicit AlbumCoverTagData(const QByteArray &_data = QByteArray(), const QString &_mimetype = QString());
QByteArray data;
QString mimetype;
QString error;
};
#endif // ALBUMCOVERTAGDATA_H

View File

@@ -0,0 +1,32 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QByteArray>
#include <QString>
#include "savetagcoverdata.h"
SaveTagCoverData::SaveTagCoverData(const QString &_cover_filename, const QByteArray &_cover_data, const QString &_cover_mimetype)
: cover_filename(_cover_filename),
cover_data(_cover_data),
cover_mimetype(_cover_mimetype) {}
SaveTagCoverData::SaveTagCoverData(const QByteArray &_cover_data, const QString &_cover_mimetype)
: cover_data(_cover_data),
cover_mimetype(_cover_mimetype) {}

View File

@@ -0,0 +1,35 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SAVETAGCOVERDATA_H
#define SAVETAGCOVERDATA_H
#include <QByteArray>
#include <QString>
class SaveTagCoverData {
public:
SaveTagCoverData(const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray(), const QString &_cover_mimetype = QString());
SaveTagCoverData(const QByteArray &_cover_data, const QString &_cover_mimetype = QString());
QString cover_filename;
QByteArray cover_data;
QString cover_mimetype;
};
#endif // SAVETAGCOVERDATA_H

View File

@@ -0,0 +1,34 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SAVETAGSOPTIONS_H
#define SAVETAGSOPTIONS_H
#include <QtGlobal>
enum class SaveTagsOption {
NoType = 0,
Tags = 1,
Playcount = 2,
Rating = 4,
Cover = 8
};
Q_DECLARE_FLAGS(SaveTagsOptions, SaveTagsOption)
#endif // SAVETAGSOPTIONS_H

View File

@@ -0,0 +1,110 @@
/*
* Strawberry Music Player
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QObject>
#include <QByteArray>
#include <QString>
#include <QIODevice>
#include <QFile>
#include <QBuffer>
#include <QImage>
#include <QMimeDatabase>
#include "core/logging.h"
#include "tagreaderbase.h"
using namespace Qt::StringLiterals;
TagReaderBase::TagReaderBase() = default;
TagReaderBase::~TagReaderBase() = default;
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
if (POPM_rating < 0x01) return 0.0F;
if (POPM_rating < 0x40) return 0.20F;
if (POPM_rating < 0x80) return 0.40F;
if (POPM_rating < 0xC0) return 0.60F;
if (POPM_rating < 0xFC) return 0.80F;
return 1.0F;
}
int TagReaderBase::ConvertToPOPMRating(const float rating) {
if (rating < 0.20) return 0x00;
if (rating < 0.40) return 0x01;
if (rating < 0.60) return 0x40;
if (rating < 0.80) return 0x80;
if (rating < 1.0) return 0xC0;
return 0xFF;
}
AlbumCoverTagData TagReaderBase::LoadAlbumCoverTagData(const QString &song_filename, const SaveTagCoverData &save_tag_cover_data) {
const QString &cover_filename = save_tag_cover_data.cover_filename;
QByteArray cover_data = save_tag_cover_data.cover_data;
QString cover_mimetype = save_tag_cover_data.cover_mimetype;
if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
QFile file(cover_filename);
if (!file.open(QIODevice::ReadOnly)) {
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
return AlbumCoverTagData();
}
cover_data = file.readAll();
file.close();
}
if (!cover_data.isEmpty()) {
if (cover_mimetype.isEmpty()) {
cover_mimetype = QMimeDatabase().mimeTypeForData(cover_data).name();
}
if (cover_mimetype == "image/jpeg"_L1) {
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
return AlbumCoverTagData(cover_data, cover_mimetype);
}
if (cover_mimetype == "image/png"_L1) {
qLog(Debug) << "Using cover from PNG data for" << song_filename;
return AlbumCoverTagData(cover_data, cover_mimetype);
}
// Convert image to JPEG.
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
QImage cover_image;
if (!cover_image.loadFromData(cover_data)) {
qLog(Error) << "Failed to load image from cover data for" << song_filename;
return AlbumCoverTagData();
}
cover_data.clear();
QBuffer buffer(&cover_data);
if (buffer.open(QIODevice::WriteOnly)) {
cover_image.save(&buffer, "JPEG");
buffer.close();
}
return AlbumCoverTagData(cover_data, u"image/jpeg"_s);
}
return AlbumCoverTagData();
}

View File

@@ -0,0 +1,61 @@
/*
* Strawberry Music Player
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERBASE_H
#define TAGREADERBASE_H
#include "config.h"
#include <QtGlobal>
#include <QByteArray>
#include <QString>
#include "core/song.h"
#include "tagreaderresult.h"
#include "savetagsoptions.h"
#include "savetagcoverdata.h"
#include "albumcovertagdata.h"
class TagReaderBase {
public:
explicit TagReaderBase();
virtual ~TagReaderBase();
virtual TagReaderResult IsMediaFile(const QString &filename) const = 0;
virtual TagReaderResult ReadFile(const QString &filename, Song *song) const = 0;
virtual TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const = 0;
virtual TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const = 0;
virtual TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const = 0;
virtual TagReaderResult SaveSongPlaycount(const QString &filename, const uint playcount) const = 0;
virtual TagReaderResult SaveSongRating(const QString &filename, const float rating) const = 0;
protected:
static float ConvertPOPMRating(const int POPM_rating);
static int ConvertToPOPMRating(const float rating);
static AlbumCoverTagData LoadAlbumCoverTagData(const QString &song_filename, const SaveTagCoverData &save_tag_cover_data);
Q_DISABLE_COPY(TagReaderBase)
};
#endif // TAGREADERBASE_H

View File

@@ -0,0 +1,423 @@
/*
* Strawberry Music Player
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QtGlobal>
#include <QObject>
#include <QThread>
#include <QMutex>
#include <QByteArray>
#include <QString>
#include <QImage>
#include <QScopeGuard>
#include "core/logging.h"
#include "core/song.h"
#include "tagreaderclient.h"
#include "tagreadertaglib.h"
#include "tagreaderresult.h"
#include "tagreaderrequest.h"
#include "tagreaderismediafilerequest.h"
#include "tagreaderreadfilerequest.h"
#include "tagreaderwritefilerequest.h"
#include "tagreaderloadcoverdatarequest.h"
#include "tagreaderloadcoverimagerequest.h"
#include "tagreadersavecoverrequest.h"
#include "tagreadersaveplaycountrequest.h"
#include "tagreadersaveratingrequest.h"
#include "tagreaderreply.h"
#include "tagreaderreadfilereply.h"
#include "tagreaderloadcoverdatareply.h"
#include "tagreaderloadcoverimagereply.h"
using std::dynamic_pointer_cast;
using namespace Qt::StringLiterals;
TagReaderClient *TagReaderClient::sInstance = nullptr;
TagReaderClient::TagReaderClient(QObject *parent)
: QObject(parent),
original_thread_(thread()),
abort_(false),
processing_(false) {
setObjectName(QLatin1String(metaObject()->className()));
sInstance = this;
}
void TagReaderClient::ExitAsync() {
Q_ASSERT(QThread::currentThread() != thread());
abort_ = true;
QMetaObject::invokeMethod(this, &TagReaderClient::Exit, Qt::QueuedConnection);
}
void TagReaderClient::Exit() {
Q_ASSERT(QThread::currentThread() == thread());
moveToThread(original_thread_);
Q_EMIT ExitFinished();
}
bool TagReaderClient::HaveRequests() const {
Q_ASSERT(QThread::currentThread() == thread());
{
QMutexLocker l(&mutex_requests_);
return !requests_.isEmpty();
}
}
void TagReaderClient::EnqueueRequest(TagReaderRequestPtr request) {
Q_ASSERT(QThread::currentThread() != thread());
{
QMutexLocker l(&mutex_requests_);
requests_.enqueue(request);
}
if (!processing_.value()) {
ProcessRequestsAsync();
}
}
TagReaderRequestPtr TagReaderClient::DequeueRequest() {
Q_ASSERT(QThread::currentThread() == thread());
{
QMutexLocker l(&mutex_requests_);
if (requests_.isEmpty()) return TagReaderRequestPtr();
return requests_.dequeue();
}
}
void TagReaderClient::ProcessRequestsAsync() {
Q_ASSERT(QThread::currentThread() != thread());
QMetaObject::invokeMethod(this, &TagReaderClient::ProcessRequests, Qt::QueuedConnection);
}
void TagReaderClient::ProcessRequests() {
Q_ASSERT(QThread::currentThread() == thread());
processing_ = true;
const QScopeGuard scopeguard_processing = qScopeGuard([this]() {
processing_ = false;
});
while (HaveRequests()) {
if (abort_.value()) return;
ProcessRequest(DequeueRequest());
}
}
void TagReaderClient::ProcessRequest(TagReaderRequestPtr request) {
Q_ASSERT(QThread::currentThread() == thread());
TagReaderReplyPtr reply = request->reply;
TagReaderResult result;
if (TagReaderIsMediaFileRequestPtr is_media_file_request = std::dynamic_pointer_cast<TagReaderIsMediaFileRequest>(request)) {
result = tagreader_.IsMediaFile(is_media_file_request->filename);
if (result.error_code == TagReaderResult::ErrorCode::Unsupported) {
result = gmereader_.IsMediaFile(is_media_file_request->filename);
}
}
else if (TagReaderReadFileRequestPtr read_file_request = std::dynamic_pointer_cast<TagReaderReadFileRequest>(request)) {
Song song;
result = ReadFileBlocking(read_file_request->filename, &song);
if (result.error_code == TagReaderResult::ErrorCode::Unsupported) {
result = gmereader_.ReadFile(read_file_request->filename, &song);
}
if (result.success()) {
if (TagReaderReadFileReplyPtr read_file_reply = std::dynamic_pointer_cast<TagReaderReadFileReply>(reply)) {
read_file_reply->set_song(song);
}
}
}
else if (TagReaderWriteFileRequestPtr write_file_request = dynamic_pointer_cast<TagReaderWriteFileRequest>(request)) {
result = WriteFileBlocking(write_file_request->filename, write_file_request->song, write_file_request->save_tags_options, write_file_request->save_tag_cover_data);
}
else if (TagReaderLoadCoverDataRequestPtr load_cover_data_request = dynamic_pointer_cast<TagReaderLoadCoverDataRequest>(request)) {
QByteArray cover_data;
result = LoadCoverDataBlocking(load_cover_data_request->filename, cover_data);
if (result.success()) {
if (TagReaderLoadCoverDataReplyPtr load_cover_data_reply = std::dynamic_pointer_cast<TagReaderLoadCoverDataReply>(reply)) {
load_cover_data_reply->set_data(cover_data);
}
}
}
else if (TagReaderLoadCoverImageRequestPtr load_cover_image_request = dynamic_pointer_cast<TagReaderLoadCoverImageRequest>(request)) {
QImage cover_image;
result = LoadCoverImageBlocking(load_cover_image_request->filename, cover_image);
if (result.success()) {
if (TagReaderLoadCoverImageReplyPtr load_cover_image_reply = std::dynamic_pointer_cast<TagReaderLoadCoverImageReply>(reply)) {
load_cover_image_reply->set_image(cover_image);
}
}
}
else if (TagReaderSaveCoverRequestPtr save_cover_request = dynamic_pointer_cast<TagReaderSaveCoverRequest>(request)) {
result = SaveCoverBlocking(save_cover_request->filename, save_cover_request->save_tag_cover_data);
}
else if (TagReaderSavePlaycountRequestPtr save_playcount_request = dynamic_pointer_cast<TagReaderSavePlaycountRequest>(request)) {
result = SaveSongPlaycountBlocking(save_playcount_request->filename, save_playcount_request->playcount);
}
else if (TagReaderSaveRatingRequestPtr save_rating_request = dynamic_pointer_cast<TagReaderSaveRatingRequest>(request)) {
result = SaveSongRatingBlocking(save_rating_request->filename, save_rating_request->rating);
}
reply->set_result(result);
reply->Finish();
}
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) const {
Q_ASSERT(QThread::currentThread() != thread());
return tagreader_.IsMediaFile(filename).success();
}
TagReaderReplyPtr TagReaderClient::IsMediaFileAsync(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
TagReaderIsMediaFileRequestPtr request = TagReaderIsMediaFileRequest::Create(filename);
request->reply = reply;
request->filename = filename;
EnqueueRequest(request);
return reply;
}
TagReaderResult TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
return tagreader_.ReadFile(filename, song);
}
TagReaderReadFileReplyPtr TagReaderClient::ReadFileAsync(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderReadFileReplyPtr reply = TagReaderReply::Create<TagReaderReadFileReply>(filename);
TagReaderReadFileRequestPtr request = TagReaderReadFileRequest::Create(filename);
request->reply = reply;
request->filename = filename;
EnqueueRequest(request);
return reply;
}
TagReaderResult TagReaderClient::WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data);
}
TagReaderReplyPtr TagReaderClient::WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
TagReaderWriteFileRequestPtr request = TagReaderWriteFileRequest::Create(filename);
request->reply = reply;
request->filename = filename;
request->song = song;
request->save_tags_options = save_tags_options;
request->save_tag_cover_data = save_tag_cover_data;
EnqueueRequest(request);
return reply;
}
TagReaderResult TagReaderClient::LoadCoverDataBlocking(const QString &filename, QByteArray &data) {
return tagreader_.LoadEmbeddedCover(filename, data);
}
TagReaderResult TagReaderClient::LoadCoverImageBlocking(const QString &filename, QImage &image) {
QByteArray data;
TagReaderResult result = LoadCoverDataBlocking(filename, data);
if (result.error_code == TagReaderResult::ErrorCode::Success && !image.loadFromData(data)) {
result.error_code = TagReaderResult::ErrorCode::Unsupported;
result.error_text = QObject::tr("Failed to load image from data for %1").arg(filename);
}
return result;
}
TagReaderLoadCoverDataReplyPtr TagReaderClient::LoadCoverDataAsync(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderLoadCoverDataReplyPtr reply = TagReaderReply::Create<TagReaderLoadCoverDataReply>(filename);
TagReaderLoadCoverDataRequestPtr request = TagReaderLoadCoverDataRequest::Create(filename);
request->reply = reply;
request->filename = filename;
EnqueueRequest(request);
return reply;
}
TagReaderLoadCoverImageReplyPtr TagReaderClient::LoadCoverImageAsync(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderLoadCoverImageReplyPtr reply = TagReaderReply::Create<TagReaderLoadCoverImageReply>(filename);
TagReaderLoadCoverImageRequestPtr request = TagReaderLoadCoverImageRequest::Create(filename);
request->reply = reply;
request->filename = filename;
EnqueueRequest(request);
return reply;
}
TagReaderResult TagReaderClient::SaveCoverBlocking(const QString &filename, const SaveTagCoverData &save_tag_cover_data) {
return tagreader_.SaveEmbeddedCover(filename, save_tag_cover_data);
}
TagReaderReplyPtr TagReaderClient::SaveCoverAsync(const QString &filename, const SaveTagCoverData &save_tag_cover_data) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
TagReaderSaveCoverRequestPtr request = TagReaderSaveCoverRequest::Create(filename);
request->reply = reply;
request->filename = filename;
request->save_tag_cover_data = save_tag_cover_data;
EnqueueRequest(request);
return reply;
}
TagReaderReplyPtr TagReaderClient::SaveSongPlaycountAsync(const QString &filename, const uint playcount) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
TagReaderSavePlaycountRequestPtr request = TagReaderSavePlaycountRequest::Create(filename);
request->reply = reply;
request->filename = filename;
request->playcount = playcount;
EnqueueRequest(request);
return reply;
}
TagReaderResult TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) {
return tagreader_.SaveSongPlaycount(filename, playcount);
}
void TagReaderClient::SaveSongsPlaycountAsync(const SongList &songs) {
Q_ASSERT(QThread::currentThread() != thread());
for (const Song &song : songs) {
TagReaderReplyPtr reply = SaveSongPlaycountAsync(song.url().toLocalFile(), song.playcount());
QObject::connect(&*reply, &TagReaderReply::Finished, &*reply, &TagReaderReply::deleteLater);
}
}
TagReaderResult TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) {
return tagreader_.SaveSongRating(filename, rating);
}
TagReaderReplyPtr TagReaderClient::SaveSongRatingAsync(const QString &filename, const float rating) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
TagReaderSaveRatingRequestPtr request = TagReaderSaveRatingRequest::Create(filename);
request->reply = reply;
request->filename = filename;
request->rating = rating;
EnqueueRequest(request);
return reply;
}
void TagReaderClient::SaveSongsRatingAsync(const SongList &songs) {
Q_ASSERT(QThread::currentThread() != thread());
for (const Song &song : songs) {
TagReaderReplyPtr reply = SaveSongRatingAsync(song.url().toLocalFile(), song.rating());
QObject::connect(&*reply, &TagReaderReply::Finished, &*reply, &TagReaderReply::deleteLater);
}
}

View File

@@ -0,0 +1,117 @@
/*
* Strawberry Music Player
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERCLIENT_H
#define TAGREADERCLIENT_H
#include "config.h"
#include <QObject>
#include <QList>
#include <QQueue>
#include <QString>
#include <QImage>
#include <QMutex>
#include "core/shared_ptr.h"
#include "core/mutex_protected.h"
#include "core/song.h"
#include "tagreadertaglib.h"
#include "tagreadergme.h"
#include "tagreaderrequest.h"
#include "tagreaderresult.h"
#include "tagreaderreply.h"
#include "tagreaderreadfilereply.h"
#include "tagreaderloadcoverdatareply.h"
#include "tagreaderloadcoverimagereply.h"
#include "savetagsoptions.h"
#include "savetagcoverdata.h"
class QThread;
class Song;
class TagReaderClient : public QObject {
Q_OBJECT
public:
explicit TagReaderClient(QObject *parent = nullptr);
static TagReaderClient *Instance() { return sInstance; }
void Start();
void ExitAsync();
using SaveOption = SaveTagsOption;
using SaveOptions = SaveTagsOptions;
bool IsMediaFileBlocking(const QString &filename) const;
TagReaderReplyPtr IsMediaFileAsync(const QString &filename);
TagReaderResult ReadFileBlocking(const QString &filename, Song *song);
TagReaderReadFileReplyPtr ReadFileAsync(const QString &filename);
TagReaderResult WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
TagReaderReplyPtr WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
TagReaderResult LoadCoverDataBlocking(const QString &filename, QByteArray &data);
TagReaderResult LoadCoverImageBlocking(const QString &filename, QImage &image);
TagReaderLoadCoverDataReplyPtr LoadCoverDataAsync(const QString &filename);
TagReaderLoadCoverImageReplyPtr LoadCoverImageAsync(const QString &filename);
TagReaderResult SaveCoverBlocking(const QString &filename, const SaveTagCoverData &save_tag_cover_data);
TagReaderReplyPtr SaveCoverAsync(const QString &filename, const SaveTagCoverData &save_tag_cover_data);
TagReaderReplyPtr SaveSongPlaycountAsync(const QString &filename, const uint playcount);
TagReaderResult SaveSongPlaycountBlocking(const QString &filename, const uint playcount);
TagReaderReplyPtr SaveSongRatingAsync(const QString &filename, const float rating);
TagReaderResult SaveSongRatingBlocking(const QString &filename, const float rating);
private:
bool HaveRequests() const;
void EnqueueRequest(TagReaderRequestPtr request);
TagReaderRequestPtr DequeueRequest();
void ProcessRequestsAsync();
void ProcessRequest(TagReaderRequestPtr request);
Q_SIGNALS:
void ExitFinished();
private Q_SLOTS:
void Exit();
void ProcessRequests();
public Q_SLOTS:
void SaveSongsPlaycountAsync(const SongList &songs);
void SaveSongsRatingAsync(const SongList &songs);
private:
static TagReaderClient *sInstance;
QThread *original_thread_;
QQueue<TagReaderRequestPtr> requests_;
mutable QMutex mutex_requests_;
TagReaderTagLib tagreader_;
TagReaderGME gmereader_;
mutex_protected<bool> abort_;
mutex_protected<bool> processing_;
};
#endif // TAGREADERCLIENT_H

View File

@@ -0,0 +1,349 @@
/*
* Strawberry Music Player
* Copyright 2022, Eoin O'Neill <eoinoneill1991@gmail.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "tagreadergme.h"
#include <taglib/apefile.h>
#include <QByteArray>
#include <QString>
#include <QChar>
#include <QFileInfo>
#include <QFile>
#include <QTextStream>
#include "utilities/timeconstants.h"
#include "core/logging.h"
#include "tagreaderbase.h"
#include "tagreadertaglib.h"
using namespace Qt::StringLiterals;
#undef TStringToQString
#undef QStringToTString
bool GME::IsSupportedFormat(const QFileInfo &fileinfo) {
return fileinfo.exists() && (fileinfo.completeSuffix().endsWith("spc"_L1, Qt::CaseInsensitive) || fileinfo.completeSuffix().endsWith("vgm"_L1), Qt::CaseInsensitive);
}
TagReaderResult GME::ReadFile(const QFileInfo &fileinfo, Song *song) {
if (fileinfo.completeSuffix().endsWith("spc"_L1), Qt::CaseInsensitive) {
return SPC::Read(fileinfo, song);
}
if (fileinfo.completeSuffix().endsWith("vgm"_L1, Qt::CaseInsensitive)) {
return VGM::Read(fileinfo, song);
}
return TagReaderResult::ErrorCode::Unsupported;
}
quint32 GME::UnpackBytes32(const char *const bytes, size_t length) {
Q_ASSERT(length <= 4 && length > 0);
quint32 value = 0;
for (size_t i = 0; i < length; i++) {
value |= static_cast<unsigned char>(bytes[i]) << (8 * i);
}
return value;
}
TagReaderResult GME::SPC::Read(const QFileInfo &fileinfo, Song *song) {
QFile file(fileinfo.filePath());
if (!file.open(QIODevice::ReadOnly)) {
return TagReaderResult(TagReaderResult::ErrorCode::FileOpenError, file.errorString());
}
qLog(Debug) << "Reading tags from SPC file" << fileinfo.fileName();
// Check for header -- more reliable than file name alone.
if (!file.read(33).startsWith("SNES-SPC700")) {
return TagReaderResult::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,
// so they will likely be overwritten either by the id666 standard or the APETAG format (as used by other players, such as foobar and winamp)
//
// Make sure to check id6 documentation before changing the read values!
file.seek(HAS_ID6_OFFSET);
const QByteArray id6_status = file.read(1);
const bool has_id6 = id6_status.length() >= 1 && id6_status[0] == static_cast<char>(xID6_STATUS::ON);
file.seek(SONG_TITLE_OFFSET);
song->set_title(QString::fromLatin1(file.read(32)).toStdString());
file.seek(GAME_TITLE_OFFSET);
song->set_album(QString::fromLatin1(file.read(32)).toStdString());
file.seek(ARTIST_OFFSET);
song->set_artist(QString::fromLatin1(file.read(32)).toStdString());
file.seek(INTRO_LENGTH_OFFSET);
QByteArray length_bytes = file.read(INTRO_LENGTH_SIZE);
if (length_bytes.size() >= INTRO_LENGTH_SIZE) {
quint64 length_in_sec = ConvertSPCStringToNum(length_bytes);
if (!length_in_sec || length_in_sec >= 0x1FFF) {
// This means that parsing the length as a string failed, so get value LE.
length_in_sec = length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16);
}
if (length_in_sec < 0x1FFF) {
song->set_length_nanosec(static_cast<qint64>(length_in_sec * kNsecPerSec));
}
}
file.seek(FADE_LENGTH_OFFSET);
QByteArray fade_bytes = file.read(FADE_LENGTH_SIZE);
if (fade_bytes.size() >= FADE_LENGTH_SIZE) {
quint64 fade_length_in_ms = ConvertSPCStringToNum(fade_bytes);
if (fade_length_in_ms > 0x7FFF) {
fade_length_in_ms = fade_bytes[0] | (fade_bytes[1] << 8) | (fade_bytes[2] << 16) | (fade_bytes[3] << 24);
}
Q_UNUSED(fade_length_in_ms)
}
// Check for XID6 data -- this is infrequently used, but being able to fill in data from this is ideal before trying to rely on APETAG values.
// XID6 format follows EA's binary file format standard named "IFF"
file.seek(XID6_OFFSET);
if (has_id6 && file.read(4) == "xid6") {
QByteArray xid6_head_data = file.read(4);
if (xid6_head_data.size() >= 4) {
qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3];
// 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) << fileinfo.fileName() << "has ID6 tag.";
while ((file.pos()) + 4 < XID6_OFFSET + xid6_size) {
QByteArray arr = file.read(4);
if (arr.size() < 4) break;
qint8 id = arr[0];
qint8 type = arr[1];
Q_UNUSED(id);
Q_UNUSED(type);
qint16 length = static_cast<qint16>(arr[2] | (arr[3] << 8));
file.read(GetNextMemAddressAlign32bit(length));
}
}
}
// Music Players that support SPC tend to support additional tagging data as
// an APETAG entry at the bottom of the file instead of writing into the xid6 tagging space.
// This is where a lot of the extra data for a file is stored, such as genre or replaygain data.
// This data is currently supported by TagLib, so we will simply use that for the remaining values.
TagLib::APE::File ape(fileinfo.filePath().toStdString().data());
if (ape.hasAPETag()) {
TagLib::Tag *tag = ape.tag();
if (!tag) {
return TagReaderResult::ErrorCode::FileParseError;
}
song->set_artist(tag->artist());
song->set_album(tag->album());
song->set_title(tag->title());
song->set_genre(tag->genre());
song->set_track(static_cast<std::int32_t>(tag->track()));
song->set_year(static_cast<std::int32_t>(tag->year()));
}
song->set_valid(true);
song->set_filetype(Song::FileType::SPC);
return TagReaderResult::ErrorCode::Success;
}
qint16 GME::SPC::GetNextMemAddressAlign32bit(qint16 input) {
return static_cast<qint16>((input + 0x3) & ~0x3);
// Plus 0x3 for rounding up (not down), AND NOT to flatten out on a 32 bit level.
}
quint64 GME::SPC::ConvertSPCStringToNum(const QByteArray &arr) {
quint64 result = 0;
for (auto it = arr.begin(); it != arr.end(); it++) {
unsigned int num = *it - '0';
if (num > 9) break;
result = (result * 10) + num; // Shift Left and add.
}
return result;
}
TagReaderResult GME::VGM::Read(const QFileInfo &fileinfo, Song *song) {
QFile file(fileinfo.filePath());
if (!file.open(QIODevice::ReadOnly)) {
return TagReaderResult(TagReaderResult::ErrorCode::FileOpenError, file.errorString());
}
qLog(Debug) << "Reading tags from VGM file" << fileinfo.filePath();
if (!file.read(4).startsWith("Vgm ")) {
return TagReaderResult::ErrorCode::Unsupported;
}
file.seek(GD3_TAG_PTR);
QByteArray gd3_head = file.read(4);
if (gd3_head.size() < 4) {
return TagReaderResult::ErrorCode::FileParseError;
}
quint64 pt = GME::UnpackBytes32(gd3_head.constData(), gd3_head.size());
file.seek(SAMPLE_COUNT);
QByteArray sample_count_bytes = file.read(4);
file.seek(LOOP_SAMPLE_COUNT);
QByteArray loop_count_bytes = file.read(4);
quint64 length = 0;
if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) {
return TagReaderResult::ErrorCode::FileParseError;
}
file.seek(static_cast<qint64>(GD3_TAG_PTR + pt));
QByteArray gd3_version = file.read(4);
Q_UNUSED(gd3_version)
file.seek(file.pos() + 4);
QByteArray gd3_length_bytes = file.read(4);
quint32 gd3_length = GME::UnpackBytes32(gd3_length_bytes.constData(), gd3_length_bytes.size());
QByteArray gd3Data = file.read(gd3_length);
QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly);
// Stored as 16 bit UTF string, two bytes per letter.
fileTagStream.setEncoding(QStringConverter::Utf16);
QStringList strings = fileTagStream.readLine(0).split(u'\0');
if (strings.count() < 10) {
return TagReaderResult::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->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(static_cast<qint64>(length * kNsecPerMsec));
song->set_valid(true);
song->set_filetype(Song::FileType::VGM);
return TagReaderResult::ErrorCode::Success;
}
bool GME::VGM::GetPlaybackLength(const QByteArray &sample_count_bytes, const QByteArray &loop_count_bytes, quint64 &out_length) {
if (sample_count_bytes.size() != 4) return false;
if (loop_count_bytes.size() != 4) return false;
quint64 sample_count = GME::UnpackBytes32(sample_count_bytes.constData(), sample_count_bytes.size());
if (sample_count == 0) return false;
quint64 loop_sample_count = GME::UnpackBytes32(loop_count_bytes.constData(), loop_count_bytes.size());
if (loop_sample_count == 0) {
out_length = sample_count * 1000 / SAMPLE_TIMEBASE;
return true;
}
quint64 intro_length_ms = (sample_count - loop_sample_count) * 1000 / SAMPLE_TIMEBASE;
quint64 loop_length_ms = (loop_sample_count) * 1000 / SAMPLE_TIMEBASE;
out_length = intro_length_ms + (loop_length_ms * 2) + GST_GME_LOOP_TIME_MS;
return true;
}
TagReaderGME::TagReaderGME() = default;
TagReaderResult TagReaderGME::IsMediaFile(const QString &filename) const {
QFileInfo fileinfo(filename);
return GME::IsSupportedFormat(fileinfo) ? TagReaderResult::ErrorCode::Success : TagReaderResult::ErrorCode::Unsupported;
}
TagReaderResult TagReaderGME::ReadFile(const QString &filename, Song *song) const {
QFileInfo fileinfo(filename);
return GME::ReadFile(fileinfo, song);
}
TagReaderResult TagReaderGME::WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const {
Q_UNUSED(filename);
Q_UNUSED(song);
Q_UNUSED(save_tags_options);
Q_UNUSED(save_tag_cover_data);
return TagReaderResult::ErrorCode::Unsupported;
}
TagReaderResult TagReaderGME::LoadEmbeddedCover(const QString &filename, QByteArray &data) const {
Q_UNUSED(filename);
Q_UNUSED(data);
return TagReaderResult::ErrorCode::Unsupported;
}
TagReaderResult TagReaderGME::SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const {
Q_UNUSED(filename);
Q_UNUSED(save_tag_cover_data);
return TagReaderResult::ErrorCode::Unsupported;
}
TagReaderResult TagReaderGME::SaveSongPlaycount(const QString &filename, const uint playcount) const {
Q_UNUSED(filename);
Q_UNUSED(playcount);
return TagReaderResult::ErrorCode::Unsupported;
}
TagReaderResult TagReaderGME::SaveSongRating(const QString &filename, const float rating) const {
Q_UNUSED(filename);
Q_UNUSED(rating);
return TagReaderResult::ErrorCode::Unsupported;
}

View File

@@ -0,0 +1,114 @@
/*
* Strawberry Music Player
* Copyright 2022, Eoin O'Neill <eoinoneill1991@gmail.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERGME_H
#define TAGREADERGME_H
#include <QByteArray>
#include <QString>
#include <QFileInfo>
#include "tagreaderbase.h"
namespace GME {
bool IsSupportedFormat(const QFileInfo &fileinfo);
TagReaderResult ReadFile(const QFileInfo &fileinfo, Song *song);
uint32_t UnpackBytes32(const char *const bytes, size_t length);
namespace SPC {
// SPC SPEC: http://vspcplay.raphnet.net/spc_file_format.txt
constexpr int HAS_ID6_OFFSET = 0x23;
constexpr int SONG_TITLE_OFFSET = 0x2E;
constexpr int GAME_TITLE_OFFSET = 0x4E;
constexpr int DUMPER_OFFSET = 0x6E;
constexpr int COMMENTS_OFFSET = 0x7E;
// It seems that intro length and fade length are inconsistent from file to file.
// It should be looked into within the GME source code to see how GStreamer gets its values for playback length.
constexpr int INTRO_LENGTH_OFFSET = 0xA9;
constexpr int INTRO_LENGTH_SIZE = 3;
constexpr int FADE_LENGTH_OFFSET = 0xAC;
constexpr int FADE_LENGTH_SIZE = 4;
constexpr int ARTIST_OFFSET = 0xB1;
constexpr int XID6_OFFSET = (0x101C0 + 64);
constexpr int NANO_PER_MS = 1000000;
enum class xID6_STATUS {
ON = 0x26,
OFF = 0x27
};
enum class xID6_ID {
SongName = 0x01,
GameName = 0x02,
ArtistName = 0x03
};
enum class xID6_TYPE {
Length = 0x0,
String = 0x1,
Integer = 0x4
};
TagReaderResult Read(const QFileInfo &fileinfo, Song *song);
qint16 GetNextMemAddressAlign32bit(qint16 input);
quint64 ConvertSPCStringToNum(const QByteArray &arr);
} // namespace SPC
namespace VGM {
// VGM SPEC:
// http://www.smspower.org/uploads/Music/vgmspec170.txt?sid=17c810c54633b6dd4982f92f718361c1
// GD3 TAG SPEC:
// http://www.smspower.org/uploads/Music/gd3spec100.txt
constexpr int GD3_TAG_PTR = 0x14;
constexpr int SAMPLE_COUNT = 0x18;
constexpr int LOOP_SAMPLE_COUNT = 0x20;
constexpr int SAMPLE_TIMEBASE = 44100;
constexpr int GST_GME_LOOP_TIME_MS = 8000;
TagReaderResult Read(const QFileInfo &fileinfo, Song *song);
// Takes in two QByteArrays, expected to be 4 bytes long. Desired length is returned via output parameter out_length. Returns false on error.
bool GetPlaybackLength(const QByteArray &sample_count_bytes, const QByteArray &loop_count_bytes, quint64 &out_length);
} // namespace VGM
} // namespace GME
// TagReaderGME
// Fulfills Strawberry's Intended interface for tag reading.
class TagReaderGME : public TagReaderBase {
public:
explicit TagReaderGME();
TagReaderResult IsMediaFile(const QString &filename) const override;
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
TagReaderResult SaveSongPlaycount(const QString &filename, const uint playcount) const override;
TagReaderResult SaveSongRating(const QString &filename, const float rating) const override;
};
#endif // TAGREADERGME_H

View File

@@ -0,0 +1,24 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "tagreaderismediafilerequest.h"
TagReaderIsMediaFileRequest::TagReaderIsMediaFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}

View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERISMEDIAFILEREQUEST_H
#define TAGREADERISMEDIAFILEREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderrequest.h"
using std::make_shared;
class TagReaderIsMediaFileRequest : public TagReaderRequest {
public:
explicit TagReaderIsMediaFileRequest(const QString &_filename);
static SharedPtr<TagReaderIsMediaFileRequest> Create(const QString &filename) { return make_shared<TagReaderIsMediaFileRequest>(filename); }
};
using TagReaderIsMediaFileRequestPtr = std::shared_ptr<TagReaderIsMediaFileRequest>;
#endif // TAGREADERISMEDIAFILEREQUEST_H

View File

@@ -0,0 +1,40 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "core/logging.h"
#include "tagreaderloadcoverdatareply.h"
TagReaderLoadCoverDataReply::TagReaderLoadCoverDataReply(const QString &_filename, QObject *parent)
: TagReaderReply(_filename, parent) {}
void TagReaderLoadCoverDataReply::Finish() {
qLog(Debug) << "Finishing tagreader reply for" << filename_;
finished_ = true;
Q_EMIT TagReaderReply::Finished(filename_, result_);
Q_EMIT TagReaderLoadCoverDataReply::Finished(filename_, data_, result_);
QObject::disconnect(this, &TagReaderReply::Finished, nullptr, nullptr);
QObject::disconnect(this, &TagReaderLoadCoverDataReply::Finished, nullptr, nullptr);
}

View File

@@ -0,0 +1,51 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERLOADCOVERDATAREPLY_H
#define TAGREADERLOADCOVERDATAREPLY_H
#include <QByteArray>
#include <QString>
#include "core/shared_ptr.h"
#include "core/song.h"
#include "tagreaderreply.h"
#include "tagreaderresult.h"
class TagReaderLoadCoverDataReply : public TagReaderReply {
Q_OBJECT
public:
explicit TagReaderLoadCoverDataReply(const QString &_filename, QObject *parent = nullptr);
void Finish() override;
QByteArray data() const { return data_; }
void set_data(const QByteArray &data) { data_ = data; }
Q_SIGNALS:
void Finished(const QString &filename, const QByteArray &data, const TagReaderResult &result);
private:
QByteArray data_;
};
using TagReaderLoadCoverDataReplyPtr = SharedPtr<TagReaderLoadCoverDataReply>;
#endif // TAGREADERLOADCOVERDATAREPLY_H

View File

@@ -0,0 +1,24 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "tagreaderloadcoverdatarequest.h"
TagReaderLoadCoverDataRequest::TagReaderLoadCoverDataRequest(const QString &_filename) : TagReaderRequest(_filename) {}

View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERLOADCOVERDATAREQUEST_H
#define TAGREADERLOADCOVERDATAREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderrequest.h"
using std::make_shared;
class TagReaderLoadCoverDataRequest : public TagReaderRequest {
public:
explicit TagReaderLoadCoverDataRequest(const QString &_filename);
static SharedPtr<TagReaderLoadCoverDataRequest> Create(const QString &filename) { return make_shared<TagReaderLoadCoverDataRequest>(filename); }
};
using TagReaderLoadCoverDataRequestPtr = SharedPtr<TagReaderLoadCoverDataRequest>;
#endif // TAGREADERLOADCOVERDATAREQUEST_H

View File

@@ -0,0 +1,39 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "core/logging.h"
#include "tagreaderloadcoverimagereply.h"
TagReaderLoadCoverImageReply::TagReaderLoadCoverImageReply(const QString &_filename, QObject *parent)
: TagReaderReply(_filename, parent) {}
void TagReaderLoadCoverImageReply::Finish() {
qLog(Debug) << "Finishing tagreader reply for" << filename_;
finished_ = true;
Q_EMIT TagReaderReply::Finished(filename_, result_);
Q_EMIT TagReaderLoadCoverImageReply::Finished(filename_, image_, result_);
QObject::disconnect(this, &TagReaderLoadCoverImageReply::Finished, nullptr, nullptr);
}

View File

@@ -0,0 +1,51 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERLOADCOVERIMAGEREPLY_H
#define TAGREADERLOADCOVERIMAGEREPLY_H
#include <QString>
#include <QImage>
#include "core/shared_ptr.h"
#include "core/song.h"
#include "tagreaderreply.h"
#include "tagreaderresult.h"
class TagReaderLoadCoverImageReply : public TagReaderReply {
Q_OBJECT
public:
explicit TagReaderLoadCoverImageReply(const QString &_filename, QObject *parent = nullptr);
void Finish() override;
QImage image() const { return image_; }
void set_image(const QImage &image) { image_ = image; }
Q_SIGNALS:
void Finished(const QString &filename, const QImage &image, const TagReaderResult &result);
private:
QImage image_;
};
using TagReaderLoadCoverImageReplyPtr = SharedPtr<TagReaderLoadCoverImageReply>;
#endif // TAGREADERLOADCOVERIMAGEREPLY_H

View File

@@ -0,0 +1,24 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "tagreaderloadcoverimagerequest.h"
TagReaderLoadCoverImageRequest::TagReaderLoadCoverImageRequest(const QString &_filename) : TagReaderRequest(_filename) {}

View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERLOADCOVERIMAGEREQUEST_H
#define TAGREADERLOADCOVERIMAGEREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderrequest.h"
using std::make_shared;
class TagReaderLoadCoverImageRequest : public TagReaderRequest {
public:
explicit TagReaderLoadCoverImageRequest(const QString &_filename);
static SharedPtr<TagReaderLoadCoverImageRequest> Create(const QString &filename) { return make_shared<TagReaderLoadCoverImageRequest>(filename); }
};
using TagReaderLoadCoverImageRequestPtr = SharedPtr<TagReaderLoadCoverImageRequest>;
#endif // TAGREADERLOADEMBEDDEDCOVERASIMAGEREQUEST_H

View File

@@ -0,0 +1,39 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "core/logging.h"
#include "tagreaderreadfilereply.h"
TagReaderReadFileReply::TagReaderReadFileReply(const QString &_filename, QObject *parent)
: TagReaderReply(_filename, parent) {}
void TagReaderReadFileReply::Finish() {
qLog(Debug) << "Finishing tagreader reply for" << filename_;
finished_ = true;
Q_EMIT TagReaderReply::Finished(filename_, result_);
Q_EMIT TagReaderReadFileReply::Finished(filename_, song_, result_);
QObject::disconnect(this, &TagReaderReadFileReply::Finished, nullptr, nullptr);
}

View File

@@ -0,0 +1,50 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERREADFILEREPLY_H
#define TAGREADERREADFILEREPLY_H
#include <QString>
#include "core/shared_ptr.h"
#include "core/song.h"
#include "tagreaderreply.h"
#include "tagreaderresult.h"
class TagReaderReadFileReply : public TagReaderReply {
Q_OBJECT
public:
explicit TagReaderReadFileReply(const QString &_filename, QObject *parent = nullptr);
void Finish() override;
Song song() const { return song_; }
void set_song(const Song &song) { song_ = song; }
Q_SIGNALS:
void Finished(const QString &filename, const Song &song, const TagReaderResult &result);
private:
Song song_;
};
using TagReaderReadFileReplyPtr = SharedPtr<TagReaderReadFileReply>;
#endif // TAGREADERREADFILEREPLY_H

View File

@@ -0,0 +1,24 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "tagreaderreadfilerequest.h"
TagReaderReadFileRequest::TagReaderReadFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}

View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERREADFILEREQUEST_H
#define TAGREADERREADFILEREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderrequest.h"
using std::make_shared;
class TagReaderReadFileRequest : public TagReaderRequest {
public:
explicit TagReaderReadFileRequest(const QString &_filename);
static SharedPtr<TagReaderReadFileRequest> Create(const QString &filename) { return make_shared<TagReaderReadFileRequest>(filename); }
};
using TagReaderReadFileRequestPtr = SharedPtr<TagReaderReadFileRequest>;
#endif // TAGREADERREADFILEREQUEST_H

View File

@@ -0,0 +1,51 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "core/logging.h"
#include "tagreaderreply.h"
TagReaderReply::TagReaderReply(const QString &filename, QObject *parent)
: QObject(parent),
filename_(filename),
finished_(false) {
qLog(Debug) << "New tagreader reply for" << filename_;
}
TagReaderReply::~TagReaderReply() {
qLog(Debug) << "Deleting tagreader reply for" << filename_;
}
void TagReaderReply::Finish() {
qLog(Debug) << "Finishing tagreader reply for" << filename_;
finished_ = true;
Q_EMIT Finished(filename_, result_);
QObject::disconnect(this, &TagReaderReply::Finished, nullptr, nullptr);
}

View File

@@ -0,0 +1,67 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERREPLY_H
#define TAGREADERREPLY_H
#include <QObject>
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderresult.h"
class TagReaderReply : public QObject {
Q_OBJECT
public:
explicit TagReaderReply(const QString &filename, QObject *parent = nullptr);
virtual ~TagReaderReply() override;
template<typename T>
static SharedPtr<T> Create(const QString &filename) {
SharedPtr<T> reply;
reply.reset(new T(filename), [](QObject *obj) { obj->deleteLater(); });
return reply;
}
QString filename() const { return filename_; }
TagReaderResult result() const { return result_; }
void set_result(const TagReaderResult &result) { result_ = result; }
bool finished() const { return finished_; }
bool success() const { return result_.success(); }
QString error() const { return result_.error_string(); }
virtual void Finish();
Q_SIGNALS:
void Finished(const QString &filename, const TagReaderResult &result);
protected:
const QString filename_;
bool finished_;
TagReaderResult result_;
};
using TagReaderReplyPtr = SharedPtr<TagReaderReply>;
#endif // TAGREADERREPLY_H

View File

@@ -0,0 +1,34 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "core/logging.h"
#include "tagreaderrequest.h"
TagReaderRequest::TagReaderRequest(const QString &_filename) : filename(_filename) {
qLog(Debug) << "New tagreader request for" << filename;
}
TagReaderRequest::~TagReaderRequest() {
qLog(Debug) << "Deleting tagreader request for" << filename;
}

View File

@@ -0,0 +1,38 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERREQUEST_H
#define TAGREADERREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderreply.h"
class TagReaderRequest {
public:
explicit TagReaderRequest(const QString &_filename);
virtual ~TagReaderRequest();
QString filename;
TagReaderReplyPtr reply;
};
using TagReaderRequestPtr = SharedPtr<TagReaderRequest>;
#endif // TAGREADERREQUEST_H

View File

@@ -0,0 +1,47 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QObject>
#include "tagreaderresult.h"
QString TagReaderResult::error_string() const {
switch (error_code) {
case ErrorCode::Success:
return QObject::tr("Success");
case ErrorCode::Unsupported:
return QObject::tr("File is unsupported");
case ErrorCode::FilenameMissing:
return QObject::tr("Filename is missing");
case ErrorCode::FileDoesNotExist:
return QObject::tr("File does not exist");
case ErrorCode::FileOpenError:
return QObject::tr("File could not be opened");
case ErrorCode::FileParseError:
return QObject::tr("Could not parse file");
case ErrorCode::FileSaveError:
return QObject::tr("Could save file");
case ErrorCode::CustomError:
return error_text;
}
return QObject::tr("Unknown error");
}

View File

@@ -0,0 +1,44 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERRESULT_H
#define TAGREADERRESULT_H
#include <QString>
class TagReaderResult {
public:
enum class ErrorCode {
Success,
Unsupported,
FilenameMissing,
FileDoesNotExist,
FileOpenError,
FileParseError,
FileSaveError,
CustomError,
};
TagReaderResult(const ErrorCode _error_code = ErrorCode::Unsupported, const QString &_error_text = QString()) : error_code(_error_code), error_text(_error_text) {}
ErrorCode error_code;
QString error_text;
bool success() const { return error_code == ErrorCode::Success; }
QString error_string() const;
};
#endif // TAGREADERRESULT_H

View File

@@ -0,0 +1,24 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "tagreadersavecoverrequest.h"
TagReaderSaveCoverRequest::TagReaderSaveCoverRequest(const QString &_filename) : TagReaderRequest(_filename) {}

View File

@@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERSAVECOVERREQUEST_H
#define TAGREADERSAVECOVERREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderrequest.h"
#include "savetagcoverdata.h"
using std::make_shared;
class TagReaderSaveCoverRequest : public TagReaderRequest {
public:
explicit TagReaderSaveCoverRequest(const QString &_filename);
static SharedPtr<TagReaderSaveCoverRequest> Create(const QString &filename) { return make_shared<TagReaderSaveCoverRequest>(filename); }
SaveTagCoverData save_tag_cover_data;
};
using TagReaderSaveCoverRequestPtr = SharedPtr<TagReaderSaveCoverRequest>;
#endif // TAGREADERSAVECOVERREQUEST_H

View File

@@ -0,0 +1,24 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "tagreadersaveplaycountrequest.h"
TagReaderSavePlaycountRequest::TagReaderSavePlaycountRequest(const QString &_filename) : TagReaderRequest(_filename), playcount(0) {}

View File

@@ -0,0 +1,39 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERSAVEPLAYCOUNTREQUEST_H
#define TAGREADERSAVEPLAYCOUNTREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderrequest.h"
using std::make_shared;
class TagReaderSavePlaycountRequest : public TagReaderRequest {
public:
explicit TagReaderSavePlaycountRequest(const QString &_filename);
static SharedPtr<TagReaderSavePlaycountRequest> Create(const QString &filename) { return make_shared<TagReaderSavePlaycountRequest>(filename); }
uint playcount;
};
using TagReaderSavePlaycountRequestPtr = SharedPtr<TagReaderSavePlaycountRequest>;
#endif // TAGREADERSAVEPLAYCOUNTREQUEST_H

View File

@@ -0,0 +1,24 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "tagreadersaveratingrequest.h"
TagReaderSaveRatingRequest::TagReaderSaveRatingRequest(const QString &_filename) : TagReaderRequest(_filename), rating(0.0F) {}

View File

@@ -0,0 +1,39 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERSAVERATINGREQUEST_H
#define TAGREADERSAVERATINGREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "tagreaderrequest.h"
using std::make_shared;
class TagReaderSaveRatingRequest : public TagReaderRequest {
public:
explicit TagReaderSaveRatingRequest(const QString &_filename);
static SharedPtr<TagReaderSaveRatingRequest> Create(const QString &filename) { return make_shared<TagReaderSaveRatingRequest>(filename); }
float rating;
};
using TagReaderSaveRatingRequestPtr = SharedPtr<TagReaderSaveRatingRequest>;
#endif // TAGREADERSAVERATINGREQUEST_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
/*
* Strawberry Music Player
* Copyright 2013, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERTAGLIB_H
#define TAGREADERTAGLIB_H
#include "config.h"
#include <QByteArray>
#include <QString>
#include <taglib/tstring.h>
#include <taglib/fileref.h>
#include <taglib/xiphcomment.h>
#include <taglib/flacfile.h>
#include <taglib/mpegfile.h>
#include <taglib/mp4file.h>
#include <taglib/apetag.h>
#include <taglib/apefile.h>
#include <taglib/asffile.h>
#include <taglib/id3v2tag.h>
#include <taglib/popularimeterframe.h>
#include <taglib/mp4tag.h>
#include <taglib/asftag.h>
#include "core/song.h"
#include "tagreaderbase.h"
#include "savetagcoverdata.h"
#undef TStringToQString
#undef QStringToTString
class FileRefFactory;
class TagReaderTagLib : public TagReaderBase {
public:
explicit TagReaderTagLib();
~TagReaderTagLib() override;
static inline TagLib::String QStringToTagLibString(const QString &s) {
return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8);
}
static inline QString TagLibStringToQString(const TagLib::String &s) {
return QString::fromUtf8((s).toCString(true));
}
TagReaderResult IsMediaFile(const QString &filename) const override;
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
TagReaderResult SaveSongPlaycount(const QString &filename, const uint playcount) const override;
TagReaderResult SaveSongRating(const QString &filename, const float rating) const override;
private:
Song::FileType GuessFileType(TagLib::FileRef *fileref) const;
void ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, Song *song) const;
void ParseVorbisComments(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, Song *song) const;
void ParseAPETags(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, Song *song) const;
void ParseMP4Tags(TagLib::MP4::Tag *tag, QString *disc, QString *compilation, Song *song) const;
void ParseASFTags(TagLib::ASF::Tag *tag, QString *disc, QString *compilation, Song *song) const;
void ParseASFAttribute(const TagLib::ASF::AttributeListMap &attributes_map, const char *attribute, QString *str) const;
void SetID3v2Tag(TagLib::ID3v2::Tag *tag, const Song &song) const;
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
void SetUnsyncLyricsFrame(const QString &value, TagLib::ID3v2::Tag *tag) const;
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const Song &song) const;
void SetAPETag(TagLib::APE::Tag *tag, const Song &song) const;
void SetASFTag(TagLib::ASF::Tag *tag, const Song &song) const;
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const QString &value) const;
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const int value) const;
QByteArray LoadEmbeddedAPECover(const TagLib::APE::ItemListMap &map) const;
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
void SetPlaycount(TagLib::Ogg::XiphComment *vorbis_comment, const uint playcount) const;
void SetPlaycount(TagLib::APE::Tag *tag, const uint playcount) const;
void SetPlaycount(TagLib::ID3v2::Tag *tag, const uint playcount) const;
void SetPlaycount(TagLib::MP4::Tag *tag, const uint playcount) const;
void SetPlaycount(TagLib::ASF::Tag *tag, const uint playcount) const;
void SetRating(TagLib::Ogg::XiphComment *vorbis_comment, const float rating) const;
void SetRating(TagLib::APE::Tag *tag, const float rating) const;
void SetRating(TagLib::ID3v2::Tag *tag, const float rating) const;
void SetRating(TagLib::MP4::Tag *tag, const float rating) const;
void SetRating(TagLib::ASF::Tag *tag, const float rating) const;
void SetEmbeddedCover(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mimetype) const;
void SetEmbeddedCover(TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mimetype) const;
void SetEmbeddedCover(TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mimetype) const;
void SetEmbeddedCover(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mimetype) const;
static TagLib::String TagLibStringListToSlashSeparatedString(const TagLib::StringList &taglib_string_list);
private:
FileRefFactory *factory_;
Q_DISABLE_COPY(TagReaderTagLib)
};
#endif // TAGREADERTAGLIB_H

View File

@@ -0,0 +1,24 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "tagreaderwritefilerequest.h"
TagReaderWriteFileRequest::TagReaderWriteFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}

View File

@@ -0,0 +1,46 @@
/*
* Strawberry Music Player
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERWRITEFILEREQUEST_H
#define TAGREADERWRITEFILEREQUEST_H
#include <QString>
#include "core/shared_ptr.h"
#include "core/song.h"
#include "tagreaderrequest.h"
#include "savetagsoptions.h"
#include "savetagcoverdata.h"
using std::make_shared;
class TagReaderWriteFileRequest : public TagReaderRequest {
public:
explicit TagReaderWriteFileRequest(const QString &_filename);
static SharedPtr<TagReaderWriteFileRequest> Create(const QString &filename) { return make_shared<TagReaderWriteFileRequest>(filename); }
SaveTagsOptions save_tags_options;
Song song;
SaveTagCoverData save_tag_cover_data;
};
using TagReaderWriteFileRequestPtr = SharedPtr<TagReaderWriteFileRequest>;
#endif // TAGREADERWRITEFILEREQUEST_H

View File

@@ -394,10 +394,6 @@ msgstr ""
msgid "Couldn't link GStreamer source, typefind and fakesink elements for %1"
msgstr ""
#, qt-format
msgid "Failed to load image from data for %1"
msgstr ""
msgid "Playlist"
msgstr ""
@@ -444,6 +440,34 @@ msgid ""
"them all?"
msgstr ""
#, qt-format
msgid "Failed to load image from data for %1"
msgstr ""
msgid "Success"
msgstr ""
msgid "File is unsupported"
msgstr ""
msgid "Filename is missing"
msgstr ""
msgid "File does not exist"
msgstr ""
msgid "File could not be opened"
msgstr ""
msgid "Could not parse file"
msgstr ""
msgid "Could save file"
msgstr ""
msgid "Unknown error"
msgstr ""
msgid ""
"Prefix a search term with a field name to limit the search to that field, e."
"g.:"
@@ -843,11 +867,11 @@ msgid "Sample rate"
msgstr ""
#, qt-format
msgid "Could not write metadata to %1: %2"
msgid "Could not write metadata to %1"
msgstr ""
#, qt-format
msgid "Could not write metadata to %1"
msgid "Could not write metadata to %1: %2"
msgstr ""
msgid "Title"
@@ -2328,9 +2352,6 @@ msgstr ""
msgid "Retrieving album covers for %1 albums..."
msgstr ""
msgid "Unknown error"
msgstr ""
msgid "Configuration incomplete"
msgstr ""