From 239b88aa3b69d627cadf5a226981b03506cd76d9 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Fri, 2 Jul 2021 01:16:14 +0200 Subject: [PATCH] Add support for TagParser as an alternative to TagLib --- CMakeLists.txt | 40 +- README.md | 2 +- ext/libstrawberry-tagreader/CMakeLists.txt | 30 +- ext/libstrawberry-tagreader/tagreaderbase.cpp | 27 ++ ext/libstrawberry-tagreader/tagreaderbase.h | 51 +++ .../{tagreader.cpp => tagreadertaglib.cpp} | 43 +- .../{tagreader.h => tagreadertaglib.h} | 36 +- .../tagreadertagparser.cpp | 413 ++++++++++++++++++ .../tagreadertagparser.h | 51 +++ ext/strawberry-tagreader/CMakeLists.txt | 25 +- ext/strawberry-tagreader/tagreaderworker.h | 12 +- src/config.h.in | 3 + 12 files changed, 671 insertions(+), 62 deletions(-) create mode 100644 ext/libstrawberry-tagreader/tagreaderbase.cpp create mode 100644 ext/libstrawberry-tagreader/tagreaderbase.h rename ext/libstrawberry-tagreader/{tagreader.cpp => tagreadertaglib.cpp} (94%) rename ext/libstrawberry-tagreader/{tagreader.h => tagreadertaglib.h} (85%) create mode 100644 ext/libstrawberry-tagreader/tagreadertagparser.cpp create mode 100644 ext/libstrawberry-tagreader/tagreadertagparser.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d941183..b943d887 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,16 +223,36 @@ if(X11_FOUND) endif() endif(X11_FOUND) +option(USE_TAGLIB "Build with TagLib" OFF) +option(USE_TAGPARSER "Build with TagParser" OFF) + +if(NOT USE_TAGLIB AND NOT USE_TAGPARSER) + set(USE_TAGLIB ON) +endif() + # TAGLIB -pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1) -find_path(HAVE_TAGLIB_DSFFILE_H taglib/dsffile.h) -find_path(HAVE_TAGLIB_DSDIFFFILE_H taglib/dsdifffile.h) -if(HAVE_TAGLIB_DSFFILE_H) - set(HAVE_TAGLIB_DSFFILE ON) -endif(HAVE_TAGLIB_DSFFILE_H) -if(HAVE_TAGLIB_DSDIFFFILE_H) - set(HAVE_TAGLIB_DSDIFFFILE ON) -endif(HAVE_TAGLIB_DSDIFFFILE_H) +if(USE_TAGLIB) + pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1) + if(TAGLIB_FOUND) + find_path(HAVE_TAGLIB_DSFFILE_H taglib/dsffile.h) + find_path(HAVE_TAGLIB_DSDIFFFILE_H taglib/dsdifffile.h) + if(HAVE_TAGLIB_DSFFILE_H) + set(HAVE_TAGLIB_DSFFILE ON) + endif(HAVE_TAGLIB_DSFFILE_H) + if(HAVE_TAGLIB_DSDIFFFILE_H) + set(HAVE_TAGLIB_DSDIFFFILE ON) + endif(HAVE_TAGLIB_DSDIFFFILE_H) + endif() +endif() + +# TAGPARSER +if(USE_TAGPARSER) + pkg_check_modules(TAGPARSER REQUIRED tagparser) +endif() + +if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND) + message(FATAL_ERROR "You need either TagLib or TagParser!") +endif() # SingleApplication add_subdirectory(3rdparty/singleapplication) @@ -476,6 +496,6 @@ if(NOT CMAKE_CROSSCOMPILING) endif() endif() -if(NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12) +if(USE_TAGLIB AND TAGLIB_FOUND AND NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12) message(WARNING "There is a critical bug in TagLib (1.11.1) that can result in corrupt Ogg files, see: https://github.com/taglib/taglib/issues/864, please consider updating TagLib to the newest version.") endif() diff --git a/README.md b/README.md index 6bbf0e3f..7855fdf4 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ To build Strawberry from source you need the following installed on your system * [D-Bus (Linux required)](https://www.freedesktop.org/wiki/Software/dbus/) * [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org) * [GnuTLS](https://www.gnutls.org/) -* [TagLib 1.11.1 or higher](https://www.taglib.org/) +* [TagLib 1.11.1 or higher](https://www.taglib.org/) or [TagParser](https://github.com/Martchus/tagparser) Optional dependencies: diff --git a/ext/libstrawberry-tagreader/CMakeLists.txt b/ext/libstrawberry-tagreader/CMakeLists.txt index 505535c5..f15560f5 100644 --- a/ext/libstrawberry-tagreader/CMakeLists.txt +++ b/ext/libstrawberry-tagreader/CMakeLists.txt @@ -1,7 +1,15 @@ cmake_minimum_required(VERSION 3.0) set(MESSAGES tagreadermessages.proto) -set(SOURCES tagreader.cpp) +set(SOURCES tagreaderbase.cpp) + +if(USE_TAGLIB AND TAGLIB_FOUND) + list(APPEND SOURCES tagreadertaglib.cpp) +endif() + +if(USE_TAGPARSER AND TAGPARSER_FOUND) + list(APPEND SOURCES tagreadertagparser.cpp) +endif() protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES}) @@ -11,12 +19,19 @@ link_directories( ${TAGLIB_LIBRARY_DIRS} ) +if(USE_TAGLIB AND TAGLIB_FOUND) + link_directories(${TAGLIB_LIBRARY_DIRS}) +endif() + +if(USE_TAGPARSER AND TAGPARSER_FOUND) + link_directories(${TAGPARSER_LIBRARY_DIRS}) +endif() + add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES}) target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${GLIB_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS} - ${TAGLIB_INCLUDE_DIRS} ) target_include_directories(libstrawberry-tagreader PRIVATE @@ -30,8 +45,17 @@ target_include_directories(libstrawberry-tagreader PRIVATE target_link_libraries(libstrawberry-tagreader PRIVATE ${GLIB_LIBRARIES} ${PROTOBUF_LIBRARY} - ${TAGLIB_LIBRARIES} ${QtCore_LIBRARIES} ${QtNetwork_LIBRARIES} libstrawberry-common ) + +if(USE_TAGLIB AND TAGLIB_FOUND) + target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS}) + target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES}) +endif() + +if(USE_TAGPARSER AND TAGPARSER_FOUND) + target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS}) + target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES}) +endif() diff --git a/ext/libstrawberry-tagreader/tagreaderbase.cpp b/ext/libstrawberry-tagreader/tagreaderbase.cpp new file mode 100644 index 00000000..c4de9388 --- /dev/null +++ b/ext/libstrawberry-tagreader/tagreaderbase.cpp @@ -0,0 +1,27 @@ +/* This file is part of Strawberry. + Copyright 2018-2021, Jonas Kvinge + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 . +*/ + +#include "config.h" + +#include + +#include "tagreaderbase.h" + +const std::string TagReaderBase::kEmbeddedCover = "(embedded)"; + +TagReaderBase::TagReaderBase() {} +TagReaderBase::~TagReaderBase() {} diff --git a/ext/libstrawberry-tagreader/tagreaderbase.h b/ext/libstrawberry-tagreader/tagreaderbase.h new file mode 100644 index 00000000..6e0b5a54 --- /dev/null +++ b/ext/libstrawberry-tagreader/tagreaderbase.h @@ -0,0 +1,51 @@ +/* This file is part of Strawberry. + Copyright 2018-2021, Jonas Kvinge + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 . +*/ + +#ifndef TAGREADERBASE_H +#define TAGREADERBASE_H + +#include "config.h" + +#include + +#include +#include + +#include "tagreadermessages.pb.h" + +/* + * This class holds all useful methods to read and write tags from/to files. + * You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient) + */ +class TagReaderBase { + public: + explicit TagReaderBase(); + ~TagReaderBase(); + + virtual bool IsMediaFile(const QString &filename) const = 0; + + virtual void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0; + virtual bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0; + + virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0; + virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0; + + protected: + static const std::string kEmbeddedCover; +}; + +#endif // TAGREADERBASE_H diff --git a/ext/libstrawberry-tagreader/tagreader.cpp b/ext/libstrawberry-tagreader/tagreadertaglib.cpp similarity index 94% rename from ext/libstrawberry-tagreader/tagreader.cpp rename to ext/libstrawberry-tagreader/tagreadertaglib.cpp index 32e62d44..1b3e7905 100644 --- a/ext/libstrawberry-tagreader/tagreader.cpp +++ b/ext/libstrawberry-tagreader/tagreadertaglib.cpp @@ -18,7 +18,7 @@ #include "config.h" -#include "tagreader.h" +#include "tagreadertaglib.h" #include #include @@ -132,25 +132,22 @@ const char *kASF_OriginalYear_ID = "WM/OriginalReleaseYear"; } // namespace -TagReader::TagReader() : - factory_(new TagLibFileRefFactory), - kEmbeddedCover("(embedded)") { -} +TagReaderTagLib::TagReaderTaglib() : factory_(new TagLibFileRefFactory) {} -TagReader::~TagReader() { +TagReaderTagLib::~TagReaderTaglib() { delete factory_; } -bool TagReader::IsMediaFile(const QString &filename) const { +bool TagReaderTagLib::IsMediaFile(const QString &filename) const { qLog(Debug) << "Checking for valid file" << filename; std::unique_ptr fileref(factory_->GetFileRef(filename)); - return !fileref->isNull() && fileref->tag(); + return fileref && !fileref->isNull() && fileref->file() && fileref->tag(); } -spb::tagreader::SongMetadata_FileType TagReader::GuessFileType(TagLib::FileRef *fileref) const { +spb::tagreader::SongMetadata_FileType TagReaderTagLib::GuessFileType(TagLib::FileRef *fileref) const { if (dynamic_cast(fileref->file())) return spb::tagreader::SongMetadata_FileType_WAV; if (dynamic_cast(fileref->file())) return spb::tagreader::SongMetadata_FileType_FLAC; @@ -177,7 +174,7 @@ spb::tagreader::SongMetadata_FileType TagReader::GuessFileType(TagLib::FileRef * } -void TagReader::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const { +void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const { const QByteArray url(QUrl::fromLocalFile(filename).toEncoded()); const QFileInfo info(filename); @@ -428,20 +425,20 @@ void TagReader::ReadFile(const QString &filename, spb::tagreader::SongMetadata * } -void TagReader::Decode(const TagLib::String &tag, std::string *output) { +void TagReaderTagLib::Decode(const TagLib::String &tag, std::string *output) { QString tmp = TStringToQString(tag).trimmed(); output->assign(DataCommaSizeFromQString(tmp)); } -void TagReader::Decode(const QString &tag, std::string *output) { +void TagReaderTagLib::Decode(const QString &tag, std::string *output) { output->assign(DataCommaSizeFromQString(tag)); } -void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const { +void TagReaderTagLib::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const { if (!map["COMPOSER"].isEmpty()) Decode(map["COMPOSER"].front(), song->mutable_composer()); if (!map["PERFORMER"].isEmpty()) Decode(map["PERFORMER"].front(), song->mutable_performer()); @@ -466,7 +463,7 @@ void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, } -void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const { +void TagReaderTagLib::ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const { TagLib::APE::ItemListMap::ConstIterator it = map.find("ALBUM ARTIST"); if (it != map.end()) { @@ -510,7 +507,7 @@ void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, } -void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const spb::tagreader::SongMetadata &song) const { +void TagReaderTagLib::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const spb::tagreader::SongMetadata &song) const { vorbis_comments->addField("COMPOSER", StdStringToTaglibString(song.composer()), true); vorbis_comments->addField("PERFORMER", StdStringToTaglibString(song.performer()), true); @@ -528,7 +525,7 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, con } -bool TagReader::SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const { +bool TagReaderTagLib::SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const { if (filename.isEmpty()) return false; @@ -609,7 +606,7 @@ bool TagReader::SaveFile(const QString &filename, const spb::tagreader::SongMeta return result; } -void TagReader::SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const { +void TagReaderTagLib::SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const { tag->setItem("album artist", TagLib::APE::Item("album artist", TagLib::StringList(song.albumartist().c_str()))); tag->addValue("disc", QStringToTaglibString(song.disc() <= 0 ? QString() : QString::number(song.disc())), true); @@ -621,14 +618,14 @@ void TagReader::SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMeta } -void TagReader::SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const { +void TagReaderTagLib::SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const { const QByteArray utf8(value.toUtf8()); SetTextFrame(id, std::string(utf8.constData(), utf8.length()), tag); } -void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const { +void TagReaderTagLib::SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const { TagLib::ByteVector id_vector(id); QVector frames_buffer; @@ -659,7 +656,7 @@ void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::I } -void TagReader::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const { +void TagReaderTagLib::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const { TagLib::ByteVector id_vector("USLT"); QVector frames_buffer; @@ -691,7 +688,7 @@ void TagReader::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Ta } -QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const { +QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const { if (filename.isEmpty()) return QByteArray(); @@ -792,7 +789,7 @@ QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const { } -QByteArray TagReader::LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const { +QByteArray TagReaderTagLib::LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const { QByteArray ret; @@ -810,7 +807,7 @@ QByteArray TagReader::LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) co } -bool TagReader::SaveEmbeddedArt(const QString &filename, const QByteArray &data) { +bool TagReaderTagLib::SaveEmbeddedArt(const QString &filename, const QByteArray &data) { if (filename.isEmpty()) return false; diff --git a/ext/libstrawberry-tagreader/tagreader.h b/ext/libstrawberry-tagreader/tagreadertaglib.h similarity index 85% rename from ext/libstrawberry-tagreader/tagreader.h rename to ext/libstrawberry-tagreader/tagreadertaglib.h index e517dfad..f23acf69 100644 --- a/ext/libstrawberry-tagreader/tagreader.h +++ b/ext/libstrawberry-tagreader/tagreadertaglib.h @@ -16,8 +16,8 @@ along with Strawberry. If not, see . */ -#ifndef TAGREADER_H -#define TAGREADER_H +#ifndef TAGREADERTAGLIB_H +#define TAGREADERTAGLIB_H #include "config.h" @@ -33,29 +33,31 @@ #include #include +#include "tagreaderbase.h" #include "tagreadermessages.pb.h" class FileRefFactory; -/** +/* * This class holds all useful methods to read and write tags from/to files. * You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient) */ -class TagReader { +class TagReaderTagLib : public TagReaderBase { public: - explicit TagReader(); - ~TagReader(); + explicit TagReaderTagLib(); + ~TagReaderTagLib(); - bool IsMediaFile(const QString &filename) const; + bool IsMediaFile(const QString &filename) const override; + + void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override; + bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override; + + QByteArray LoadEmbeddedArt(const QString &filename) const override; + bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override; + + private: spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const; - void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const; - bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const; - - QByteArray LoadEmbeddedArt(const QString &filename) const; - QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const; - bool SaveEmbeddedArt(const QString &filename, const QByteArray &data); - static void Decode(const TagLib::String &tag, std::string *output); static void Decode(const QString &tag, std::string *output); @@ -69,10 +71,10 @@ class TagReader { void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const; void SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const; + QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const; + private: FileRefFactory *factory_; - - const std::string kEmbeddedCover; }; -#endif // TAGREADER_H +#endif // TAGREADERTAGLIB_H diff --git a/ext/libstrawberry-tagreader/tagreadertagparser.cpp b/ext/libstrawberry-tagreader/tagreadertagparser.cpp new file mode 100644 index 00000000..1b4bfdd0 --- /dev/null +++ b/ext/libstrawberry-tagreader/tagreadertagparser.cpp @@ -0,0 +1,413 @@ +/* This file is part of Strawberry. + Copyright 2021, Jonas Kvinge + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 . +*/ + +#include "config.h" + +#include "tagreadertagparser.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/logging.h" +#include "core/messagehandler.h" +#include "core/timeconstants.h" + +TagReaderTagParser::TagReaderTagParser() {} + +TagReaderTagParser::~TagReaderTagParser() {} + +bool TagReaderTagParser::IsMediaFile(const QString &filename) const { + + qLog(Debug) << "Checking for valid file" << filename; + + QFileInfo fileinfo(filename); + if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false; + + try { + TagParser::MediaFileInfo taginfo; + TagParser::Diagnostics diag; + TagParser::AbortableProgressFeedback progress; + + taginfo.setPath(QFile::encodeName(filename).toStdString()); + taginfo.open(); + + taginfo.parseContainerFormat(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return false; + } + + taginfo.parseTracks(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return false; + } + + for (const TagParser::DiagMessage &msg : diag) { + qLog(Debug) << QString::fromStdString(msg.message()); + } + + const auto tracks = taginfo.tracks(); + for (const auto track : tracks) { + if (track->mediaType() == TagParser::MediaType::Audio) return true; + } + taginfo.close(); + } + catch(...) {} + + return false; + +} + +void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const { + + qLog(Debug) << "Reading tags from" << filename; + + const QFileInfo fileinfo(filename); + + if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return; + + const QByteArray url(QUrl::fromLocalFile(filename).toEncoded()); + + song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName())); + song->set_url(url.constData(), url.size()); + song->set_filesize(fileinfo.size()); + song->set_mtime(fileinfo.lastModified().toSecsSinceEpoch()); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + song->set_ctime(fileinfo.birthTime().isValid() ? fileinfo.birthTime().toSecsSinceEpoch() : fileinfo.lastModified().toSecsSinceEpoch()); +#else + song->set_ctime(fileinfo.created().toSecsSinceEpoch()); +#endif + song->set_lastseen(QDateTime::currentDateTime().toSecsSinceEpoch()); + + try { + TagParser::MediaFileInfo taginfo; + TagParser::Diagnostics diag; + TagParser::AbortableProgressFeedback progress; + +#ifdef Q_OS_WIN32 + taginfo.setPath(filename.toStdWString().toStdString()); +#else + taginfo.setPath(QFile::encodeName(filename).toStdString()); +#endif + + taginfo.open(); + + taginfo.parseContainerFormat(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return; + } + + taginfo.parseTracks(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return; + } + + taginfo.parseTags(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return; + } + + for (const TagParser::DiagMessage &msg : diag) { + qLog(Debug) << QString::fromStdString(msg.message()); + } + + const auto tracks = taginfo.tracks(); + for (const auto track : tracks) { + switch(track->format().general) { + case TagParser::GeneralMediaFormat::Flac: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_FLAC); + break; + case TagParser::GeneralMediaFormat::WavPack: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_WAVPACK); + break; + case TagParser::GeneralMediaFormat::MonkeysAudio: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_APE); + break; + case TagParser::GeneralMediaFormat::WindowsMediaAudio: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_ASF); + break; + case TagParser::GeneralMediaFormat::Vorbis: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGVORBIS); + break; + case TagParser::GeneralMediaFormat::Opus: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGOPUS); + break; + case TagParser::GeneralMediaFormat::Speex: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGSPEEX); + break; + case TagParser::GeneralMediaFormat::Mpeg1Audio: + switch(track->format().sub) { + case TagParser::SubFormats::Mpeg1Layer3: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_MPEG); + break; + case TagParser::SubFormats::None: + default: + break; + } + break; + case TagParser::GeneralMediaFormat::Mpc: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_MPC); + break; + case TagParser::GeneralMediaFormat::Pcm: + song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_PCM); + break; + case TagParser::GeneralMediaFormat::Unknown: + default: + break; + } + song->set_length_nanosec(track->duration().totalMilliseconds() * kNsecPerMsec); + song->set_samplerate(track->samplingFrequency()); + song->set_bitdepth(track->bitsPerSample()); + song->set_bitrate(std::max(track->bitrate(), track->maxBitrate())); + } + + if (song->filetype() == spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_UNKNOWN) { + taginfo.close(); + return; + } + + for (const auto tag : taginfo.tags()) { + song->set_albumartist(tag->value(TagParser::KnownField::AlbumArtist).toString(TagParser::TagTextEncoding::Utf8)); + song->set_artist(tag->value(TagParser::KnownField::Artist).toString(TagParser::TagTextEncoding::Utf8)); + song->set_album(tag->value(TagParser::KnownField::Album).toString(TagParser::TagTextEncoding::Utf8)); + song->set_title(tag->value(TagParser::KnownField::Title).toString(TagParser::TagTextEncoding::Utf8)); + song->set_genre(tag->value(TagParser::KnownField::Genre).toString(TagParser::TagTextEncoding::Utf8)); + song->set_composer(tag->value(TagParser::KnownField::Composer).toString(TagParser::TagTextEncoding::Utf8)); + song->set_performer(tag->value(TagParser::KnownField::Performers).toString(TagParser::TagTextEncoding::Utf8)); + song->set_grouping(tag->value(TagParser::KnownField::Grouping).toString(TagParser::TagTextEncoding::Utf8)); + song->set_comment(tag->value(TagParser::KnownField::Comment).toString(TagParser::TagTextEncoding::Utf8)); + song->set_lyrics(tag->value(TagParser::KnownField::Lyrics).toString(TagParser::TagTextEncoding::Utf8)); + song->set_year(tag->value(TagParser::KnownField::ReleaseDate).toInteger()); + song->set_originalyear(tag->value(TagParser::KnownField::RecordDate).toInteger()); + song->set_track(tag->value(TagParser::KnownField::TrackPosition).toInteger()); + song->set_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger()); + if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) { + song->set_art_automatic(kEmbeddedCover); + } + } + + // Set integer fields to -1 if they're not valid + #define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); } + SetDefault(track); + SetDefault(disc); + SetDefault(year); + SetDefault(originalyear); + SetDefault(bitrate); + SetDefault(samplerate); + SetDefault(bitdepth); + SetDefault(lastplayed); + #undef SetDefault + + song->set_valid(true); + + taginfo.close(); + } + catch(...) {} + +} + +bool TagReaderTagParser::SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const { + + if (filename.isEmpty()) return false; + + qLog(Debug) << "Saving tags to" << filename; + + try { + TagParser::MediaFileInfo taginfo; + TagParser::Diagnostics diag; + TagParser::AbortableProgressFeedback progress; +#ifdef Q_OS_WIN32 + taginfo.setPath(filename.toStdWString().toStdString()); +#else + taginfo.setPath(QFile::encodeName(filename).toStdString()); +#endif + taginfo.open(); + taginfo.parseContainerFormat(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return false; + } + taginfo.parseTags(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return false; + } + + if (taginfo.tags().size() <= 0) { + taginfo.createAppropriateTags(); + } + + for (const auto tag : taginfo.tags()) { + tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding())); + tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track())); + tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc())); + } + taginfo.applyChanges(diag, progress); + taginfo.close(); + + for (const TagParser::DiagMessage &msg : diag) { + qLog(Debug) << QString::fromStdString(msg.message()); + } + + return true; + } + catch(...) {} + + return false; + +} + +QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const { + + if (filename.isEmpty()) return QByteArray(); + + qLog(Debug) << "Loading art from" << filename; + + try { + + TagParser::MediaFileInfo taginfo; + TagParser::Diagnostics diag; + TagParser::AbortableProgressFeedback progress; + +#ifdef Q_OS_WIN32 + taginfo.setPath(filename.toStdWString().toStdString()); +#else + taginfo.setPath(QFile::encodeName(filename).toStdString()); +#endif + + taginfo.open(); + + taginfo.parseContainerFormat(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return QByteArray(); + } + + taginfo.parseTags(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return QByteArray(); + } + + for (const auto tag : taginfo.tags()) { + if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) { + QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize()); + taginfo.close(); + return data; + } + } + + taginfo.close(); + + for (const TagParser::DiagMessage &msg : diag) { + qLog(Debug) << QString::fromStdString(msg.message()); + } + + } + catch(...) {} + + return QByteArray(); + +} + +bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArray &data) { + + if (filename.isEmpty()) return false; + + qLog(Debug) << "Saving art to" << filename; + + try { + + TagParser::MediaFileInfo taginfo; + TagParser::Diagnostics diag; + TagParser::AbortableProgressFeedback progress; + +#ifdef Q_OS_WIN32 + taginfo.setPath(filename.toStdWString().toStdString()); +#else + taginfo.setPath(QFile::encodeName(filename).toStdString()); +#endif + + taginfo.open(); + + taginfo.parseContainerFormat(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return false; + } + + taginfo.parseTags(diag, progress); + if (progress.isAborted()) { + taginfo.close(); + return false; + } + + if (taginfo.tags().size() <= 0) { + taginfo.createAppropriateTags(); + } + + for (const auto tag : taginfo.tags()) { + tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString())); + } + + taginfo.applyChanges(diag, progress); + taginfo.close(); + + for (const TagParser::DiagMessage &msg : diag) { + qLog(Debug) << QString::fromStdString(msg.message()); + } + + return true; + + } + catch(...) {} + + return false; + +} diff --git a/ext/libstrawberry-tagreader/tagreadertagparser.h b/ext/libstrawberry-tagreader/tagreadertagparser.h new file mode 100644 index 00000000..999a3c61 --- /dev/null +++ b/ext/libstrawberry-tagreader/tagreadertagparser.h @@ -0,0 +1,51 @@ +/* This file is part of Strawberry. + Copyright 2021, Jonas Kvinge + + Strawberry is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + 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 . +*/ + +#ifndef TAGREADERTAGPARSER_H +#define TAGREADERTAGPARSER_H + +#include "config.h" + +#include + +#include +#include + +#include "tagreadermessages.pb.h" +#include "tagreaderbase.h" + +/* + * This class holds all useful methods to read and write tags from/to files. + * You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient) + */ +class TagReaderTagParser : public TagReaderBase { + public: + explicit TagReaderTagParser(); + ~TagReaderTagParser(); + + bool IsMediaFile(const QString &filename) const override; + + void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override; + bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override; + + QByteArray LoadEmbeddedArt(const QString &filename) const override; + bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override; + + private: +}; + +#endif // TAGREADERTAGPARSER_H diff --git a/ext/strawberry-tagreader/CMakeLists.txt b/ext/strawberry-tagreader/CMakeLists.txt index a7a761f5..6ed005da 100644 --- a/ext/strawberry-tagreader/CMakeLists.txt +++ b/ext/strawberry-tagreader/CMakeLists.txt @@ -11,17 +11,21 @@ else() qt5_wrap_cpp(MOC ${HEADERS}) endif() -link_directories( - ${GLIB_LIBRARY_DIRS} - ${TAGLIB_LIBRARY_DIRS} -) +link_directories(${GLIB_LIBRARY_DIRS}) + +if(USE_TAGLIB AND TAGLIB_FOUND) + link_directories(${TAGLIB_LIBRARY_DIRS}) +endif() + +if(USE_TAGPARSER AND TAGPARSER_FOUND) + link_directories(${TAGPARSER_LIBRARY_DIRS}) +endif() add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC}) target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${GLIB_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS} - ${TAGLIB_INCLUDE_DIRS} ) target_include_directories(strawberry-tagreader PRIVATE @@ -33,13 +37,22 @@ target_include_directories(strawberry-tagreader PRIVATE target_link_libraries(strawberry-tagreader PRIVATE ${GLIB_LIBRARIES} - ${TAGLIB_LIBRARIES} ${QtCore_LIBRARIES} ${QtNetwork_LIBRARIES} libstrawberry-common libstrawberry-tagreader ) +if(USE_TAGLIB AND TAGLIB_FOUND) + target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS}) + target_link_libraries(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES}) +endif() + +if(USE_TAGPARSER AND TAGPARSER_FOUND) + target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS}) + target_link_libraries(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES}) +endif() + if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") target_link_libraries(strawberry-tagreader PRIVATE execinfo) endif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") diff --git a/ext/strawberry-tagreader/tagreaderworker.h b/ext/strawberry-tagreader/tagreaderworker.h index f962ff02..b8753f75 100644 --- a/ext/strawberry-tagreader/tagreaderworker.h +++ b/ext/strawberry-tagreader/tagreaderworker.h @@ -24,7 +24,11 @@ #include #include "core/messagehandler.h" -#include "tagreader.h" +#if defined(USE_TAGLIB) +# include "tagreadertaglib.h" +#elif defined(USE_TAGPARSER) +# include "tagreadertagparser.h" +#endif #include "tagreadermessages.pb.h" class QIODevice; @@ -40,7 +44,11 @@ class TagReaderWorker : public AbstractMessageHandler { void DeviceClosed() override; private: - TagReader tag_reader_; +#if defined(USE_TAGLIB) + TagReaderTagLib tag_reader_; +#elif defined(USE_TAGPARSER) + TagReaderTagParser tag_reader_; +#endif }; #endif // TAGREADERWORKER_H diff --git a/src/config.h.in b/src/config.h.in index 1978026a..e73ba6e7 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -49,4 +49,7 @@ #cmakedefine ENABLE_WIN32_CONSOLE +#cmakedefine USE_TAGLIB +#cmakedefine USE_TAGPARSER + #endif // CONFIG_H_IN