1
0
mirror of https://github.com/strawberrymusicplayer/strawberry synced 2025-02-02 10:36:45 +01:00

Initial support for GME's VGM/SPC playback and tag management.

Co-Authored-By: Jonas Kvinge <jonas@jkvinge.net>
This commit is contained in:
Eoin O'Neill 2022-08-01 00:41:12 -07:00 committed by Jonas Kvinge
parent 6bc46e4598
commit 80da565609
17 changed files with 518 additions and 52 deletions

View File

@ -35,9 +35,6 @@
class QIODevice;
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
// Reads and writes uint32 length encoded protobufs to a socket.
// This base QObject is separate from AbstractMessageHandler because moc can't handle templated classes.
// Use AbstractMessageHandler instead.

View File

@ -4,7 +4,7 @@ set(MESSAGES tagreadermessages.proto)
set(SOURCES tagreaderbase.cpp)
if(USE_TAGLIB AND TAGLIB_FOUND)
list(APPEND SOURCES tagreadertaglib.cpp)
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
endif()
if(USE_TAGPARSER AND TAGPARSER_FOUND)

View File

@ -26,6 +26,12 @@ const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
TagReaderBase::TagReaderBase() = default;
TagReaderBase::~TagReaderBase() = default;
void TagReaderBase::Decode(const QString &tag, std::string *output) {
output->assign(DataCommaSizeFromQString(tag));
}
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
if (POPM_rating < 0x01) return 0.0F;

View File

@ -27,6 +27,9 @@
#include "tagreadermessages.pb.h"
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
/*
* 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)
@ -38,7 +41,7 @@ class TagReaderBase {
virtual bool IsMediaFile(const QString &filename) const = 0;
virtual void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
virtual bool 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;
@ -47,6 +50,8 @@ class TagReaderBase {
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
static void Decode(const QString &tag, std::string *output);
static float ConvertPOPMRating(const int POPM_rating);
static int ConvertToPOPMRating(const float rating);

View File

@ -0,0 +1,299 @@
/*
* 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 <tag.h>
#include <apefile.h>
#include <QByteArray>
#include <QString>
#include <QChar>
#include <QFileInfo>
#include <QFile>
#include <QTextStream>
#include "core/logging.h"
#include "core/timeconstants.h"
#include "core/messagehandler.h"
#include "tagreaderbase.h"
#include "tagreadertaglib.h"
bool GME::IsSupportedFormat(const QFileInfo &file_info) {
return file_info.exists() && (file_info.completeSuffix().endsWith("spc") || file_info.completeSuffix().endsWith("vgm"));
}
bool GME::ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
if (file_info.completeSuffix().endsWith("spc")) {
SPC::Read(file_info, song_info);
return true;
}
if (file_info.completeSuffix().endsWith("vgm")) {
VGM::Read(file_info, song_info);
return true;
}
return false;
}
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;
}
void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
QFile file(file_info.filePath());
if (!file.open(QIODevice::ReadOnly)) return;
qLog(Debug) << "Reading tags from SPC file" << file_info.fileName();
// Check for header -- more reliable than file name alone.
if (!file.read(33).startsWith(QString("SNES-SPC700").toLatin1())) return;
// 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);
bool has_id6 = (file.read(1)[0] == static_cast<char>(xID6_STATUS::ON));
file.seek(SONG_TITLE_OFFSET);
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
file.seek(GAME_TITLE_OFFSET);
song_info->set_album(QString::fromLatin1(file.read(32)).toStdString());
file.seek(ARTIST_OFFSET);
song_info->set_artist(QString::fromLatin1(file.read(32)).toStdString());
file.seek(INTRO_LENGTH_OFFSET);
QByteArray length_bytes = file.read(INTRO_LENGTH_SIZE);
quint64 length_in_sec = 0;
if (length_bytes.size() >= INTRO_LENGTH_SIZE) {
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_info->set_length_nanosec(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);
}
}
// 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) == QString("xid6").toLatin1()) {
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) << file_info.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 = 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(file_info.filePath().toStdString().data());
if (ape.hasAPETag()) {
TagLib::Tag *tag = ape.tag();
if (!tag) return;
TagReaderTagLib::Decode(tag->artist(), song_info->mutable_artist());
TagReaderTagLib::Decode(tag->album(), song_info->mutable_album());
TagReaderTagLib::Decode(tag->title(), song_info->mutable_title());
TagReaderTagLib::Decode(tag->genre(), song_info->mutable_genre());
song_info->set_track(tag->track());
song_info->set_year(tag->year());
}
song_info->set_valid(true);
song_info->set_filetype(spb::tagreader::SongMetadata_FileType_SPC);
}
qint16 GME::SPC::GetNextMemAddressAlign32bit(qint16 input) {
return ((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;
}
void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
QFile file(file_info.filePath());
if (!file.open(QIODevice::ReadOnly)) return;
qLog(Debug) << "Reading tags from VGM file" << file_info.fileName();
if (!file.read(4).startsWith(QString("Vgm ").toLatin1())) return;
file.seek(GD3_TAG_PTR);
QByteArray gd3_head = file.read(4);
if (gd3_head.size() < 4) return;
quint64 pt = GME::UnpackBytes32(gd3_head, 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;
file.seek(GD3_TAG_PTR + pt);
QByteArray gd3_version = file.read(4);
file.seek(file.pos() + 4);
QByteArray gd3_length_bytes = file.read(4);
quint32 gd3_length = GME::UnpackBytes32(gd3_length_bytes, 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.
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
fileTagStream.setEncoding(QStringConverter::Utf16);
#else
fileTagStream.setCodec("UTF-8");
#endif
QStringList strings = fileTagStream.readLine(0).split(QChar('\0'));
if (strings.count() < 10) return;
// VGM standard dictates string tag data exist in specific order.
// Order alternates between English and Japanese version of data.
// Read GD3 tag standard for more details.
song_info->set_title(strings[0].toStdString());
song_info->set_album(strings[2].toStdString());
song_info->set_artist(strings[6].toStdString());
song_info->set_year(strings[8].left(4).toInt());
song_info->set_length_nanosec(length * kNsecPerMsec);
song_info->set_valid(true);
song_info->set_filetype(spb::tagreader::SongMetadata_FileType_VGM);
}
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, sample_count_bytes.size());
if (sample_count <= 0) return false;
quint64 loop_sample_count = GME::UnpackBytes32(loop_count_bytes, 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;
TagReaderGME::~TagReaderGME() = default;
bool TagReaderGME::IsMediaFile(const QString &filename) const {
QFileInfo fileinfo(filename);
return GME::IsSupportedFormat(fileinfo);
}
bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
QFileInfo fileinfo(filename);
return GME::ReadFile(fileinfo, song);
}
bool TagReaderGME::SaveFile(const QString&, const spb::tagreader::SongMetadata&) const {
return false;
}
QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const {
return QByteArray();
}
bool TagReaderGME::SaveEmbeddedArt(const QString&, const QByteArray&) {
return false;
}
bool TagReaderGME::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const {
return false;
}
bool TagReaderGME::SaveSongRatingToFile(const QString&, const spb::tagreader::SongMetadata&) const {
return false;
}

View File

@ -0,0 +1,111 @@
/*
* 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 <taglib/tstring.h>
#include <QByteArray>
#include <QString>
#include <QFileInfo>
#include "tagreaderbase.h"
#include "tagreadermessages.pb.h"
namespace GME {
bool IsSupportedFormat(const QFileInfo &file_info);
bool ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
uint32_t UnpackBytes32(const char *const arr, 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 xID6_STATUS {
ON = 0x26,
OFF = 0x27,
};
enum xID6_ID { SongName = 0x01, GameName = 0x02, ArtistName = 0x03 };
enum xID6_TYPE { Length = 0x0, String = 0x1, Integer = 0x4 };
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
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;
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
// 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();
~TagReaderGME();
bool IsMediaFile(const QString &filename) const override;
bool 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;
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
};
#endif

View File

@ -27,6 +27,8 @@ message SongMetadata {
S3M = 19;
XM = 20;
IT = 21;
SPC = 22;
VGM = 23;
CDDA = 90;
STREAM = 91;
}

View File

@ -186,7 +186,7 @@ spb::tagreader::SongMetadata_FileType TagReaderTagLib::GuessFileType(TagLib::Fil
}
void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
const QFileInfo fileinfo(filename);
@ -212,7 +212,7 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (fileref->isNull()) {
qLog(Info) << "TagLib hasn't been able to read" << filename << "file";
return;
return false;
}
song->set_filetype(GuessFileType(fileref.get()));
@ -254,9 +254,7 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
}
if (TagLib::FLAC::File *file_flac = dynamic_cast<TagLib::FLAC::File *>(fileref->file())) {
song->set_bitdepth(file_flac->audioProperties()->bitsPerSample());
if (file_flac->xiphComment()) {
ParseOggTag(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
TagLib::List<TagLib::FLAC::Picture*> pictures = file_flac->pictureList();
@ -517,6 +515,8 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (song->bitrate() <= 0) { song->set_bitrate(-1); }
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
return song->filetype() != spb::tagreader::SongMetadata_FileType_UNKNOWN;
}
void TagReaderTagLib::Decode(const TagLib::String &tag, std::string *output) {
@ -526,12 +526,6 @@ void TagReaderTagLib::Decode(const TagLib::String &tag, std::string *output) {
}
void TagReaderTagLib::Decode(const QString &tag, std::string *output) {
output->assign(DataCommaSizeFromQString(tag));
}
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());

View File

@ -50,7 +50,7 @@ class TagReaderTagLib : public TagReaderBase {
bool IsMediaFile(const QString &filename) const override;
void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
bool 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;
@ -59,12 +59,11 @@ class TagReaderTagLib : public TagReaderBase {
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
static void Decode(const TagLib::String &tag, std::string *output);
private:
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
static void Decode(const TagLib::String &tag, std::string *output);
static void Decode(const QString &tag, std::string *output);
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;

View File

@ -93,13 +93,13 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
}
void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
bool 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;
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
@ -135,19 +135,19 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
taginfo.parseContainerFormat(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return;
return false;
}
taginfo.parseTracks(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return;
return false;
}
taginfo.parseTags(diag, progress);
if (progress.isAborted()) {
taginfo.close();
return;
return false;
}
for (const TagParser::DiagMessage &msg : diag) {
@ -206,7 +206,7 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
if (song->filetype() == spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_UNKNOWN) {
taginfo.close();
return;
return false;
}
for (const auto tag : taginfo.tags()) {
@ -247,8 +247,12 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
taginfo.close();
return true;
}
catch(...) {
return false;
}
catch(...) {}
}

View File

@ -39,7 +39,7 @@ class TagReaderTagParser : public TagReaderBase {
bool IsMediaFile(const QString &filename) const override;
void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
bool 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;

View File

@ -34,28 +34,11 @@ void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
spb::tagreader::Message reply;
if (message.has_is_media_file_request()) {
reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
}
else if (message.has_read_file_request()) {
tag_reader_.ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
}
else if (message.has_save_file_request()) {
reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata()));
}
else if (message.has_load_embedded_art_request()) {
QByteArray data = tag_reader_.LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
}
else if (message.has_save_embedded_art_request()) {
reply.mutable_save_embedded_art_response()->set_success(tag_reader_.SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), static_cast<qint64>(message.save_embedded_art_request().data().size()))));
}
else if (message.has_save_song_playcount_to_file_request()) {
reply.mutable_save_song_playcount_to_file_response()->set_success(tag_reader_.SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata()));
}
else if (message.has_save_song_rating_to_file_request()) {
reply.mutable_save_song_rating_to_file_response()->set_success(tag_reader_.SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata()));
bool success = HandleMessage(message, reply, &tag_reader_);
if (!success) {
#if defined(USE_TAGLIB)
HandleMessage(message, reply, &tag_reader_gme_);
#endif
}
SendReply(message, &reply);
@ -63,7 +46,50 @@ void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
}
void TagReaderWorker::DeviceClosed() {
AbstractMessageHandler<spb::tagreader::Message>::DeviceClosed();
QCoreApplication::exit();
}
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
if (message.has_is_media_file_request()) {
bool success = reader->IsMediaFile(QStringFromStdString(message.is_media_file_request().filename()));
reply.mutable_is_media_file_response()->set_success(success);
return success;
}
else if (message.has_read_file_request()) {
bool success = reader->ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
return success;
}
else if (message.has_save_file_request()) {
bool success = reader->SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata());
reply.mutable_save_file_response()->set_success(success);
return success;
}
else if (message.has_load_embedded_art_request()) {
QByteArray data = reader->LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
return true;
}
else if (message.has_save_embedded_art_request()) {
bool success = reader->SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), static_cast<qint64>(message.save_embedded_art_request().data().size())));
reply.mutable_save_embedded_art_response()->set_success(success);
return success;
}
else if (message.has_save_song_playcount_to_file_request()) {
bool success = reader->SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata());
reply.mutable_save_song_playcount_to_file_response()->set_success(success);
return success;
}
else if (message.has_save_song_rating_to_file_request()) {
bool success = reader->SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata());
reply.mutable_save_song_rating_to_file_response()->set_success(success);
return success;
}
return false;
}

View File

@ -26,9 +26,11 @@
#include "core/messagehandler.h"
#if defined(USE_TAGLIB)
# include "tagreadertaglib.h"
# include "tagreadergme.h"
#elif defined(USE_TAGPARSER)
# include "tagreadertagparser.h"
#endif
#include "tagreadermessages.pb.h"
class QIODevice;
@ -44,8 +46,12 @@ class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
void DeviceClosed() override;
private:
// Handle message using specific TagReaderBase implementation. Returns true on successful message handle.
bool HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase* reader);
#if defined(USE_TAGLIB)
TagReaderTagLib tag_reader_;
TagReaderGME tag_reader_gme_;
#elif defined(USE_TAGPARSER)
TagReaderTagParser tag_reader_;
#endif

View File

@ -52,7 +52,6 @@
#include <QtDebug>
#include "core/logging.h"
#include "core/messagehandler.h"
#include "core/iconloader.h"
#include "engine/enginebase.h"
@ -65,6 +64,9 @@
#include "sqlrow.h"
#include "tagreadermessages.pb.h"
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
const QStringList Song::kColumns = QStringList() << "title"
<< "album"
<< "artist"
@ -156,7 +158,7 @@ const QStringList Song::kArticles = QStringList() << "the " << "a " << "an ";
const QStringList Song::kAcceptedExtensions = QStringList() << "wav" << "flac" << "wv" << "ogg" << "oga" << "opus" << "spx" << "ape" << "mpc"
<< "mp2" << "mp3" << "m4a" << "mp4" << "aac" << "asf" << "asx" << "wma"
<< "aif << aiff" << "mka" << "tta" << "dsf" << "dsd"
<< "ac3" << "dts";
<< "ac3" << "dts" << "spc" << "vgm";
struct Song::Private : public QSharedData {
@ -604,6 +606,8 @@ QString Song::TextForFiletype(FileType filetype) {
case Song::FileType_XM: return "Module Music Format";
case Song::FileType_IT: return "Module Music Format";
case Song::FileType_CDDA: return "CDDA";
case Song::FileType_SPC: return "SNES SPC700";
case Song::FileType_VGM: return "VGM";
case Song::FileType_Stream: return "Stream";
case Song::FileType_Unknown:
default: return QObject::tr("Unknown");
@ -634,6 +638,8 @@ QString Song::ExtensionForFiletype(FileType filetype) {
case Song::FileType_S3M: return "s3m";
case Song::FileType_XM: return "xm";
case Song::FileType_IT: return "it";
case Song::FileType_SPC: return "spc";
case Song::FileType_VGM: return "vgm";
case Song::FileType_Unknown:
default: return "dat";
}
@ -710,6 +716,8 @@ Song::FileType Song::FiletypeByMimetype(const QString &mimetype) {
else if (mimetype.compare("audio/x-ape", Qt::CaseInsensitive) == 0 || mimetype.compare("application/x-ape", Qt::CaseInsensitive) == 0 || mimetype.compare("audio/x-ffmpeg-parsed-ape", Qt::CaseInsensitive) == 0) return Song::FileType_APE;
else if (mimetype.compare("audio/x-mod", Qt::CaseInsensitive) == 0) return Song::FileType_MOD;
else if (mimetype.compare("audio/x-s3m", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
else if (mimetype.compare("audio/x-spc", Qt::CaseInsensitive) == 0) return Song::FileType_SPC;
else if (mimetype.compare("audio/x-vgm", Qt::CaseInsensitive) == 0) return Song::FileType_VGM;
else return Song::FileType_Unknown;
@ -733,6 +741,8 @@ Song::FileType Song::FiletypeByDescription(const QString &text) {
else if (text.compare("audio/x-ffmpeg-parsed-ape", Qt::CaseInsensitive) == 0) return Song::FileType_APE;
else if (text.compare("Module Music Format (MOD)", Qt::CaseInsensitive) == 0) return Song::FileType_MOD;
else if (text.compare("Module Music Format (MOD)", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
else if (text.compare("SNES SPC700", Qt::CaseInsensitive) == 0) return Song::FileType_SPC;
else if (text.compare("VGM", Qt::CaseInsensitive) == 0) return Song::FileType_VGM;
else return Song::FileType_Unknown;
}
@ -760,6 +770,8 @@ Song::FileType Song::FiletypeByExtension(const QString &ext) {
else if (ext.compare("s3m", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
else if (ext.compare("xm", Qt::CaseInsensitive) == 0) return Song::FileType_XM;
else if (ext.compare("it", Qt::CaseInsensitive) == 0) return Song::FileType_IT;
else if (ext.compare("spc", Qt::CaseInsensitive) == 0) return Song::FileType_SPC;
else if (ext.compare("vgm", Qt::CaseInsensitive) == 0) return Song::FileType_VGM;
else return Song::FileType_Unknown;

View File

@ -106,6 +106,8 @@ class Song {
FileType_S3M = 19,
FileType_XM = 20,
FileType_IT = 21,
FileType_SPC = 22,
FileType_VGM = 23,
FileType_CDDA = 90,
FileType_Stream = 91,
};

View File

@ -39,6 +39,8 @@
#include "tagreaderclient.h"
#include "settings/collectionsettingspage.h"
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
const char *TagReaderClient::kWorkerExecutableName = "strawberry-tagreader";
TagReaderClient *TagReaderClient::sInstance = nullptr;

View File

@ -52,7 +52,8 @@ const char *FileView::kFileFilter =
"*.aif *.aiff *.mka *.tta *.dsf *.dsd "
"*.cue *.m3u *.m3u8 *.pls *.xspf *.asxini "
"*.ac3 *.dts "
"*.mod *.s3m *.xm *.it";
"*.mod *.s3m *.xm *.it"
"*.spc *.vgm";
FileView::FileView(QWidget *parent)
: QWidget(parent),