Remove external tagreader
This commit is contained in:
@@ -5,6 +5,7 @@ if(HAVE_TRANSLATIONS)
|
||||
endif()
|
||||
|
||||
set(SOURCES
|
||||
core/logging.cpp
|
||||
core/mainwindow.cpp
|
||||
core/application.cpp
|
||||
core/player.cpp
|
||||
@@ -31,7 +32,6 @@ set(SOURCES
|
||||
core/songloader.cpp
|
||||
core/stylehelper.cpp
|
||||
core/stylesheetloader.cpp
|
||||
core/tagreaderclient.cpp
|
||||
core/taskmanager.cpp
|
||||
core/thread.cpp
|
||||
core/urlhandler.cpp
|
||||
@@ -63,6 +63,27 @@ set(SOURCES
|
||||
utilities/screenutils.cpp
|
||||
utilities/textencodingutils.cpp
|
||||
|
||||
tagreader/tagreaderclient.cpp
|
||||
tagreader/tagreaderresult.cpp
|
||||
tagreader/tagreaderbase.cpp
|
||||
tagreader/tagreadertaglib.cpp
|
||||
tagreader/tagreadergme.cpp
|
||||
tagreader/tagreaderrequest.cpp
|
||||
tagreader/tagreaderismediafilerequest.cpp
|
||||
tagreader/tagreaderreadfilerequest.cpp
|
||||
tagreader/tagreaderwritefilerequest.cpp
|
||||
tagreader/tagreaderloadcoverdatarequest.cpp
|
||||
tagreader/tagreaderloadcoverimagerequest.cpp
|
||||
tagreader/tagreadersavecoverrequest.cpp
|
||||
tagreader/tagreadersaveplaycountrequest.cpp
|
||||
tagreader/tagreadersaveratingrequest.cpp
|
||||
tagreader/albumcovertagdata.cpp
|
||||
tagreader/savetagcoverdata.cpp
|
||||
tagreader/tagreaderreply.cpp
|
||||
tagreader/tagreaderreadfilereply.cpp
|
||||
tagreader/tagreaderloadcoverdatareply.cpp
|
||||
tagreader/tagreaderloadcoverimagereply.cpp
|
||||
|
||||
filterparser/filterparser.cpp
|
||||
filterparser/filtertree.cpp
|
||||
|
||||
@@ -319,6 +340,7 @@ set(SOURCES
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
core/logging.h
|
||||
core/mainwindow.h
|
||||
core/application.h
|
||||
core/player.h
|
||||
@@ -333,7 +355,6 @@ set(HEADERS
|
||||
core/qtfslistener.h
|
||||
core/settings.h
|
||||
core/songloader.h
|
||||
core/tagreaderclient.h
|
||||
core/taskmanager.h
|
||||
core/thread.h
|
||||
core/urlhandler.h
|
||||
@@ -344,6 +365,12 @@ set(HEADERS
|
||||
core/stylesheetloader.h
|
||||
core/localredirectserver.h
|
||||
|
||||
tagreader/tagreaderclient.h
|
||||
tagreader/tagreaderreply.h
|
||||
tagreader/tagreaderreadfilereply.h
|
||||
tagreader/tagreaderloadcoverdatareply.h
|
||||
tagreader/tagreaderloadcoverimagereply.h
|
||||
|
||||
engine/enginebase.h
|
||||
engine/devicefinders.h
|
||||
|
||||
@@ -1059,8 +1086,8 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
${GOBJECT_INCLUDE_DIRS}
|
||||
${SQLITE_INCLUDE_DIRS}
|
||||
${PROTOBUF_INCLUDE_DIRS}
|
||||
${ICU_INCLUDE_DIRS}
|
||||
${TAGLIB_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||
@@ -1072,9 +1099,6 @@ target_include_directories(strawberry_lib PUBLIC
|
||||
${CMAKE_BINARY_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader
|
||||
${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader
|
||||
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
@@ -1083,9 +1107,9 @@ target_link_directories(strawberry_lib PUBLIC
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${SQLITE_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${ICU_LIBRARY_DIRS}
|
||||
${TAGLIB_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(strawberry_lib PUBLIC
|
||||
@@ -1094,16 +1118,14 @@ target_link_libraries(strawberry_lib PUBLIC
|
||||
${GOBJECT_LIBRARIES}
|
||||
${SQLITE_LIBRARIES}
|
||||
${ICU_LIBRARIES}
|
||||
${TAGLIB_LIBRARIES}
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
Qt${QT_VERSION_MAJOR}::Sql
|
||||
${Protobuf_LIBRARIES}
|
||||
${SINGLEAPPLICATION_LIBRARIES}
|
||||
libstrawberry-common
|
||||
libstrawberry-tagreader
|
||||
)
|
||||
|
||||
if(HAVE_DBUS)
|
||||
@@ -1268,9 +1290,6 @@ endif()
|
||||
|
||||
target_link_libraries(strawberry PRIVATE strawberry_lib)
|
||||
|
||||
# macdeploy.py relies on the blob being built first.
|
||||
add_dependencies(strawberry strawberry-tagreader)
|
||||
|
||||
if(NOT APPLE)
|
||||
install(TARGETS strawberry RUNTIME DESTINATION bin)
|
||||
endif()
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
#include "core/application.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/database.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/thread.h"
|
||||
#include "core/song.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/settings.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "utilities/threadutils.h"
|
||||
#include "collection.h"
|
||||
#include "collectionwatcher.h"
|
||||
@@ -216,7 +216,7 @@ void SCollection::SyncPlaycountAndRatingToFiles() {
|
||||
void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
|
||||
|
||||
if (save_tags || save_playcounts_to_files_) {
|
||||
app_->tag_reader_client()->SaveSongsPlaycount(songs);
|
||||
app_->tag_reader_client()->SaveSongsPlaycountAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -224,7 +224,7 @@ void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_t
|
||||
void SCollection::SongsRatingChanged(const SongList &songs, const bool save_tags) {
|
||||
|
||||
if (save_tags || save_ratings_to_files_) {
|
||||
app_->tag_reader_client()->SaveSongsRating(songs);
|
||||
app_->tag_reader_client()->SaveSongsRatingAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "collectionplaylistitem.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
|
||||
class SqlRow;
|
||||
|
||||
@@ -42,9 +42,9 @@ QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
||||
|
||||
void CollectionPlaylistItem::Reload() {
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not reload file" << song_.url() << result.error;
|
||||
qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
|
||||
return;
|
||||
}
|
||||
UpdateTemporaryMetadata(song_);
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
|
||||
#include "core/filesystemwatcherinterface.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/settings.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collectiondirectory.h"
|
||||
#include "collectionbackend.h"
|
||||
#include "collectionwatcher.h"
|
||||
@@ -866,7 +866,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||
}
|
||||
|
||||
Song song_on_disk(source_);
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
|
||||
if (result.success() && song_on_disk.is_valid()) {
|
||||
song_on_disk.set_source(source_);
|
||||
song_on_disk.set_directory_id(t->dir());
|
||||
@@ -919,7 +919,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
||||
}
|
||||
else { // It's a normal media file
|
||||
Song song(source_);
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(file, &song);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(file, &song);
|
||||
if (result.success() && song.is_valid()) {
|
||||
song.set_source(source_);
|
||||
PerformEBUR128Analysis(song);
|
||||
|
||||
@@ -37,10 +37,8 @@
|
||||
#cmakedefine HAVE_KEYSYMDEF_H
|
||||
#cmakedefine HAVE_XF86KEYSYM_H
|
||||
|
||||
#cmakedefine HAVE_TAGLIB
|
||||
#cmakedefine HAVE_TAGLIB_DSFFILE
|
||||
#cmakedefine HAVE_TAGLIB_DSDIFFFILE
|
||||
#cmakedefine HAVE_TAGPARSER
|
||||
|
||||
#cmakedefine USE_BUNDLE
|
||||
|
||||
|
||||
@@ -36,12 +36,11 @@
|
||||
|
||||
#include "shared_ptr.h"
|
||||
#include "lazy.h"
|
||||
#include "tagreaderclient.h"
|
||||
#include "database.h"
|
||||
#include "taskmanager.h"
|
||||
#include "player.h"
|
||||
#include "networkaccessmanager.h"
|
||||
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "engine/devicefinders.h"
|
||||
#ifndef Q_OS_WIN
|
||||
# include "device/devicemanager.h"
|
||||
@@ -118,7 +117,6 @@ class ApplicationImpl {
|
||||
tag_reader_client_([app](){
|
||||
TagReaderClient *client = new TagReaderClient();
|
||||
app->MoveToNewThread(client);
|
||||
client->Start();
|
||||
return client;
|
||||
}),
|
||||
database_([app]() {
|
||||
|
||||
439
src/core/logging.cpp
Normal file
439
src/core/logging.cpp
Normal 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
104
src/core/logging.h
Normal 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
|
||||
@@ -2177,21 +2177,20 @@ void MainWindow::RenumberTracks() {
|
||||
Song song = item->OriginalMetadata();
|
||||
if (song.IsEditable()) {
|
||||
song.set_track(track);
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
|
||||
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
|
||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||
}
|
||||
++track;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx) {
|
||||
void MainWindow::SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx) {
|
||||
|
||||
if (reply->is_successful() && idx.isValid()) {
|
||||
if (reply->success() && idx.isValid()) {
|
||||
app_->playlist_manager()->current()->ReloadItems(QList<int>() << idx.row());
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
}
|
||||
|
||||
@@ -2209,9 +2208,9 @@ void MainWindow::SelectionSetValue() {
|
||||
Song song = item->OriginalMetadata();
|
||||
if (!song.is_valid()) continue;
|
||||
if (song.url().isLocalFile() && Playlist::set_column_value(song, column, column_value)) {
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
|
||||
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
|
||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||
}
|
||||
else if (song.source() == Song::Source::Stream) {
|
||||
app_->playlist_manager()->current()->setData(source_index, column_value, 0);
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
#include "lazy.h"
|
||||
#include "platforminterface.h"
|
||||
#include "song.h"
|
||||
#include "tagreaderclient.h"
|
||||
#include "settings.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "osd/osdbase.h"
|
||||
#include "playlist/playlist.h"
|
||||
@@ -218,7 +218,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
|
||||
void PlayingWidgetPositionChanged(const bool above_status_bar);
|
||||
|
||||
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx);
|
||||
void SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx);
|
||||
|
||||
void ShowCoverManager();
|
||||
void ShowEqualizer();
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QSqlRecord>
|
||||
|
||||
#include <taglib/tstring.h>
|
||||
|
||||
#include "core/iconloader.h"
|
||||
#include "engine/enginemetadata.h"
|
||||
#include "utilities/strutils.h"
|
||||
@@ -55,13 +57,13 @@
|
||||
#include "utilities/coverutils.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "utilities/sqlhelper.h"
|
||||
|
||||
#include "song.h"
|
||||
#include "sqlquery.h"
|
||||
#include "sqlrow.h"
|
||||
#ifdef HAVE_DBUS
|
||||
# include "mpris_common.h"
|
||||
#endif
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
@@ -482,6 +484,29 @@ const QString &Song::musicbrainz_work_id() const { return d->musicbrainz_work_id
|
||||
std::optional<double> Song::ebur128_integrated_loudness_lufs() const { return d->ebur128_integrated_loudness_lufs_; }
|
||||
std::optional<double> Song::ebur128_loudness_range_lu() const { return d->ebur128_loudness_range_lu_; }
|
||||
|
||||
QString *Song::mutable_title() { return &d->title_; }
|
||||
QString *Song::mutable_album() { return &d->album_; }
|
||||
QString *Song::mutable_artist() { return &d->artist_; }
|
||||
QString *Song::mutable_albumartist() { return &d->albumartist_; }
|
||||
QString *Song::mutable_genre() { return &d->genre_; }
|
||||
QString *Song::mutable_composer() { return &d->composer_; }
|
||||
QString *Song::mutable_performer() { return &d->performer_; }
|
||||
QString *Song::mutable_grouping() { return &d->grouping_; }
|
||||
QString *Song::mutable_comment() { return &d->comment_; }
|
||||
QString *Song::mutable_lyrics() { return &d->lyrics_; }
|
||||
QString *Song::mutable_acoustid_id() { return &d->acoustid_id_; }
|
||||
QString *Song::mutable_acoustid_fingerprint() { return &d->acoustid_fingerprint_; }
|
||||
QString *Song::mutable_musicbrainz_album_artist_id() { return &d->musicbrainz_album_artist_id_; }
|
||||
QString *Song::mutable_musicbrainz_artist_id() { return &d->musicbrainz_artist_id_; }
|
||||
QString *Song::mutable_musicbrainz_original_artist_id() { return &d->musicbrainz_original_artist_id_; }
|
||||
QString *Song::mutable_musicbrainz_album_id() { return &d->musicbrainz_album_id_; }
|
||||
QString *Song::mutable_musicbrainz_original_album_id() { return &d->musicbrainz_original_album_id_; }
|
||||
QString *Song::mutable_musicbrainz_recording_id() { return &d->musicbrainz_recording_id_; }
|
||||
QString *Song::mutable_musicbrainz_track_id() { return &d->musicbrainz_track_id_; }
|
||||
QString *Song::mutable_musicbrainz_disc_id() { return &d->musicbrainz_disc_id_; }
|
||||
QString *Song::mutable_musicbrainz_release_group_id() { return &d->musicbrainz_release_group_id_; }
|
||||
QString *Song::mutable_musicbrainz_work_id() { return &d->musicbrainz_work_id_; }
|
||||
|
||||
bool Song::init_from_file() const { return d->init_from_file_; }
|
||||
|
||||
const QString &Song::title_sortable() const { return d->title_sortable_; }
|
||||
@@ -503,7 +528,7 @@ void Song::set_disc(const int v) { d->disc_ = v; }
|
||||
void Song::set_year(const int v) { d->year_ = v; }
|
||||
void Song::set_originalyear(const int v) { d->originalyear_ = v; }
|
||||
void Song::set_genre(const QString &v) { d->genre_ = v; }
|
||||
void Song::set_compilation(bool v) { d->compilation_ = v; }
|
||||
void Song::set_compilation(const bool v) { d->compilation_ = v; }
|
||||
void Song::set_composer(const QString &v) { d->composer_ = v; }
|
||||
void Song::set_performer(const QString &v) { d->performer_ = v; }
|
||||
void Song::set_grouping(const QString &v) { d->grouping_ = v; }
|
||||
@@ -571,6 +596,59 @@ void Song::set_ebur128_loudness_range_lu(const std::optional<double> v) { d->ebu
|
||||
|
||||
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
|
||||
|
||||
void Song::set_title(const TagLib::String &v) {
|
||||
|
||||
const QString title = TagLibStringToQString(v);
|
||||
d->title_sortable_ = sortable(title);
|
||||
d->title_ = title;
|
||||
|
||||
}
|
||||
|
||||
void Song::set_album(const TagLib::String &v) {
|
||||
|
||||
const QString album = TagLibStringToQString(v);
|
||||
d->album_sortable_ = sortable(album);
|
||||
d->album_ = album;
|
||||
|
||||
}
|
||||
void Song::set_artist(const TagLib::String &v) {
|
||||
|
||||
const QString artist = TagLibStringToQString(v);
|
||||
d->artist_sortable_ = sortable(artist);
|
||||
d->artist_ = artist;
|
||||
|
||||
}
|
||||
|
||||
void Song::set_albumartist(const TagLib::String &v) {
|
||||
|
||||
const QString albumartist = TagLibStringToQString(v);
|
||||
d->albumartist_sortable_ = sortable(albumartist);
|
||||
d->albumartist_ = albumartist;
|
||||
|
||||
}
|
||||
|
||||
void Song::set_genre(const TagLib::String &v) { d->genre_ = TagLibStringToQString(v); }
|
||||
void Song::set_composer(const TagLib::String &v) { d->composer_ = TagLibStringToQString(v); }
|
||||
void Song::set_performer(const TagLib::String &v) { d->performer_ = TagLibStringToQString(v); }
|
||||
void Song::set_grouping(const TagLib::String &v) { d->grouping_ = TagLibStringToQString(v); }
|
||||
void Song::set_comment(const TagLib::String &v) { d->comment_ = TagLibStringToQString(v); }
|
||||
void Song::set_lyrics(const TagLib::String &v) { d->lyrics_ = TagLibStringToQString(v); }
|
||||
void Song::set_artist_id(const TagLib::String &v) { d->artist_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_album_id(const TagLib::String &v) { d->album_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_song_id(const TagLib::String &v) { d->song_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_acoustid_id(const TagLib::String &v) { d->acoustid_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_acoustid_fingerprint(const TagLib::String &v) { d->acoustid_fingerprint_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_album_artist_id(const TagLib::String &v) { d->musicbrainz_album_artist_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_artist_id(const TagLib::String &v) { d->musicbrainz_artist_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_original_artist_id(const TagLib::String &v) { d->musicbrainz_original_artist_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_album_id(const TagLib::String &v) { d->musicbrainz_album_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_original_album_id(const TagLib::String &v) { d->musicbrainz_original_album_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_recording_id(const TagLib::String &v) { d->musicbrainz_recording_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_track_id(const TagLib::String &v) { d->musicbrainz_track_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_disc_id(const TagLib::String &v) { d->musicbrainz_disc_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_release_group_id(const TagLib::String &v) { d->musicbrainz_release_group_id_ = TagLibStringToQString(v); }
|
||||
void Song::set_musicbrainz_work_id(const TagLib::String &v) { d->musicbrainz_work_id_ = TagLibStringToQString(v); }
|
||||
|
||||
const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
|
||||
const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
|
||||
const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
|
||||
@@ -1312,125 +1390,6 @@ void Song::Init(const QString &title, const QString &artist, const QString &albu
|
||||
|
||||
}
|
||||
|
||||
void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
|
||||
|
||||
if (d->source_ == Source::Unknown) d->source_ = Source::LocalFile;
|
||||
|
||||
d->init_from_file_ = true;
|
||||
d->valid_ = pb.valid();
|
||||
set_title(QString::fromStdString(pb.title()));
|
||||
set_album(QString::fromStdString(pb.album()));
|
||||
set_artist(QString::fromStdString(pb.artist()));
|
||||
set_albumartist(QString::fromStdString(pb.albumartist()));
|
||||
d->track_ = pb.track();
|
||||
d->disc_ = pb.disc();
|
||||
d->year_ = pb.year();
|
||||
d->originalyear_ = pb.originalyear();
|
||||
d->genre_ = QString::fromStdString(pb.genre());
|
||||
d->compilation_ = pb.compilation();
|
||||
d->composer_ = QString::fromStdString(pb.composer());
|
||||
d->performer_ = QString::fromStdString(pb.performer());
|
||||
d->grouping_ = QString::fromStdString(pb.grouping());
|
||||
d->comment_ = QString::fromStdString(pb.comment());
|
||||
d->lyrics_ = QString::fromStdString(pb.lyrics());
|
||||
set_length_nanosec(static_cast<qint64>(pb.length_nanosec()));
|
||||
d->bitrate_ = pb.bitrate();
|
||||
d->samplerate_ = pb.samplerate();
|
||||
d->bitdepth_ = pb.bitdepth();
|
||||
set_url(QUrl::fromEncoded(QString::fromStdString(pb.url()).toUtf8()));
|
||||
d->basefilename_ = QString::fromStdString(pb.basefilename());
|
||||
d->filetype_ = static_cast<FileType>(pb.filetype());
|
||||
d->filesize_ = pb.filesize();
|
||||
d->mtime_ = pb.mtime();
|
||||
d->ctime_ = pb.ctime();
|
||||
d->skipcount_ = pb.skipcount();
|
||||
d->lastplayed_ = pb.lastplayed();
|
||||
d->lastseen_ = pb.lastseen();
|
||||
|
||||
if (pb.has_playcount()) {
|
||||
d->playcount_ = pb.playcount();
|
||||
}
|
||||
if (pb.has_rating()) {
|
||||
d->rating_ = pb.rating();
|
||||
}
|
||||
|
||||
d->art_embedded_ = pb.has_art_embedded();
|
||||
|
||||
d->acoustid_id_ = QString::fromStdString(pb.acoustid_id());
|
||||
d->acoustid_fingerprint_ = QString::fromStdString(pb.acoustid_fingerprint());
|
||||
|
||||
d->musicbrainz_album_artist_id_ = QString::fromStdString(pb.musicbrainz_album_artist_id());
|
||||
d->musicbrainz_artist_id_ = QString::fromStdString(pb.musicbrainz_artist_id().data());
|
||||
d->musicbrainz_original_artist_id_ = QString::fromStdString(pb.musicbrainz_original_artist_id());
|
||||
d->musicbrainz_album_id_ = QString::fromStdString(pb.musicbrainz_album_id());
|
||||
d->musicbrainz_original_album_id_ = QString::fromStdString(pb.musicbrainz_original_album_id());
|
||||
d->musicbrainz_recording_id_ = QString::fromStdString(pb.musicbrainz_recording_id());
|
||||
d->musicbrainz_track_id_ = QString::fromStdString(pb.musicbrainz_track_id());
|
||||
d->musicbrainz_disc_id_ = QString::fromStdString(pb.musicbrainz_disc_id());
|
||||
d->musicbrainz_release_group_id_ = QString::fromStdString(pb.musicbrainz_release_group_id());
|
||||
d->musicbrainz_work_id_ = QString::fromStdString(pb.musicbrainz_work_id());
|
||||
|
||||
d->suspicious_tags_ = pb.suspicious_tags();
|
||||
|
||||
InitArtManual();
|
||||
|
||||
}
|
||||
|
||||
void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
|
||||
|
||||
const QByteArray url(d->url_.toEncoded());
|
||||
|
||||
pb->set_valid(d->valid_);
|
||||
pb->set_title(d->title_.toStdString());
|
||||
pb->set_album(d->album_.toStdString());
|
||||
pb->set_artist(d->artist_.toStdString());
|
||||
pb->set_albumartist(d->albumartist_.toStdString());
|
||||
pb->set_track(d->track_);
|
||||
pb->set_disc(d->disc_);
|
||||
pb->set_year(d->year_);
|
||||
pb->set_originalyear(d->originalyear_);
|
||||
pb->set_genre(d->genre_.toStdString());
|
||||
pb->set_compilation(d->compilation_);
|
||||
pb->set_composer(d->composer_.toStdString());
|
||||
pb->set_performer(d->performer_.toStdString());
|
||||
pb->set_grouping(d->grouping_.toStdString());
|
||||
pb->set_comment(d->comment_.toStdString());
|
||||
pb->set_lyrics(d->lyrics_.toStdString());
|
||||
pb->set_length_nanosec(length_nanosec());
|
||||
pb->set_bitrate(d->bitrate_);
|
||||
pb->set_samplerate(d->samplerate_);
|
||||
pb->set_bitdepth(d->bitdepth_);
|
||||
pb->set_url(url.constData(), url.size());
|
||||
pb->set_basefilename(d->basefilename_.toStdString());
|
||||
pb->set_filetype(static_cast<spb::tagreader::SongMetadata_FileType>(d->filetype_));
|
||||
pb->set_filesize(d->filesize_);
|
||||
pb->set_mtime(d->mtime_);
|
||||
pb->set_ctime(d->ctime_);
|
||||
pb->set_playcount(d->playcount_);
|
||||
pb->set_skipcount(d->skipcount_);
|
||||
pb->set_lastplayed(d->lastplayed_);
|
||||
pb->set_lastseen(d->lastseen_);
|
||||
pb->set_art_embedded(d->art_embedded_);
|
||||
pb->set_rating(d->rating_);
|
||||
|
||||
pb->set_acoustid_id(d->acoustid_id_.toStdString());
|
||||
pb->set_acoustid_fingerprint(d->acoustid_fingerprint_.toStdString());
|
||||
|
||||
pb->set_musicbrainz_album_artist_id(d->musicbrainz_album_artist_id_.toStdString());
|
||||
pb->set_musicbrainz_artist_id(d->musicbrainz_artist_id_.toStdString());
|
||||
pb->set_musicbrainz_original_artist_id(d->musicbrainz_original_artist_id_.toStdString());
|
||||
pb->set_musicbrainz_album_id(d->musicbrainz_album_id_.toStdString());
|
||||
pb->set_musicbrainz_original_album_id(d->musicbrainz_original_album_id_.toStdString());
|
||||
pb->set_musicbrainz_recording_id(d->musicbrainz_recording_id_.toStdString());
|
||||
pb->set_musicbrainz_track_id(d->musicbrainz_track_id_.toStdString());
|
||||
pb->set_musicbrainz_disc_id(d->musicbrainz_disc_id_.toStdString());
|
||||
pb->set_musicbrainz_release_group_id(d->musicbrainz_release_group_id_.toStdString());
|
||||
pb->set_musicbrainz_work_id(d->musicbrainz_work_id_.toStdString());
|
||||
|
||||
pb->set_suspicious_tags(d->suspicious_tags_);
|
||||
|
||||
}
|
||||
|
||||
void Song::InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col) {
|
||||
|
||||
Q_ASSERT(kRowIdColumns.count() + col <= r.count());
|
||||
|
||||
@@ -41,17 +41,15 @@
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
|
||||
#include <taglib/tstring.h>
|
||||
#undef TStringToQString
|
||||
#undef QStringToTString
|
||||
|
||||
class SqlQuery;
|
||||
class QSqlRecord;
|
||||
|
||||
class EngineMetadata;
|
||||
|
||||
namespace spb {
|
||||
namespace tagreader {
|
||||
class SongMetadata;
|
||||
} // namespace tagreader
|
||||
} // namespace spb
|
||||
|
||||
#ifdef HAVE_LIBGPOD
|
||||
struct _Itdb_Track;
|
||||
#endif
|
||||
@@ -81,9 +79,6 @@ class Song {
|
||||
Spotify = 11
|
||||
};
|
||||
|
||||
// Don't change these values - they're stored in the database, and defined in the tag reader protobuf.
|
||||
// If a new lossless file is added, also add it to IsFileLossless().
|
||||
|
||||
enum class FileType {
|
||||
Unknown = 0,
|
||||
WAV = 1,
|
||||
@@ -227,6 +222,29 @@ class Song {
|
||||
std::optional<double> ebur128_integrated_loudness_lufs() const;
|
||||
std::optional<double> ebur128_loudness_range_lu() const;
|
||||
|
||||
QString *mutable_title();
|
||||
QString *mutable_album();
|
||||
QString *mutable_artist();
|
||||
QString *mutable_albumartist();
|
||||
QString *mutable_genre();
|
||||
QString *mutable_composer();
|
||||
QString *mutable_performer();
|
||||
QString *mutable_grouping();
|
||||
QString *mutable_comment();
|
||||
QString *mutable_lyrics();
|
||||
QString *mutable_acoustid_id();
|
||||
QString *mutable_acoustid_fingerprint();
|
||||
QString *mutable_musicbrainz_album_artist_id();
|
||||
QString *mutable_musicbrainz_artist_id();
|
||||
QString *mutable_musicbrainz_original_artist_id();
|
||||
QString *mutable_musicbrainz_album_id();
|
||||
QString *mutable_musicbrainz_original_album_id();
|
||||
QString *mutable_musicbrainz_recording_id();
|
||||
QString *mutable_musicbrainz_track_id();
|
||||
QString *mutable_musicbrainz_disc_id();
|
||||
QString *mutable_musicbrainz_release_group_id();
|
||||
QString *mutable_musicbrainz_work_id();
|
||||
|
||||
bool init_from_file() const;
|
||||
|
||||
const QString &title_sortable() const;
|
||||
@@ -317,6 +335,32 @@ class Song {
|
||||
|
||||
void set_stream_url(const QUrl &v);
|
||||
|
||||
void set_title(const TagLib::String &v);
|
||||
void set_album(const TagLib::String &v);
|
||||
void set_artist(const TagLib::String &v);
|
||||
void set_albumartist(const TagLib::String &v);
|
||||
void set_genre(const TagLib::String &v);
|
||||
void set_composer(const TagLib::String &v);
|
||||
void set_performer(const TagLib::String &v);
|
||||
void set_grouping(const TagLib::String &v);
|
||||
void set_comment(const TagLib::String &v);
|
||||
void set_lyrics(const TagLib::String &v);
|
||||
void set_artist_id(const TagLib::String &v);
|
||||
void set_album_id(const TagLib::String &v);
|
||||
void set_song_id(const TagLib::String &v);
|
||||
void set_acoustid_id(const TagLib::String &v);
|
||||
void set_acoustid_fingerprint(const TagLib::String &v);
|
||||
void set_musicbrainz_album_artist_id(const TagLib::String &v);
|
||||
void set_musicbrainz_artist_id(const TagLib::String &v);
|
||||
void set_musicbrainz_original_artist_id(const TagLib::String &v);
|
||||
void set_musicbrainz_album_id(const TagLib::String &v);
|
||||
void set_musicbrainz_original_album_id(const TagLib::String &v);
|
||||
void set_musicbrainz_recording_id(const TagLib::String &v);
|
||||
void set_musicbrainz_track_id(const TagLib::String &v);
|
||||
void set_musicbrainz_disc_id(const TagLib::String &v);
|
||||
void set_musicbrainz_release_group_id(const TagLib::String &v);
|
||||
void set_musicbrainz_work_id(const TagLib::String &v);
|
||||
|
||||
const QUrl &effective_stream_url() const;
|
||||
const QString &effective_albumartist() const;
|
||||
const QString &effective_albumartist_sortable() const;
|
||||
@@ -422,7 +466,6 @@ class Song {
|
||||
// Constructors
|
||||
void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec);
|
||||
void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end);
|
||||
void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
|
||||
void InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col = 0);
|
||||
void InitFromQuery(const SqlQuery &query, const bool reliable_metadata, const int col = 0);
|
||||
void InitFromQuery(const SqlRow &row, const bool reliable_metadata, const int col = 0);
|
||||
@@ -445,7 +488,6 @@ class Song {
|
||||
#ifdef HAVE_DBUS
|
||||
void ToXesam(QVariantMap *map) const;
|
||||
#endif
|
||||
void ToProtobuf(spb::tagreader::SongMetadata *pb) const;
|
||||
|
||||
bool MergeFromEngineMetadata(const EngineMetadata &engine_metadata);
|
||||
|
||||
@@ -465,6 +507,10 @@ class Song {
|
||||
static QString AlbumRemoveDiscMisc(const QString &album);
|
||||
static QString TitleRemoveMisc(const QString &title);
|
||||
|
||||
static inline QString TagLibStringToQString(const TagLib::String &s) {
|
||||
return QString::fromUtf8((s).toCString(true));
|
||||
}
|
||||
|
||||
private:
|
||||
struct Private;
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
#include "player.h"
|
||||
#include "song.h"
|
||||
#include "songloader.h"
|
||||
#include "tagreaderclient.h"
|
||||
#include "database.h"
|
||||
#include "sqlrow.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "playlistparsers/cueparser.h"
|
||||
@@ -362,9 +362,9 @@ void SongLoader::EffectiveSongLoad(Song *song) {
|
||||
else {
|
||||
// It's a normal media file
|
||||
const QString filename = song->url().toLocalFile();
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not read file" << song->url() << result.error;
|
||||
qLog(Error) << "Could not read file" << song->url() << result.error_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/workerpool.h"
|
||||
|
||||
#include "song.h"
|
||||
#include "tagreaderclient.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace {
|
||||
constexpr char kWorkerExecutableName[] = "strawberry-tagreader";
|
||||
}
|
||||
|
||||
TagReaderClient *TagReaderClient::sInstance = nullptr;
|
||||
|
||||
TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool<HandlerType>(this)) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
sInstance = this;
|
||||
original_thread_ = thread();
|
||||
|
||||
worker_pool_->SetExecutableName(QLatin1String(kWorkerExecutableName));
|
||||
QObject::connect(worker_pool_, &WorkerPool<HandlerType>::WorkerFailedToStart, this, &TagReaderClient::WorkerFailedToStart);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::Start() { worker_pool_->Start(); }
|
||||
|
||||
void TagReaderClient::ExitAsync() {
|
||||
QMetaObject::invokeMethod(this, &TagReaderClient::Exit, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void TagReaderClient::Exit() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
moveToThread(original_thread_);
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::WorkerFailedToStart() {
|
||||
qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it.";
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
message.mutable_is_media_file_request()->set_filename(filename.toStdString());
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
message.mutable_read_file_request()->set_filename(filename.toStdString());
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::WriteFile(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::WriteFileRequest *request = message.mutable_write_file_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
|
||||
request->set_save_tags(save_types.testFlag(SaveType::Tags));
|
||||
request->set_save_playcount(save_types.testFlag(SaveType::PlayCount));
|
||||
request->set_save_rating(save_types.testFlag(SaveType::Rating));
|
||||
request->set_save_cover(save_types.testFlag(SaveType::Cover));
|
||||
|
||||
if (!save_cover_options.cover_filename.isEmpty()) {
|
||||
request->set_cover_filename(save_cover_options.cover_filename.toStdString());
|
||||
}
|
||||
if (!save_cover_options.cover_data.isEmpty()) {
|
||||
request->set_cover_data(save_cover_options.cover_data.toStdString());
|
||||
}
|
||||
if (!save_cover_options.mime_type.isEmpty()) {
|
||||
request->set_cover_mime_type(save_cover_options.mime_type.toStdString());
|
||||
}
|
||||
|
||||
metadata.ToProtobuf(request->mutable_metadata());
|
||||
|
||||
ReplyType *reply = worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::LoadEmbeddedArtRequest *request = message.mutable_load_embedded_art_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::SaveEmbeddedArtRequest *request = message.mutable_save_embedded_art_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
|
||||
if (!save_cover_options.cover_filename.isEmpty()) {
|
||||
request->set_cover_filename(save_cover_options.cover_filename.toStdString());
|
||||
}
|
||||
if (!save_cover_options.cover_data.isEmpty()) {
|
||||
request->set_cover_data(save_cover_options.cover_data.toStdString());
|
||||
}
|
||||
if (!save_cover_options.mime_type.isEmpty()) {
|
||||
request->set_cover_mime_type(save_cover_options.mime_type.toStdString());
|
||||
}
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::SaveSongPlaycount(const QString &filename, const uint playcount) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::SaveSongPlaycountToFileRequest *request = message.mutable_save_song_playcount_to_file_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
request->set_playcount(playcount);
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::SaveSongsPlaycount(const SongList &songs) {
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReply *reply = SaveSongPlaycount(song.url().toLocalFile(), song.playcount());
|
||||
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::SaveSongRating(const QString &filename, const float rating) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::SaveSongRatingToFileRequest *request = message.mutable_save_song_rating_to_file_request();
|
||||
|
||||
request->set_filename(filename.toStdString());
|
||||
request->set_rating(rating);
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::SaveSongsRating(const SongList &songs) {
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReply *reply = SaveSongRating(song.url().toLocalFile(), song.rating());
|
||||
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool success = false;
|
||||
|
||||
TagReaderReply *reply = IsMediaFile(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::IsMediaFileResponse &response = reply->message().is_media_file_response();
|
||||
if (response.has_success()) {
|
||||
success = response.success();
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return success;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = ReadFile(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::ReadFileResponse &response = reply->message().read_file_response();
|
||||
if (response.has_success()) {
|
||||
if (response.success()) {
|
||||
result.error_code = Result::ErrorCode::Success;
|
||||
if (response.has_metadata()) {
|
||||
song->InitFromProtobuf(response.metadata());
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.error_code = Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = WriteFile(filename, metadata, save_types, save_cover_options);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::WriteFileResponse &response = reply->message().write_file_response();
|
||||
if (response.has_success()) {
|
||||
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = LoadEmbeddedArt(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::LoadEmbeddedArtResponse &response = reply->message().load_embedded_art_response();
|
||||
if (response.has_success()) {
|
||||
if (response.success()) {
|
||||
result.error_code = Result::ErrorCode::Success;
|
||||
if (response.has_data()) {
|
||||
data = QByteArray(response.data().data(), static_cast<qint64>(response.data().size()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.error_code = Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
QByteArray data;
|
||||
Result result = LoadEmbeddedArtBlocking(filename, data);
|
||||
if (result.error_code == Result::ErrorCode::Success && !image.loadFromData(data)) {
|
||||
result.error_code = Result::ErrorCode::Failure;
|
||||
result.error = QObject::tr("Failed to load image from data for %1").arg(filename);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = SaveEmbeddedArt(filename, save_cover_options);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::SaveEmbeddedArtResponse &response = reply->message().save_embedded_art_response();
|
||||
if (response.has_success()) {
|
||||
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = SaveSongPlaycount(filename, playcount);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::SaveSongPlaycountToFileResponse &response = reply->message().save_song_playcount_to_file_response();
|
||||
if (response.has_success()) {
|
||||
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderClient::Result TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
Result result(Result::ErrorCode::Failure);
|
||||
|
||||
TagReaderReply *reply = SaveSongRating(filename, rating);
|
||||
if (reply->WaitForFinished()) {
|
||||
const spb::tagreader::SaveSongRatingToFileResponse &response = reply->message().save_song_rating_to_file_response();
|
||||
if (response.has_success()) {
|
||||
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
|
||||
if (response.has_error()) {
|
||||
result.error = QString::fromStdString(response.error());
|
||||
}
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERCLIENT_H
|
||||
#define TAGREADERCLIENT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include "core/messagehandler.h"
|
||||
#include "core/workerpool.h"
|
||||
|
||||
#include "song.h"
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
class QThread;
|
||||
class Song;
|
||||
template<typename HandlerType> class WorkerPool;
|
||||
|
||||
class TagReaderClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderClient(QObject *parent = nullptr);
|
||||
|
||||
using HandlerType = AbstractMessageHandler<spb::tagreader::Message>;
|
||||
using ReplyType = HandlerType::ReplyType;
|
||||
|
||||
void Start();
|
||||
void ExitAsync();
|
||||
|
||||
enum class SaveType {
|
||||
NoType = 0,
|
||||
Tags = 1,
|
||||
PlayCount = 2,
|
||||
Rating = 4,
|
||||
Cover = 8
|
||||
};
|
||||
Q_DECLARE_FLAGS(SaveTypes, SaveType)
|
||||
|
||||
class SaveCoverOptions {
|
||||
public:
|
||||
explicit SaveCoverOptions(const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray(), const QString &_mime_type = QString()) : cover_filename(_cover_filename), cover_data(_cover_data), mime_type(_mime_type) {}
|
||||
explicit SaveCoverOptions(const QString &_cover_filename, const QString &_mime_type = QString()) : cover_filename(_cover_filename), mime_type(_mime_type) {}
|
||||
explicit SaveCoverOptions(const QByteArray &_cover_data, const QString &_mime_type = QString()) : cover_data(_cover_data), mime_type(_mime_type) {}
|
||||
QString cover_filename;
|
||||
QByteArray cover_data;
|
||||
QString mime_type;
|
||||
};
|
||||
|
||||
class Result {
|
||||
public:
|
||||
enum class ErrorCode {
|
||||
Success,
|
||||
Unsupported,
|
||||
Failure,
|
||||
};
|
||||
Result(const ErrorCode _error_code, const QString &_error = QString()) : error_code(_error_code), error(_error) {}
|
||||
ErrorCode error_code;
|
||||
QString error;
|
||||
bool success() const { return error_code == TagReaderClient::Result::ErrorCode::Success; }
|
||||
};
|
||||
|
||||
class Cover {
|
||||
public:
|
||||
explicit Cover(const QByteArray &_data = QByteArray(), const QString &_mime_type = QString()) : data(_data), mime_type(_mime_type) {}
|
||||
QByteArray data;
|
||||
QString mime_type;
|
||||
QString error;
|
||||
};
|
||||
|
||||
ReplyType *IsMediaFile(const QString &filename);
|
||||
ReplyType *ReadFile(const QString &filename);
|
||||
ReplyType *WriteFile(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
|
||||
ReplyType *LoadEmbeddedArt(const QString &filename);
|
||||
ReplyType *SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options);
|
||||
ReplyType *SaveSongPlaycount(const QString &filename, const uint playcount);
|
||||
ReplyType *SaveSongRating(const QString &filename, const float rating);
|
||||
|
||||
// Convenience functions that call the above functions and wait for a response.
|
||||
// These block the calling thread with a semaphore, and must NOT be called from the TagReaderClient's thread.
|
||||
Result ReadFileBlocking(const QString &filename, Song *song);
|
||||
Result WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
|
||||
bool IsMediaFileBlocking(const QString &filename);
|
||||
Result LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data);
|
||||
Result LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image);
|
||||
Result SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options);
|
||||
Result SaveSongPlaycountBlocking(const QString &filename, const uint playcount);
|
||||
Result SaveSongRatingBlocking(const QString &filename, const float rating);
|
||||
|
||||
// TODO: Make this not a singleton
|
||||
static TagReaderClient *Instance() { return sInstance; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void ExitFinished();
|
||||
|
||||
private Q_SLOTS:
|
||||
void Exit();
|
||||
void WorkerFailedToStart();
|
||||
|
||||
public Q_SLOTS:
|
||||
void SaveSongsPlaycount(const SongList &songs);
|
||||
void SaveSongsRating(const SongList &songs);
|
||||
|
||||
private:
|
||||
static TagReaderClient *sInstance;
|
||||
|
||||
WorkerPool<HandlerType> *worker_pool_;
|
||||
QList<spb::tagreader::Message> message_queue_;
|
||||
QThread *original_thread_;
|
||||
};
|
||||
|
||||
using TagReaderReply = TagReaderClient::ReplyType;
|
||||
|
||||
#endif // TAGREADERCLIENT_H
|
||||
@@ -63,9 +63,8 @@
|
||||
#include "core/application.h"
|
||||
#include "core/song.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionfilteroptions.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "settings/coverssettingspage.h"
|
||||
@@ -447,7 +446,7 @@ void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:{
|
||||
if (song.art_embedded() && !song.url().isEmpty() && song.url().isValid() && song.url().isLocalFile()) {
|
||||
QImage image_embedded_cover;
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song.url().toLocalFile(), image_embedded_cover);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song.url().toLocalFile(), image_embedded_cover);
|
||||
if (result.success() && !image_embedded_cover.isNull()) {
|
||||
QPixmap pixmap = QPixmap::fromImage(image_embedded_cover);
|
||||
if (!pixmap.isNull()) {
|
||||
@@ -737,8 +736,8 @@ void AlbumCoverChoiceController::SaveCoverEmbeddedToSong(const Song &song, const
|
||||
QMutexLocker l(&mutex_cover_save_tasks_);
|
||||
cover_save_tasks_.append(song);
|
||||
const bool art_embedded = !image_data.isNull();
|
||||
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(song.url().toLocalFile(), TagReaderClient::SaveCoverOptions(cover_filename, image_data, mime_type));
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, song, art_embedded]() { SaveEmbeddedCoverFinished(reply, song, art_embedded); });
|
||||
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(song.url().toLocalFile(), SaveTagCoverData(cover_filename, image_data, mime_type));
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, song, art_embedded]() { SaveEmbeddedCoverFinished(reply, song, art_embedded); });
|
||||
|
||||
}
|
||||
|
||||
@@ -815,12 +814,12 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded) {
|
||||
void AlbumCoverChoiceController::SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, Song song, const bool art_embedded) {
|
||||
|
||||
if (!cover_save_tasks_.contains(song)) return;
|
||||
cover_save_tasks_.removeAll(song);
|
||||
|
||||
if (reply->is_successful()) {
|
||||
if (reply->success()) {
|
||||
SaveArtEmbeddedToSong(&song, art_embedded);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
#include <QMutex>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "utilities/coveroptions.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
@@ -160,7 +160,7 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
|
||||
private Q_SLOTS:
|
||||
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
|
||||
void SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded);
|
||||
void SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, Song song, const bool art_embedded);
|
||||
|
||||
Q_SIGNALS:
|
||||
void Error(const QString &error);
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
#include "core/logging.h"
|
||||
#include "core/networkaccessmanager.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "utilities/mimeutils.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
@@ -318,7 +318,7 @@ AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, cons
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadEmbeddedImage(TaskPtr task) {
|
||||
|
||||
if (task->art_embedded && task->song_url.isValid() && task->song_url.isLocalFile()) {
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile(), task->album_cover.image_data);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverDataBlocking(task->song_url.toLocalFile(), task->album_cover.image_data);
|
||||
if (result.success() && !task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
|
||||
return LoadImageResult(AlbumCoverLoaderResult::Type::Embedded, LoadImageResult::Status::Success);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
#include "core/logging.h"
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/database.h"
|
||||
#include "core/sqlrow.h"
|
||||
#include "core/settings.h"
|
||||
@@ -77,6 +76,7 @@
|
||||
#include "utilities/screenutils.h"
|
||||
#include "widgets/forcescrollperpixel.h"
|
||||
#include "widgets/searchfield.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
@@ -758,9 +758,9 @@ void AlbumCoverManager::SaveCoverToFile() {
|
||||
return;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song.art_embedded()) {
|
||||
const TagReaderClient::Result tagreaderclient_result = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile(), result.image_data);
|
||||
const TagReaderResult tagreaderclient_result = TagReaderClient::Instance()->LoadCoverDataBlocking(song.url().toLocalFile(), result.image_data);
|
||||
if (!tagreaderclient_result.success()) {
|
||||
qLog(Error) << "Could not load embedded art from" << song.url() << tagreaderclient_result.error;
|
||||
qLog(Error) << "Could not load embedded art from" << song.url() << tagreaderclient_result.error_string();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -845,8 +845,8 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
|
||||
case CoverOptions::CoverType::Embedded:{
|
||||
for (const QUrl &url : std::as_const(album_item->urls)) {
|
||||
const bool art_embedded = !result.image_data.isEmpty();
|
||||
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.image_data, result.mime_type));
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(url.toLocalFile(), SaveTagCoverData(result.image_data, result.mime_type));
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
|
||||
});
|
||||
cover_save_tasks_.insert(album_item, url);
|
||||
@@ -995,8 +995,8 @@ void AlbumCoverManager::SaveAndSetCover(AlbumItem *album_item, const AlbumCoverI
|
||||
if (album_cover_choice_controller_->get_save_album_cover_type() == CoverOptions::CoverType::Embedded && Song::save_embedded_cover_supported(filetype) && !has_cue) {
|
||||
for (const QUrl &url : urls) {
|
||||
const bool art_embedded = !result.image_data.isEmpty();
|
||||
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.cover_url.isValid() ? result.cover_url.toLocalFile() : QString(), result.image_data, result.mime_type));
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
TagReaderReplyPtr reply = app_->tag_reader_client()->SaveCoverAsync(url.toLocalFile(), SaveTagCoverData(result.cover_url.isValid() ? result.cover_url.toLocalFile() : QString(), result.image_data, result.mime_type));
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
|
||||
});
|
||||
cover_save_tasks_.insert(album_item, url);
|
||||
@@ -1094,13 +1094,13 @@ bool AlbumCoverManager::ItemHasCover(const AlbumItem &album_item) const {
|
||||
return album_item.icon().cacheKey() != icon_nocover_item_.cacheKey();
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded) {
|
||||
void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded) {
|
||||
|
||||
if (cover_save_tasks_.contains(album_item, url)) {
|
||||
cover_save_tasks_.remove(album_item, url);
|
||||
}
|
||||
|
||||
if (!reply->is_successful()) {
|
||||
if (!reply->success()) {
|
||||
Q_EMIT Error(tr("Could not save cover to file %1.").arg(url.toLocalFile()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverchoicecontroller.h"
|
||||
@@ -185,7 +185,7 @@ class AlbumCoverManager : public QMainWindow {
|
||||
void UpdateCoverInList(AlbumItem *album_item, const QUrl &cover);
|
||||
void UpdateExportStatus(const int exported, const int skipped, const int max);
|
||||
|
||||
void SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded);
|
||||
void SaveEmbeddedCoverFinished(TagReaderReplyPtr reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded);
|
||||
|
||||
private:
|
||||
Ui_CoverManager *ui_;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <QImage>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverexport.h"
|
||||
#include "coverexportrunnable.h"
|
||||
@@ -78,7 +78,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song_.art_embedded() && dialog_result_.export_embedded_) {
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile(), image);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song_.url().toLocalFile(), image);
|
||||
if (result.success() && !image.isNull()) {
|
||||
extension = "jpg"_L1;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ void CoverExportRunnable::ExportCover() {
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song_.art_embedded() && dialog_result_.export_embedded_) {
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile(), image);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(song_.url().toLocalFile(), image);
|
||||
if (result.success() && !image.isNull()) {
|
||||
embedded_cover = true;
|
||||
extension = "jpg"_L1;
|
||||
|
||||
@@ -73,13 +73,13 @@
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/settings.h"
|
||||
#include "utilities/strutils.h"
|
||||
#include "utilities/timeutils.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "utilities/coverutils.h"
|
||||
#include "utilities/coveroptions.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "widgets/busyindicator.h"
|
||||
#include "widgets/lineedit.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
@@ -99,7 +99,6 @@
|
||||
#include "covermanager/albumcoverimageresult.h"
|
||||
#include "edittagdialog.h"
|
||||
#include "ui_edittagdialog.h"
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
@@ -401,7 +400,7 @@ QList<EditTagDialog::Data> EditTagDialog::LoadData(const SongList &songs) {
|
||||
if (song.IsEditable()) {
|
||||
// Try reloading the tags from file
|
||||
Song copy(song);
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), ©);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), ©);
|
||||
if (result.success() && copy.is_valid()) {
|
||||
copy.MergeUserSetData(song, false, false);
|
||||
ret << Data(copy);
|
||||
@@ -1276,31 +1275,31 @@ void EditTagDialog::SaveData() {
|
||||
if (ref.current_.originalyear() <= 0) { ref.current_.set_originalyear(-1); }
|
||||
if (ref.current_.lastplayed() <= 0) { ref.current_.set_lastplayed(-1); }
|
||||
++save_tag_pending_;
|
||||
TagReaderClient::SaveCoverOptions savecover_options;
|
||||
SaveTagCoverData save_tag_cover_data;
|
||||
if (save_embedded_cover && ref.cover_action_ == UpdateCoverAction::New) {
|
||||
if (!ref.cover_result_.image.isNull()) {
|
||||
savecover_options.mime_type = ref.cover_result_.mime_type;
|
||||
save_tag_cover_data.cover_mimetype = ref.cover_result_.mime_type;
|
||||
}
|
||||
else if (!embedded_cover_from_file.isEmpty()) {
|
||||
savecover_options.cover_filename = embedded_cover_from_file;
|
||||
save_tag_cover_data.cover_filename = embedded_cover_from_file;
|
||||
}
|
||||
savecover_options.cover_data = ref.cover_result_.image_data;
|
||||
save_tag_cover_data.cover_data = ref.cover_result_.image_data;
|
||||
}
|
||||
TagReaderClient::SaveTypes save_types;
|
||||
TagReaderClient::SaveOptions save_tags_options;
|
||||
if (save_tags) {
|
||||
save_types |= TagReaderClient::SaveType::Tags;
|
||||
save_tags_options |= TagReaderClient::SaveOption::Tags;
|
||||
}
|
||||
if (save_playcount) {
|
||||
save_types |= TagReaderClient::SaveType::PlayCount;
|
||||
save_tags_options |= TagReaderClient::SaveOption::Playcount;
|
||||
}
|
||||
if (save_rating) {
|
||||
save_types |= TagReaderClient::SaveType::Rating;
|
||||
save_tags_options |= TagReaderClient::SaveOption::Rating;
|
||||
}
|
||||
if (save_embedded_cover) {
|
||||
save_types |= TagReaderClient::SaveType::Cover;
|
||||
save_tags_options |= TagReaderClient::SaveOption::Cover;
|
||||
}
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(ref.current_.url().toLocalFile(), ref.current_, save_types, savecover_options);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_); }, Qt::QueuedConnection);
|
||||
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(ref.current_.url().toLocalFile(), ref.current_, save_tags_options, save_tag_cover_data);
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_); }, Qt::QueuedConnection);
|
||||
}
|
||||
// If the cover was changed, but no tags written, make sure to update the collection.
|
||||
else if (ref.cover_action_ != UpdateCoverAction::None && !ref.current_.effective_albumartist().isEmpty() && !ref.current_.album().isEmpty()) {
|
||||
@@ -1451,15 +1450,12 @@ void EditTagDialog::UpdateLyrics(const quint64 id, const QString &provider, cons
|
||||
|
||||
}
|
||||
|
||||
void EditTagDialog::SongSaveTagsComplete(TagReaderReply *reply, const QString &filename, Song song, const UpdateCoverAction cover_action) {
|
||||
void EditTagDialog::SongSaveTagsComplete(TagReaderReplyPtr reply, const QString &filename, Song song, const UpdateCoverAction cover_action) {
|
||||
|
||||
--save_tag_pending_;
|
||||
const bool success = reply->message().write_file_response().success();
|
||||
QString error;
|
||||
if (!success && reply->message().write_file_response().has_error()) {
|
||||
error = QString::fromStdString(reply->message().write_file_response().error());
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
const bool success = reply->success();
|
||||
const QString error = reply->error();
|
||||
|
||||
if (success) {
|
||||
if (song.is_collection_song()) {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
#include <QImage>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
@@ -133,7 +133,7 @@ class EditTagDialog : public QDialog {
|
||||
void PreviousSong();
|
||||
void NextSong();
|
||||
|
||||
void SongSaveTagsComplete(TagReaderReply *reply, const QString &filename, Song song, const UpdateCoverAction cover_action);
|
||||
void SongSaveTagsComplete(TagReaderReplyPtr reply, const QString &filename, Song song, const UpdateCoverAction cover_action);
|
||||
|
||||
private:
|
||||
struct FieldData {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "widgets/busyindicator.h"
|
||||
#include "trackselectiondialog.h"
|
||||
#include "ui_trackselectiondialog.h"
|
||||
@@ -278,9 +278,9 @@ void TrackSelectionDialog::SaveData(const QList<Data> &data) {
|
||||
copy.set_track(new_metadata.track());
|
||||
copy.set_year(new_metadata.year());
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->WriteFileBlocking(copy.url().toLocalFile(), copy, TagReaderClient::SaveType::Tags, TagReaderClient::SaveCoverOptions());
|
||||
const TagReaderResult result = TagReaderClient::Instance()->WriteFileBlocking(copy.url().toLocalFile(), copy, TagReaderClient::SaveOption::Tags, SaveTagCoverData());
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Failed to write new auto-tags to" << copy.url().toLocalFile() << result.error;
|
||||
qLog(Error) << "Failed to write new auto-tags to" << copy.url().toLocalFile() << result.error_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/song.h"
|
||||
#include "utilities/strutils.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "organize.h"
|
||||
#ifdef HAVE_GSTREAMER
|
||||
# include "transcoder/transcoder.h"
|
||||
@@ -245,9 +245,9 @@ void Organize::ProcessSomeFiles() {
|
||||
}
|
||||
}
|
||||
else if (destination_->source() == Song::Source::Device) {
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(task.song_info_.song_.url().toLocalFile(), job.cover_image_);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->LoadCoverImageBlocking(task.song_info_.song_.url().toLocalFile(), job.cover_image_);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not load embedded art from" << task.song_info_.song_.url() << result.error;
|
||||
qLog(Error) << "Could not load embedded art from" << task.song_info_.song_.url() << result.error_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,12 +59,12 @@
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/settings.h"
|
||||
#include "utilities/strutils.h"
|
||||
#include "utilities/screenutils.h"
|
||||
#include "widgets/freespacebar.h"
|
||||
#include "widgets/linetextedit.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "organize.h"
|
||||
#include "organizeformat.h"
|
||||
@@ -423,12 +423,12 @@ SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
|
||||
if (result.success() && song.is_valid()) {
|
||||
songs << song;
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Could not read file" << filename << result.error;
|
||||
qLog(Error) << "Could not read file" << filename << result.error_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/song.h"
|
||||
#include "core/settings.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collection.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionplaylistitem.h"
|
||||
@@ -418,9 +418,9 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
|
||||
if (!set_column_value(song, static_cast<Column>(idx.column()), value)) return false;
|
||||
|
||||
if (song.url().isLocalFile()) {
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
|
||||
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
|
||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(idx);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index, item]() { SongSaveComplete(reply, persistent_index, item->OriginalMetadata()); }, Qt::QueuedConnection);
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index, item]() { SongSaveComplete(reply, persistent_index, item->OriginalMetadata()); }, Qt::QueuedConnection);
|
||||
}
|
||||
else if (song.is_radio()) {
|
||||
item->SetMetadata(song);
|
||||
@@ -431,18 +431,18 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
|
||||
|
||||
}
|
||||
|
||||
void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata) {
|
||||
void Playlist::SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx, const Song &old_metadata) {
|
||||
|
||||
if (reply->is_successful() && idx.isValid()) {
|
||||
if (reply->message().write_file_response().success()) {
|
||||
if (reply->success() && idx.isValid()) {
|
||||
if (reply->success()) {
|
||||
ItemReload(idx, old_metadata, true);
|
||||
}
|
||||
else {
|
||||
if (reply->request_message().write_file_response().has_error()) {
|
||||
Q_EMIT Error(tr("Could not write metadata to %1: %2").arg(QString::fromStdString(reply->request_message().write_file_request().filename()), QString::fromStdString(reply->request_message().write_file_response().error())));
|
||||
if (reply->error().isEmpty()) {
|
||||
Q_EMIT Error(tr("Could not write metadata to %1").arg(reply->filename()));
|
||||
}
|
||||
else {
|
||||
Q_EMIT Error(tr("Could not write metadata to %1").arg(QString::fromStdString(reply->request_message().write_file_request().filename())));
|
||||
Q_EMIT Error(tr("Could not write metadata to %1: %2").arg(reply->filename(), reply->error()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "playlistitem.h"
|
||||
#include "playlistsequence.h"
|
||||
@@ -350,7 +350,7 @@ class Playlist : public QAbstractListModel {
|
||||
void TracksDequeued();
|
||||
void TracksEnqueued(const QModelIndex &parent_idx, const int begin, const int end);
|
||||
void QueueLayoutChanged();
|
||||
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx, const Song &old_metadata);
|
||||
void SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx, const Song &old_metadata);
|
||||
void ItemReloadComplete(const QPersistentModelIndex &idx, const Song &old_metadata, const bool metadata_edit);
|
||||
void ItemsLoaded();
|
||||
void ScheduleSave();
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
#include "core/sqlrow.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "playlistitem.h"
|
||||
#include "songplaylistitem.h"
|
||||
|
||||
@@ -44,9 +44,9 @@ void SongPlaylistItem::Reload() {
|
||||
|
||||
if (!song_.url().isLocalFile()) return;
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not reload file" << song_.url() << result.error;
|
||||
qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
|
||||
}
|
||||
|
||||
UpdateTemporaryMetadata(song_);
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "settings/playlistsettingspage.h"
|
||||
#include "parserbase.h"
|
||||
@@ -105,9 +105,9 @@ void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning
|
||||
}
|
||||
}
|
||||
|
||||
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
|
||||
if (!result.success()) {
|
||||
qLog(Error) << "Could not read file" << filename << result.error;
|
||||
qLog(Error) << "Could not read file" << filename << result.error_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
27
src/tagreader/albumcovertagdata.cpp
Normal file
27
src/tagreader/albumcovertagdata.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "albumcovertagdata.h"
|
||||
|
||||
AlbumCoverTagData::AlbumCoverTagData(const QByteArray &_data, const QString &_mimetype)
|
||||
: data(_data),
|
||||
mimetype(_mimetype) {}
|
||||
34
src/tagreader/albumcovertagdata.h
Normal file
34
src/tagreader/albumcovertagdata.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERTAGDATA_H
|
||||
#define ALBUMCOVERTAGDATA_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
class AlbumCoverTagData {
|
||||
public:
|
||||
explicit AlbumCoverTagData(const QByteArray &_data = QByteArray(), const QString &_mimetype = QString());
|
||||
QByteArray data;
|
||||
QString mimetype;
|
||||
QString error;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERTAGDATA_H
|
||||
32
src/tagreader/savetagcoverdata.cpp
Normal file
32
src/tagreader/savetagcoverdata.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
SaveTagCoverData::SaveTagCoverData(const QString &_cover_filename, const QByteArray &_cover_data, const QString &_cover_mimetype)
|
||||
: cover_filename(_cover_filename),
|
||||
cover_data(_cover_data),
|
||||
cover_mimetype(_cover_mimetype) {}
|
||||
|
||||
SaveTagCoverData::SaveTagCoverData(const QByteArray &_cover_data, const QString &_cover_mimetype)
|
||||
: cover_data(_cover_data),
|
||||
cover_mimetype(_cover_mimetype) {}
|
||||
35
src/tagreader/savetagcoverdata.h
Normal file
35
src/tagreader/savetagcoverdata.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SAVETAGCOVERDATA_H
|
||||
#define SAVETAGCOVERDATA_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
class SaveTagCoverData {
|
||||
public:
|
||||
SaveTagCoverData(const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray(), const QString &_cover_mimetype = QString());
|
||||
SaveTagCoverData(const QByteArray &_cover_data, const QString &_cover_mimetype = QString());
|
||||
QString cover_filename;
|
||||
QByteArray cover_data;
|
||||
QString cover_mimetype;
|
||||
};
|
||||
|
||||
#endif // SAVETAGCOVERDATA_H
|
||||
34
src/tagreader/savetagsoptions.h
Normal file
34
src/tagreader/savetagsoptions.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SAVETAGSOPTIONS_H
|
||||
#define SAVETAGSOPTIONS_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
enum class SaveTagsOption {
|
||||
NoType = 0,
|
||||
Tags = 1,
|
||||
Playcount = 2,
|
||||
Rating = 4,
|
||||
Cover = 8
|
||||
};
|
||||
Q_DECLARE_FLAGS(SaveTagsOptions, SaveTagsOption)
|
||||
|
||||
#endif // SAVETAGSOPTIONS_H
|
||||
110
src/tagreader/tagreaderbase.cpp
Normal file
110
src/tagreader/tagreaderbase.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
#include <QBuffer>
|
||||
#include <QImage>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderbase.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
TagReaderBase::TagReaderBase() = default;
|
||||
TagReaderBase::~TagReaderBase() = default;
|
||||
|
||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||
|
||||
if (POPM_rating < 0x01) return 0.0F;
|
||||
if (POPM_rating < 0x40) return 0.20F;
|
||||
if (POPM_rating < 0x80) return 0.40F;
|
||||
if (POPM_rating < 0xC0) return 0.60F;
|
||||
if (POPM_rating < 0xFC) return 0.80F;
|
||||
|
||||
return 1.0F;
|
||||
|
||||
}
|
||||
|
||||
int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
||||
|
||||
if (rating < 0.20) return 0x00;
|
||||
if (rating < 0.40) return 0x01;
|
||||
if (rating < 0.60) return 0x40;
|
||||
if (rating < 0.80) return 0x80;
|
||||
if (rating < 1.0) return 0xC0;
|
||||
|
||||
return 0xFF;
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverTagData TagReaderBase::LoadAlbumCoverTagData(const QString &song_filename, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
const QString &cover_filename = save_tag_cover_data.cover_filename;
|
||||
QByteArray cover_data = save_tag_cover_data.cover_data;
|
||||
QString cover_mimetype = save_tag_cover_data.cover_mimetype;
|
||||
|
||||
if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
|
||||
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
|
||||
QFile file(cover_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
|
||||
return AlbumCoverTagData();
|
||||
}
|
||||
cover_data = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (!cover_data.isEmpty()) {
|
||||
if (cover_mimetype.isEmpty()) {
|
||||
cover_mimetype = QMimeDatabase().mimeTypeForData(cover_data).name();
|
||||
}
|
||||
if (cover_mimetype == "image/jpeg"_L1) {
|
||||
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||
return AlbumCoverTagData(cover_data, cover_mimetype);
|
||||
}
|
||||
if (cover_mimetype == "image/png"_L1) {
|
||||
qLog(Debug) << "Using cover from PNG data for" << song_filename;
|
||||
return AlbumCoverTagData(cover_data, cover_mimetype);
|
||||
}
|
||||
// Convert image to JPEG.
|
||||
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
|
||||
QImage cover_image;
|
||||
if (!cover_image.loadFromData(cover_data)) {
|
||||
qLog(Error) << "Failed to load image from cover data for" << song_filename;
|
||||
return AlbumCoverTagData();
|
||||
}
|
||||
cover_data.clear();
|
||||
QBuffer buffer(&cover_data);
|
||||
if (buffer.open(QIODevice::WriteOnly)) {
|
||||
cover_image.save(&buffer, "JPEG");
|
||||
buffer.close();
|
||||
}
|
||||
return AlbumCoverTagData(cover_data, u"image/jpeg"_s);
|
||||
}
|
||||
|
||||
return AlbumCoverTagData();
|
||||
|
||||
}
|
||||
61
src/tagreader/tagreaderbase.h
Normal file
61
src/tagreader/tagreaderbase.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERBASE_H
|
||||
#define TAGREADERBASE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreaderresult.h"
|
||||
#include "savetagsoptions.h"
|
||||
#include "savetagcoverdata.h"
|
||||
#include "albumcovertagdata.h"
|
||||
|
||||
class TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderBase();
|
||||
virtual ~TagReaderBase();
|
||||
|
||||
virtual TagReaderResult IsMediaFile(const QString &filename) const = 0;
|
||||
|
||||
virtual TagReaderResult ReadFile(const QString &filename, Song *song) const = 0;
|
||||
virtual TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const = 0;
|
||||
|
||||
virtual TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const = 0;
|
||||
virtual TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const = 0;
|
||||
|
||||
virtual TagReaderResult SaveSongPlaycount(const QString &filename, const uint playcount) const = 0;
|
||||
virtual TagReaderResult SaveSongRating(const QString &filename, const float rating) const = 0;
|
||||
|
||||
protected:
|
||||
static float ConvertPOPMRating(const int POPM_rating);
|
||||
static int ConvertToPOPMRating(const float rating);
|
||||
|
||||
static AlbumCoverTagData LoadAlbumCoverTagData(const QString &song_filename, const SaveTagCoverData &save_tag_cover_data);
|
||||
|
||||
Q_DISABLE_COPY(TagReaderBase)
|
||||
};
|
||||
|
||||
#endif // TAGREADERBASE_H
|
||||
423
src/tagreader/tagreaderclient.cpp
Normal file
423
src/tagreader/tagreaderclient.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreaderclient.h"
|
||||
#include "tagreadertaglib.h"
|
||||
#include "tagreaderresult.h"
|
||||
#include "tagreaderrequest.h"
|
||||
#include "tagreaderismediafilerequest.h"
|
||||
#include "tagreaderreadfilerequest.h"
|
||||
#include "tagreaderwritefilerequest.h"
|
||||
#include "tagreaderloadcoverdatarequest.h"
|
||||
#include "tagreaderloadcoverimagerequest.h"
|
||||
#include "tagreadersavecoverrequest.h"
|
||||
#include "tagreadersaveplaycountrequest.h"
|
||||
#include "tagreadersaveratingrequest.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderreadfilereply.h"
|
||||
#include "tagreaderloadcoverdatareply.h"
|
||||
#include "tagreaderloadcoverimagereply.h"
|
||||
|
||||
using std::dynamic_pointer_cast;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
TagReaderClient *TagReaderClient::sInstance = nullptr;
|
||||
|
||||
TagReaderClient::TagReaderClient(QObject *parent)
|
||||
: QObject(parent),
|
||||
original_thread_(thread()),
|
||||
abort_(false),
|
||||
processing_(false) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
|
||||
sInstance = this;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ExitAsync() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
abort_ = true;
|
||||
QMetaObject::invokeMethod(this, &TagReaderClient::Exit, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::Exit() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
moveToThread(original_thread_);
|
||||
Q_EMIT ExitFinished();
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::HaveRequests() const {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_requests_);
|
||||
return !requests_.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::EnqueueRequest(TagReaderRequestPtr request) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_requests_);
|
||||
requests_.enqueue(request);
|
||||
}
|
||||
|
||||
if (!processing_.value()) {
|
||||
ProcessRequestsAsync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TagReaderRequestPtr TagReaderClient::DequeueRequest() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_requests_);
|
||||
if (requests_.isEmpty()) return TagReaderRequestPtr();
|
||||
return requests_.dequeue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ProcessRequestsAsync() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
QMetaObject::invokeMethod(this, &TagReaderClient::ProcessRequests, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ProcessRequests() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
processing_ = true;
|
||||
|
||||
const QScopeGuard scopeguard_processing = qScopeGuard([this]() {
|
||||
processing_ = false;
|
||||
});
|
||||
|
||||
while (HaveRequests()) {
|
||||
if (abort_.value()) return;
|
||||
ProcessRequest(DequeueRequest());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ProcessRequest(TagReaderRequestPtr request) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
TagReaderReplyPtr reply = request->reply;
|
||||
|
||||
TagReaderResult result;
|
||||
|
||||
if (TagReaderIsMediaFileRequestPtr is_media_file_request = std::dynamic_pointer_cast<TagReaderIsMediaFileRequest>(request)) {
|
||||
result = tagreader_.IsMediaFile(is_media_file_request->filename);
|
||||
if (result.error_code == TagReaderResult::ErrorCode::Unsupported) {
|
||||
result = gmereader_.IsMediaFile(is_media_file_request->filename);
|
||||
}
|
||||
}
|
||||
else if (TagReaderReadFileRequestPtr read_file_request = std::dynamic_pointer_cast<TagReaderReadFileRequest>(request)) {
|
||||
Song song;
|
||||
result = ReadFileBlocking(read_file_request->filename, &song);
|
||||
if (result.error_code == TagReaderResult::ErrorCode::Unsupported) {
|
||||
result = gmereader_.ReadFile(read_file_request->filename, &song);
|
||||
}
|
||||
if (result.success()) {
|
||||
if (TagReaderReadFileReplyPtr read_file_reply = std::dynamic_pointer_cast<TagReaderReadFileReply>(reply)) {
|
||||
read_file_reply->set_song(song);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TagReaderWriteFileRequestPtr write_file_request = dynamic_pointer_cast<TagReaderWriteFileRequest>(request)) {
|
||||
result = WriteFileBlocking(write_file_request->filename, write_file_request->song, write_file_request->save_tags_options, write_file_request->save_tag_cover_data);
|
||||
}
|
||||
else if (TagReaderLoadCoverDataRequestPtr load_cover_data_request = dynamic_pointer_cast<TagReaderLoadCoverDataRequest>(request)) {
|
||||
QByteArray cover_data;
|
||||
result = LoadCoverDataBlocking(load_cover_data_request->filename, cover_data);
|
||||
if (result.success()) {
|
||||
if (TagReaderLoadCoverDataReplyPtr load_cover_data_reply = std::dynamic_pointer_cast<TagReaderLoadCoverDataReply>(reply)) {
|
||||
load_cover_data_reply->set_data(cover_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TagReaderLoadCoverImageRequestPtr load_cover_image_request = dynamic_pointer_cast<TagReaderLoadCoverImageRequest>(request)) {
|
||||
QImage cover_image;
|
||||
result = LoadCoverImageBlocking(load_cover_image_request->filename, cover_image);
|
||||
if (result.success()) {
|
||||
if (TagReaderLoadCoverImageReplyPtr load_cover_image_reply = std::dynamic_pointer_cast<TagReaderLoadCoverImageReply>(reply)) {
|
||||
load_cover_image_reply->set_image(cover_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TagReaderSaveCoverRequestPtr save_cover_request = dynamic_pointer_cast<TagReaderSaveCoverRequest>(request)) {
|
||||
result = SaveCoverBlocking(save_cover_request->filename, save_cover_request->save_tag_cover_data);
|
||||
}
|
||||
else if (TagReaderSavePlaycountRequestPtr save_playcount_request = dynamic_pointer_cast<TagReaderSavePlaycountRequest>(request)) {
|
||||
result = SaveSongPlaycountBlocking(save_playcount_request->filename, save_playcount_request->playcount);
|
||||
}
|
||||
else if (TagReaderSaveRatingRequestPtr save_rating_request = dynamic_pointer_cast<TagReaderSaveRatingRequest>(request)) {
|
||||
result = SaveSongRatingBlocking(save_rating_request->filename, save_rating_request->rating);
|
||||
}
|
||||
|
||||
reply->set_result(result);
|
||||
|
||||
reply->Finish();
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) const {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
return tagreader_.IsMediaFile(filename).success();
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::IsMediaFileAsync(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderIsMediaFileRequestPtr request = TagReaderIsMediaFileRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
|
||||
|
||||
return tagreader_.ReadFile(filename, song);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReadFileReplyPtr TagReaderClient::ReadFileAsync(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReadFileReplyPtr reply = TagReaderReply::Create<TagReaderReadFileReply>(filename);
|
||||
|
||||
TagReaderReadFileRequestPtr request = TagReaderReadFileRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderWriteFileRequestPtr request = TagReaderWriteFileRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
request->song = song;
|
||||
request->save_tags_options = save_tags_options;
|
||||
request->save_tag_cover_data = save_tag_cover_data;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::LoadCoverDataBlocking(const QString &filename, QByteArray &data) {
|
||||
|
||||
return tagreader_.LoadEmbeddedCover(filename, data);
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::LoadCoverImageBlocking(const QString &filename, QImage &image) {
|
||||
|
||||
QByteArray data;
|
||||
TagReaderResult result = LoadCoverDataBlocking(filename, data);
|
||||
if (result.error_code == TagReaderResult::ErrorCode::Success && !image.loadFromData(data)) {
|
||||
result.error_code = TagReaderResult::ErrorCode::Unsupported;
|
||||
result.error_text = QObject::tr("Failed to load image from data for %1").arg(filename);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
TagReaderLoadCoverDataReplyPtr TagReaderClient::LoadCoverDataAsync(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderLoadCoverDataReplyPtr reply = TagReaderReply::Create<TagReaderLoadCoverDataReply>(filename);
|
||||
|
||||
TagReaderLoadCoverDataRequestPtr request = TagReaderLoadCoverDataRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderLoadCoverImageReplyPtr TagReaderClient::LoadCoverImageAsync(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderLoadCoverImageReplyPtr reply = TagReaderReply::Create<TagReaderLoadCoverImageReply>(filename);
|
||||
|
||||
TagReaderLoadCoverImageRequestPtr request = TagReaderLoadCoverImageRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::SaveCoverBlocking(const QString &filename, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
return tagreader_.SaveEmbeddedCover(filename, save_tag_cover_data);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::SaveCoverAsync(const QString &filename, const SaveTagCoverData &save_tag_cover_data) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderSaveCoverRequestPtr request = TagReaderSaveCoverRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
request->save_tag_cover_data = save_tag_cover_data;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::SaveSongPlaycountAsync(const QString &filename, const uint playcount) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderSavePlaycountRequestPtr request = TagReaderSavePlaycountRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
request->playcount = playcount;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) {
|
||||
|
||||
return tagreader_.SaveSongPlaycount(filename, playcount);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::SaveSongsPlaycountAsync(const SongList &songs) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReplyPtr reply = SaveSongPlaycountAsync(song.url().toLocalFile(), song.playcount());
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, &*reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TagReaderResult TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) {
|
||||
|
||||
return tagreader_.SaveSongRating(filename, rating);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReplyPtr TagReaderClient::SaveSongRatingAsync(const QString &filename, const float rating) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
TagReaderReplyPtr reply = TagReaderReply::Create<TagReaderReply>(filename);
|
||||
|
||||
TagReaderSaveRatingRequestPtr request = TagReaderSaveRatingRequest::Create(filename);
|
||||
request->reply = reply;
|
||||
request->filename = filename;
|
||||
request->rating = rating;
|
||||
|
||||
EnqueueRequest(request);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::SaveSongsRatingAsync(const SongList &songs) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReplyPtr reply = SaveSongRatingAsync(song.url().toLocalFile(), song.rating());
|
||||
QObject::connect(&*reply, &TagReaderReply::Finished, &*reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
117
src/tagreader/tagreaderclient.h
Normal file
117
src/tagreader/tagreaderclient.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERCLIENT_H
|
||||
#define TAGREADERCLIENT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/mutex_protected.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreadertaglib.h"
|
||||
#include "tagreadergme.h"
|
||||
#include "tagreaderrequest.h"
|
||||
#include "tagreaderresult.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderreadfilereply.h"
|
||||
#include "tagreaderloadcoverdatareply.h"
|
||||
#include "tagreaderloadcoverimagereply.h"
|
||||
#include "savetagsoptions.h"
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
class QThread;
|
||||
class Song;
|
||||
|
||||
class TagReaderClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderClient(QObject *parent = nullptr);
|
||||
|
||||
static TagReaderClient *Instance() { return sInstance; }
|
||||
|
||||
void Start();
|
||||
void ExitAsync();
|
||||
|
||||
using SaveOption = SaveTagsOption;
|
||||
using SaveOptions = SaveTagsOptions;
|
||||
|
||||
bool IsMediaFileBlocking(const QString &filename) const;
|
||||
TagReaderReplyPtr IsMediaFileAsync(const QString &filename);
|
||||
|
||||
TagReaderResult ReadFileBlocking(const QString &filename, Song *song);
|
||||
TagReaderReadFileReplyPtr ReadFileAsync(const QString &filename);
|
||||
|
||||
TagReaderResult WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
|
||||
TagReaderReplyPtr WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options = SaveTagsOption::Tags, const SaveTagCoverData &save_tag_cover_data = SaveTagCoverData());
|
||||
|
||||
TagReaderResult LoadCoverDataBlocking(const QString &filename, QByteArray &data);
|
||||
TagReaderResult LoadCoverImageBlocking(const QString &filename, QImage &image);
|
||||
TagReaderLoadCoverDataReplyPtr LoadCoverDataAsync(const QString &filename);
|
||||
TagReaderLoadCoverImageReplyPtr LoadCoverImageAsync(const QString &filename);
|
||||
|
||||
TagReaderResult SaveCoverBlocking(const QString &filename, const SaveTagCoverData &save_tag_cover_data);
|
||||
TagReaderReplyPtr SaveCoverAsync(const QString &filename, const SaveTagCoverData &save_tag_cover_data);
|
||||
|
||||
TagReaderReplyPtr SaveSongPlaycountAsync(const QString &filename, const uint playcount);
|
||||
TagReaderResult SaveSongPlaycountBlocking(const QString &filename, const uint playcount);
|
||||
|
||||
TagReaderReplyPtr SaveSongRatingAsync(const QString &filename, const float rating);
|
||||
TagReaderResult SaveSongRatingBlocking(const QString &filename, const float rating);
|
||||
|
||||
private:
|
||||
bool HaveRequests() const;
|
||||
void EnqueueRequest(TagReaderRequestPtr request);
|
||||
TagReaderRequestPtr DequeueRequest();
|
||||
void ProcessRequestsAsync();
|
||||
void ProcessRequest(TagReaderRequestPtr request);
|
||||
|
||||
Q_SIGNALS:
|
||||
void ExitFinished();
|
||||
|
||||
private Q_SLOTS:
|
||||
void Exit();
|
||||
void ProcessRequests();
|
||||
|
||||
public Q_SLOTS:
|
||||
void SaveSongsPlaycountAsync(const SongList &songs);
|
||||
void SaveSongsRatingAsync(const SongList &songs);
|
||||
|
||||
private:
|
||||
static TagReaderClient *sInstance;
|
||||
|
||||
QThread *original_thread_;
|
||||
QQueue<TagReaderRequestPtr> requests_;
|
||||
mutable QMutex mutex_requests_;
|
||||
TagReaderTagLib tagreader_;
|
||||
TagReaderGME gmereader_;
|
||||
mutex_protected<bool> abort_;
|
||||
mutex_protected<bool> processing_;
|
||||
};
|
||||
|
||||
#endif // TAGREADERCLIENT_H
|
||||
349
src/tagreader/tagreadergme.cpp
Normal file
349
src/tagreader/tagreadergme.cpp
Normal 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;
|
||||
|
||||
}
|
||||
114
src/tagreader/tagreadergme.h
Normal file
114
src/tagreader/tagreadergme.h
Normal 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
|
||||
24
src/tagreader/tagreaderismediafilerequest.cpp
Normal file
24
src/tagreader/tagreaderismediafilerequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderismediafilerequest.h"
|
||||
|
||||
TagReaderIsMediaFileRequest::TagReaderIsMediaFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
38
src/tagreader/tagreaderismediafilerequest.h
Normal file
38
src/tagreader/tagreaderismediafilerequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERISMEDIAFILEREQUEST_H
|
||||
#define TAGREADERISMEDIAFILEREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderIsMediaFileRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderIsMediaFileRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderIsMediaFileRequest> Create(const QString &filename) { return make_shared<TagReaderIsMediaFileRequest>(filename); }
|
||||
};
|
||||
|
||||
using TagReaderIsMediaFileRequestPtr = std::shared_ptr<TagReaderIsMediaFileRequest>;
|
||||
|
||||
#endif // TAGREADERISMEDIAFILEREQUEST_H
|
||||
40
src/tagreader/tagreaderloadcoverdatareply.cpp
Normal file
40
src/tagreader/tagreaderloadcoverdatareply.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderloadcoverdatareply.h"
|
||||
|
||||
TagReaderLoadCoverDataReply::TagReaderLoadCoverDataReply(const QString &_filename, QObject *parent)
|
||||
: TagReaderReply(_filename, parent) {}
|
||||
|
||||
void TagReaderLoadCoverDataReply::Finish() {
|
||||
|
||||
qLog(Debug) << "Finishing tagreader reply for" << filename_;
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Q_EMIT TagReaderReply::Finished(filename_, result_);
|
||||
Q_EMIT TagReaderLoadCoverDataReply::Finished(filename_, data_, result_);
|
||||
|
||||
QObject::disconnect(this, &TagReaderReply::Finished, nullptr, nullptr);
|
||||
QObject::disconnect(this, &TagReaderLoadCoverDataReply::Finished, nullptr, nullptr);
|
||||
|
||||
}
|
||||
51
src/tagreader/tagreaderloadcoverdatareply.h
Normal file
51
src/tagreader/tagreaderloadcoverdatareply.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERLOADCOVERDATAREPLY_H
|
||||
#define TAGREADERLOADCOVERDATAREPLY_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
class TagReaderLoadCoverDataReply : public TagReaderReply {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderLoadCoverDataReply(const QString &_filename, QObject *parent = nullptr);
|
||||
|
||||
void Finish() override;
|
||||
|
||||
QByteArray data() const { return data_; }
|
||||
void set_data(const QByteArray &data) { data_ = data; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const QString &filename, const QByteArray &data, const TagReaderResult &result);
|
||||
|
||||
private:
|
||||
QByteArray data_;
|
||||
};
|
||||
|
||||
using TagReaderLoadCoverDataReplyPtr = SharedPtr<TagReaderLoadCoverDataReply>;
|
||||
|
||||
#endif // TAGREADERLOADCOVERDATAREPLY_H
|
||||
24
src/tagreader/tagreaderloadcoverdatarequest.cpp
Normal file
24
src/tagreader/tagreaderloadcoverdatarequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderloadcoverdatarequest.h"
|
||||
|
||||
TagReaderLoadCoverDataRequest::TagReaderLoadCoverDataRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
38
src/tagreader/tagreaderloadcoverdatarequest.h
Normal file
38
src/tagreader/tagreaderloadcoverdatarequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERLOADCOVERDATAREQUEST_H
|
||||
#define TAGREADERLOADCOVERDATAREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderLoadCoverDataRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderLoadCoverDataRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderLoadCoverDataRequest> Create(const QString &filename) { return make_shared<TagReaderLoadCoverDataRequest>(filename); }
|
||||
};
|
||||
|
||||
using TagReaderLoadCoverDataRequestPtr = SharedPtr<TagReaderLoadCoverDataRequest>;
|
||||
|
||||
#endif // TAGREADERLOADCOVERDATAREQUEST_H
|
||||
39
src/tagreader/tagreaderloadcoverimagereply.cpp
Normal file
39
src/tagreader/tagreaderloadcoverimagereply.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderloadcoverimagereply.h"
|
||||
|
||||
TagReaderLoadCoverImageReply::TagReaderLoadCoverImageReply(const QString &_filename, QObject *parent)
|
||||
: TagReaderReply(_filename, parent) {}
|
||||
|
||||
void TagReaderLoadCoverImageReply::Finish() {
|
||||
|
||||
qLog(Debug) << "Finishing tagreader reply for" << filename_;
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Q_EMIT TagReaderReply::Finished(filename_, result_);
|
||||
Q_EMIT TagReaderLoadCoverImageReply::Finished(filename_, image_, result_);
|
||||
|
||||
QObject::disconnect(this, &TagReaderLoadCoverImageReply::Finished, nullptr, nullptr);
|
||||
|
||||
}
|
||||
51
src/tagreader/tagreaderloadcoverimagereply.h
Normal file
51
src/tagreader/tagreaderloadcoverimagereply.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERLOADCOVERIMAGEREPLY_H
|
||||
#define TAGREADERLOADCOVERIMAGEREPLY_H
|
||||
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
class TagReaderLoadCoverImageReply : public TagReaderReply {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderLoadCoverImageReply(const QString &_filename, QObject *parent = nullptr);
|
||||
|
||||
void Finish() override;
|
||||
|
||||
QImage image() const { return image_; }
|
||||
void set_image(const QImage &image) { image_ = image; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const QString &filename, const QImage &image, const TagReaderResult &result);
|
||||
|
||||
private:
|
||||
QImage image_;
|
||||
};
|
||||
|
||||
using TagReaderLoadCoverImageReplyPtr = SharedPtr<TagReaderLoadCoverImageReply>;
|
||||
|
||||
#endif // TAGREADERLOADCOVERIMAGEREPLY_H
|
||||
24
src/tagreader/tagreaderloadcoverimagerequest.cpp
Normal file
24
src/tagreader/tagreaderloadcoverimagerequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderloadcoverimagerequest.h"
|
||||
|
||||
TagReaderLoadCoverImageRequest::TagReaderLoadCoverImageRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
38
src/tagreader/tagreaderloadcoverimagerequest.h
Normal file
38
src/tagreader/tagreaderloadcoverimagerequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERLOADCOVERIMAGEREQUEST_H
|
||||
#define TAGREADERLOADCOVERIMAGEREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderLoadCoverImageRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderLoadCoverImageRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderLoadCoverImageRequest> Create(const QString &filename) { return make_shared<TagReaderLoadCoverImageRequest>(filename); }
|
||||
};
|
||||
|
||||
using TagReaderLoadCoverImageRequestPtr = SharedPtr<TagReaderLoadCoverImageRequest>;
|
||||
|
||||
#endif // TAGREADERLOADEMBEDDEDCOVERASIMAGEREQUEST_H
|
||||
39
src/tagreader/tagreaderreadfilereply.cpp
Normal file
39
src/tagreader/tagreaderreadfilereply.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderreadfilereply.h"
|
||||
|
||||
TagReaderReadFileReply::TagReaderReadFileReply(const QString &_filename, QObject *parent)
|
||||
: TagReaderReply(_filename, parent) {}
|
||||
|
||||
void TagReaderReadFileReply::Finish() {
|
||||
|
||||
qLog(Debug) << "Finishing tagreader reply for" << filename_;
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Q_EMIT TagReaderReply::Finished(filename_, result_);
|
||||
Q_EMIT TagReaderReadFileReply::Finished(filename_, song_, result_);
|
||||
|
||||
QObject::disconnect(this, &TagReaderReadFileReply::Finished, nullptr, nullptr);
|
||||
|
||||
}
|
||||
50
src/tagreader/tagreaderreadfilereply.h
Normal file
50
src/tagreader/tagreaderreadfilereply.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERREADFILEREPLY_H
|
||||
#define TAGREADERREADFILEREPLY_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "tagreaderreply.h"
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
class TagReaderReadFileReply : public TagReaderReply {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderReadFileReply(const QString &_filename, QObject *parent = nullptr);
|
||||
|
||||
void Finish() override;
|
||||
|
||||
Song song() const { return song_; }
|
||||
void set_song(const Song &song) { song_ = song; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const QString &filename, const Song &song, const TagReaderResult &result);
|
||||
|
||||
private:
|
||||
Song song_;
|
||||
};
|
||||
|
||||
using TagReaderReadFileReplyPtr = SharedPtr<TagReaderReadFileReply>;
|
||||
|
||||
#endif // TAGREADERREADFILEREPLY_H
|
||||
24
src/tagreader/tagreaderreadfilerequest.cpp
Normal file
24
src/tagreader/tagreaderreadfilerequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderreadfilerequest.h"
|
||||
|
||||
TagReaderReadFileRequest::TagReaderReadFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
38
src/tagreader/tagreaderreadfilerequest.h
Normal file
38
src/tagreader/tagreaderreadfilerequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERREADFILEREQUEST_H
|
||||
#define TAGREADERREADFILEREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderReadFileRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderReadFileRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderReadFileRequest> Create(const QString &filename) { return make_shared<TagReaderReadFileRequest>(filename); }
|
||||
};
|
||||
|
||||
using TagReaderReadFileRequestPtr = SharedPtr<TagReaderReadFileRequest>;
|
||||
|
||||
#endif // TAGREADERREADFILEREQUEST_H
|
||||
51
src/tagreader/tagreaderreply.cpp
Normal file
51
src/tagreader/tagreaderreply.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "tagreaderreply.h"
|
||||
|
||||
TagReaderReply::TagReaderReply(const QString &filename, QObject *parent)
|
||||
: QObject(parent),
|
||||
filename_(filename),
|
||||
finished_(false) {
|
||||
|
||||
qLog(Debug) << "New tagreader reply for" << filename_;
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply::~TagReaderReply() {
|
||||
|
||||
qLog(Debug) << "Deleting tagreader reply for" << filename_;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderReply::Finish() {
|
||||
|
||||
qLog(Debug) << "Finishing tagreader reply for" << filename_;
|
||||
|
||||
finished_ = true;
|
||||
|
||||
Q_EMIT Finished(filename_, result_);
|
||||
|
||||
QObject::disconnect(this, &TagReaderReply::Finished, nullptr, nullptr);
|
||||
|
||||
}
|
||||
67
src/tagreader/tagreaderreply.h
Normal file
67
src/tagreader/tagreaderreply.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERREPLY_H
|
||||
#define TAGREADERREPLY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
class TagReaderReply : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TagReaderReply(const QString &filename, QObject *parent = nullptr);
|
||||
virtual ~TagReaderReply() override;
|
||||
|
||||
template<typename T>
|
||||
static SharedPtr<T> Create(const QString &filename) {
|
||||
|
||||
SharedPtr<T> reply;
|
||||
reply.reset(new T(filename), [](QObject *obj) { obj->deleteLater(); });
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
QString filename() const { return filename_; }
|
||||
|
||||
TagReaderResult result() const { return result_; }
|
||||
void set_result(const TagReaderResult &result) { result_ = result; }
|
||||
|
||||
bool finished() const { return finished_; }
|
||||
bool success() const { return result_.success(); }
|
||||
QString error() const { return result_.error_string(); }
|
||||
|
||||
virtual void Finish();
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const QString &filename, const TagReaderResult &result);
|
||||
|
||||
protected:
|
||||
const QString filename_;
|
||||
bool finished_;
|
||||
TagReaderResult result_;
|
||||
};
|
||||
|
||||
using TagReaderReplyPtr = SharedPtr<TagReaderReply>;
|
||||
|
||||
#endif // TAGREADERREPLY_H
|
||||
34
src/tagreader/tagreaderrequest.cpp
Normal file
34
src/tagreader/tagreaderrequest.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
TagReaderRequest::TagReaderRequest(const QString &_filename) : filename(_filename) {
|
||||
|
||||
qLog(Debug) << "New tagreader request for" << filename;
|
||||
|
||||
}
|
||||
|
||||
TagReaderRequest::~TagReaderRequest() {
|
||||
|
||||
qLog(Debug) << "Deleting tagreader request for" << filename;
|
||||
|
||||
}
|
||||
38
src/tagreader/tagreaderrequest.h
Normal file
38
src/tagreader/tagreaderrequest.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERREQUEST_H
|
||||
#define TAGREADERREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderreply.h"
|
||||
|
||||
class TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderRequest(const QString &_filename);
|
||||
virtual ~TagReaderRequest();
|
||||
QString filename;
|
||||
TagReaderReplyPtr reply;
|
||||
};
|
||||
|
||||
using TagReaderRequestPtr = SharedPtr<TagReaderRequest>;
|
||||
|
||||
#endif // TAGREADERREQUEST_H
|
||||
47
src/tagreader/tagreaderresult.cpp
Normal file
47
src/tagreader/tagreaderresult.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "tagreaderresult.h"
|
||||
|
||||
QString TagReaderResult::error_string() const {
|
||||
|
||||
switch (error_code) {
|
||||
case ErrorCode::Success:
|
||||
return QObject::tr("Success");
|
||||
case ErrorCode::Unsupported:
|
||||
return QObject::tr("File is unsupported");
|
||||
case ErrorCode::FilenameMissing:
|
||||
return QObject::tr("Filename is missing");
|
||||
case ErrorCode::FileDoesNotExist:
|
||||
return QObject::tr("File does not exist");
|
||||
case ErrorCode::FileOpenError:
|
||||
return QObject::tr("File could not be opened");
|
||||
case ErrorCode::FileParseError:
|
||||
return QObject::tr("Could not parse file");
|
||||
case ErrorCode::FileSaveError:
|
||||
return QObject::tr("Could save file");
|
||||
case ErrorCode::CustomError:
|
||||
return error_text;
|
||||
}
|
||||
|
||||
return QObject::tr("Unknown error");
|
||||
|
||||
}
|
||||
44
src/tagreader/tagreaderresult.h
Normal file
44
src/tagreader/tagreaderresult.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERRESULT_H
|
||||
#define TAGREADERRESULT_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class TagReaderResult {
|
||||
public:
|
||||
enum class ErrorCode {
|
||||
Success,
|
||||
Unsupported,
|
||||
FilenameMissing,
|
||||
FileDoesNotExist,
|
||||
FileOpenError,
|
||||
FileParseError,
|
||||
FileSaveError,
|
||||
CustomError,
|
||||
};
|
||||
TagReaderResult(const ErrorCode _error_code = ErrorCode::Unsupported, const QString &_error_text = QString()) : error_code(_error_code), error_text(_error_text) {}
|
||||
ErrorCode error_code;
|
||||
QString error_text;
|
||||
bool success() const { return error_code == ErrorCode::Success; }
|
||||
QString error_string() const;
|
||||
};
|
||||
|
||||
#endif // TAGREADERRESULT_H
|
||||
24
src/tagreader/tagreadersavecoverrequest.cpp
Normal file
24
src/tagreader/tagreadersavecoverrequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreadersavecoverrequest.h"
|
||||
|
||||
TagReaderSaveCoverRequest::TagReaderSaveCoverRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
43
src/tagreader/tagreadersavecoverrequest.h
Normal file
43
src/tagreader/tagreadersavecoverrequest.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERSAVECOVERREQUEST_H
|
||||
#define TAGREADERSAVECOVERREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderSaveCoverRequest : public TagReaderRequest {
|
||||
public:
|
||||
|
||||
explicit TagReaderSaveCoverRequest(const QString &_filename);
|
||||
|
||||
static SharedPtr<TagReaderSaveCoverRequest> Create(const QString &filename) { return make_shared<TagReaderSaveCoverRequest>(filename); }
|
||||
|
||||
SaveTagCoverData save_tag_cover_data;
|
||||
};
|
||||
|
||||
using TagReaderSaveCoverRequestPtr = SharedPtr<TagReaderSaveCoverRequest>;
|
||||
|
||||
#endif // TAGREADERSAVECOVERREQUEST_H
|
||||
24
src/tagreader/tagreadersaveplaycountrequest.cpp
Normal file
24
src/tagreader/tagreadersaveplaycountrequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreadersaveplaycountrequest.h"
|
||||
|
||||
TagReaderSavePlaycountRequest::TagReaderSavePlaycountRequest(const QString &_filename) : TagReaderRequest(_filename), playcount(0) {}
|
||||
39
src/tagreader/tagreadersaveplaycountrequest.h
Normal file
39
src/tagreader/tagreadersaveplaycountrequest.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERSAVEPLAYCOUNTREQUEST_H
|
||||
#define TAGREADERSAVEPLAYCOUNTREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderSavePlaycountRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderSavePlaycountRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderSavePlaycountRequest> Create(const QString &filename) { return make_shared<TagReaderSavePlaycountRequest>(filename); }
|
||||
uint playcount;
|
||||
};
|
||||
|
||||
using TagReaderSavePlaycountRequestPtr = SharedPtr<TagReaderSavePlaycountRequest>;
|
||||
|
||||
#endif // TAGREADERSAVEPLAYCOUNTREQUEST_H
|
||||
24
src/tagreader/tagreadersaveratingrequest.cpp
Normal file
24
src/tagreader/tagreadersaveratingrequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreadersaveratingrequest.h"
|
||||
|
||||
TagReaderSaveRatingRequest::TagReaderSaveRatingRequest(const QString &_filename) : TagReaderRequest(_filename), rating(0.0F) {}
|
||||
39
src/tagreader/tagreadersaveratingrequest.h
Normal file
39
src/tagreader/tagreadersaveratingrequest.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERSAVERATINGREQUEST_H
|
||||
#define TAGREADERSAVERATINGREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "tagreaderrequest.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderSaveRatingRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderSaveRatingRequest(const QString &_filename);
|
||||
static SharedPtr<TagReaderSaveRatingRequest> Create(const QString &filename) { return make_shared<TagReaderSaveRatingRequest>(filename); }
|
||||
float rating;
|
||||
};
|
||||
|
||||
using TagReaderSaveRatingRequestPtr = SharedPtr<TagReaderSaveRatingRequest>;
|
||||
|
||||
#endif // TAGREADERSAVERATINGREQUEST_H
|
||||
1886
src/tagreader/tagreadertaglib.cpp
Normal file
1886
src/tagreader/tagreadertaglib.cpp
Normal file
File diff suppressed because it is too large
Load Diff
127
src/tagreader/tagreadertaglib.h
Normal file
127
src/tagreader/tagreadertaglib.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2013, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERTAGLIB_H
|
||||
#define TAGREADERTAGLIB_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include <taglib/tstring.h>
|
||||
#include <taglib/fileref.h>
|
||||
#include <taglib/xiphcomment.h>
|
||||
#include <taglib/flacfile.h>
|
||||
#include <taglib/mpegfile.h>
|
||||
#include <taglib/mp4file.h>
|
||||
#include <taglib/apetag.h>
|
||||
#include <taglib/apefile.h>
|
||||
#include <taglib/asffile.h>
|
||||
#include <taglib/id3v2tag.h>
|
||||
#include <taglib/popularimeterframe.h>
|
||||
#include <taglib/mp4tag.h>
|
||||
#include <taglib/asftag.h>
|
||||
|
||||
#include "core/song.h"
|
||||
|
||||
#include "tagreaderbase.h"
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
#undef TStringToQString
|
||||
#undef QStringToTString
|
||||
|
||||
class FileRefFactory;
|
||||
|
||||
class TagReaderTagLib : public TagReaderBase {
|
||||
public:
|
||||
explicit TagReaderTagLib();
|
||||
~TagReaderTagLib() override;
|
||||
|
||||
static inline TagLib::String QStringToTagLibString(const QString &s) {
|
||||
return TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8);
|
||||
}
|
||||
|
||||
static inline QString TagLibStringToQString(const TagLib::String &s) {
|
||||
return QString::fromUtf8((s).toCString(true));
|
||||
}
|
||||
|
||||
TagReaderResult IsMediaFile(const QString &filename) const override;
|
||||
|
||||
TagReaderResult ReadFile(const QString &filename, Song *song) const override;
|
||||
TagReaderResult WriteFile(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||
|
||||
TagReaderResult LoadEmbeddedCover(const QString &filename, QByteArray &data) const override;
|
||||
TagReaderResult SaveEmbeddedCover(const QString &filename, const SaveTagCoverData &save_tag_cover_data) const override;
|
||||
|
||||
TagReaderResult SaveSongPlaycount(const QString &filename, const uint playcount) const override;
|
||||
TagReaderResult SaveSongRating(const QString &filename, const float rating) const override;
|
||||
|
||||
private:
|
||||
Song::FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||
|
||||
void ParseID3v2Tags(TagLib::ID3v2::Tag *tag, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseVorbisComments(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseAPETags(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseMP4Tags(TagLib::MP4::Tag *tag, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseASFTags(TagLib::ASF::Tag *tag, QString *disc, QString *compilation, Song *song) const;
|
||||
void ParseASFAttribute(const TagLib::ASF::AttributeListMap &attributes_map, const char *attribute, QString *str) const;
|
||||
|
||||
void SetID3v2Tag(TagLib::ID3v2::Tag *tag, const Song &song) const;
|
||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUnsyncLyricsFrame(const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
|
||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const Song &song) const;
|
||||
void SetAPETag(TagLib::APE::Tag *tag, const Song &song) const;
|
||||
void SetASFTag(TagLib::ASF::Tag *tag, const Song &song) const;
|
||||
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const QString &value) const;
|
||||
void SetAsfAttribute(TagLib::ASF::Tag *tag, const char *attribute, const int value) const;
|
||||
|
||||
QByteArray LoadEmbeddedAPECover(const TagLib::APE::ItemListMap &map) const;
|
||||
|
||||
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
||||
|
||||
void SetPlaycount(TagLib::Ogg::XiphComment *vorbis_comment, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::APE::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::ID3v2::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::MP4::Tag *tag, const uint playcount) const;
|
||||
void SetPlaycount(TagLib::ASF::Tag *tag, const uint playcount) const;
|
||||
|
||||
void SetRating(TagLib::Ogg::XiphComment *vorbis_comment, const float rating) const;
|
||||
void SetRating(TagLib::APE::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::ID3v2::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::MP4::Tag *tag, const float rating) const;
|
||||
void SetRating(TagLib::ASF::Tag *tag, const float rating) const;
|
||||
|
||||
void SetEmbeddedCover(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mimetype) const;
|
||||
void SetEmbeddedCover(TagLib::Ogg::XiphComment *vorbis_comment, const QByteArray &data, const QString &mimetype) const;
|
||||
void SetEmbeddedCover(TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mimetype) const;
|
||||
void SetEmbeddedCover(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mimetype) const;
|
||||
|
||||
static TagLib::String TagLibStringListToSlashSeparatedString(const TagLib::StringList &taglib_string_list);
|
||||
|
||||
private:
|
||||
FileRefFactory *factory_;
|
||||
|
||||
Q_DISABLE_COPY(TagReaderTagLib)
|
||||
};
|
||||
|
||||
#endif // TAGREADERTAGLIB_H
|
||||
24
src/tagreader/tagreaderwritefilerequest.cpp
Normal file
24
src/tagreader/tagreaderwritefilerequest.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "tagreaderwritefilerequest.h"
|
||||
|
||||
TagReaderWriteFileRequest::TagReaderWriteFileRequest(const QString &_filename) : TagReaderRequest(_filename) {}
|
||||
46
src/tagreader/tagreaderwritefilerequest.h
Normal file
46
src/tagreader/tagreaderwritefilerequest.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TAGREADERWRITEFILEREQUEST_H
|
||||
#define TAGREADERWRITEFILEREQUEST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "tagreaderrequest.h"
|
||||
#include "savetagsoptions.h"
|
||||
#include "savetagcoverdata.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
class TagReaderWriteFileRequest : public TagReaderRequest {
|
||||
public:
|
||||
explicit TagReaderWriteFileRequest(const QString &_filename);
|
||||
|
||||
static SharedPtr<TagReaderWriteFileRequest> Create(const QString &filename) { return make_shared<TagReaderWriteFileRequest>(filename); }
|
||||
|
||||
SaveTagsOptions save_tags_options;
|
||||
Song song;
|
||||
SaveTagCoverData save_tag_cover_data;
|
||||
};
|
||||
|
||||
using TagReaderWriteFileRequestPtr = SharedPtr<TagReaderWriteFileRequest>;
|
||||
|
||||
#endif // TAGREADERWRITEFILEREQUEST_H
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user