Added support for reading lyrics from tags

Also fixed tagreader crash when saving tags to MP3 files
This commit is contained in:
Jonas Kvinge 2018-09-06 20:04:29 +02:00
parent 0a64a2a394
commit 1562585561
11 changed files with 148 additions and 77 deletions

View File

@ -20,6 +20,8 @@ Unreleased:
* Made xine enabled only for window debug * Made xine enabled only for window debug
* Removed dead code * Removed dead code
* Added DSF and DSDIFF/DFF support * Added DSF and DSDIFF/DFF support
* Fixed tagreader crash when saving tags to MP3 files
* Added support for reading/writing lyrics to tags
Version 0.2.1: Version 0.2.1:

View File

@ -1,7 +1,8 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>schema/schema.sql</file> <file>schema/schema.sql</file>
<file>schema/schema-1.sql</file> <file>schema/schema-1.sql</file>
<file>schema/schema-2.sql</file>
<file>schema/device-schema.sql</file> <file>schema/device-schema.sql</file>
<file>style/strawberry.css</file> <file>style/strawberry.css</file>
<file>misc/playing_tooltip.txt</file> <file>misc/playing_tooltip.txt</file>

View File

@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
DELETE FROM schema_version; DELETE FROM schema_version;
INSERT INTO schema_version (version) VALUES (1); INSERT INTO schema_version (version) VALUES (2);
CREATE TABLE IF NOT EXISTS directories ( CREATE TABLE IF NOT EXISTS directories (
path TEXT NOT NULL, path TEXT NOT NULL,
@ -35,6 +35,7 @@ CREATE TABLE IF NOT EXISTS songs (
performer TEXT NOT NULL, performer TEXT NOT NULL,
grouping TEXT NOT NULL, grouping TEXT NOT NULL,
comment TEXT NOT NULL, comment TEXT NOT NULL,
lyrics TEXT NOT NULL,
beginning INTEGER NOT NULL DEFAULT 0, beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0, length INTEGER NOT NULL DEFAULT 0,
@ -109,6 +110,7 @@ CREATE TABLE IF NOT EXISTS playlist_items (
performer TEXT NOT NULL, performer TEXT NOT NULL,
grouping TEXT NOT NULL, grouping TEXT NOT NULL,
comment TEXT NOT NULL, comment TEXT NOT NULL,
lyrics TEXT NOT NULL,
beginning INTEGER NOT NULL DEFAULT 0, beginning INTEGER NOT NULL DEFAULT 0,
length INTEGER NOT NULL DEFAULT 0, length INTEGER NOT NULL DEFAULT 0,

View File

@ -39,6 +39,7 @@
#include <taglib/textidentificationframe.h> #include <taglib/textidentificationframe.h>
#include <taglib/xiphcomment.h> #include <taglib/xiphcomment.h>
#include <taglib/commentsframe.h> #include <taglib/commentsframe.h>
#include <taglib/unsynchronizedlyricsframe.h>
#include <taglib/tag.h> #include <taglib/tag.h>
#include <taglib/id3v2tag.h> #include <taglib/id3v2tag.h>
#include "taglib/id3v2frame.h" #include "taglib/id3v2frame.h"
@ -157,15 +158,14 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
return; return;
} }
song->set_filetype(GuessFileType(fileref.get()));
if (fileref->audioProperties()) { if (fileref->audioProperties()) {
song->set_bitrate(fileref->audioProperties()->bitrate()); song->set_bitrate(fileref->audioProperties()->bitrate());
song->set_samplerate(fileref->audioProperties()->sampleRate()); song->set_samplerate(fileref->audioProperties()->sampleRate());
song->set_length_nanosec(fileref->audioProperties()->length() * kNsecPerSec); song->set_length_nanosec(fileref->audioProperties()->length() * kNsecPerSec);
} }
// Get the filetype if we can
song->set_filetype(GuessFileType(fileref.get()));
TagLib::Tag *tag = fileref->tag(); TagLib::Tag *tag = fileref->tag();
if (tag) { if (tag) {
Decode(tag->title(), nullptr, song->mutable_title()); Decode(tag->title(), nullptr, song->mutable_title());
@ -179,6 +179,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
QString disc; QString disc;
QString compilation; QString compilation;
QString lyrics;
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way; // Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
// apart, so we keep specific behavior for some formats by adding another "else if" block below. // apart, so we keep specific behavior for some formats by adding another "else if" block below.
@ -189,9 +190,29 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
if (!tag->pictureList().isEmpty()) song->set_art_automatic(kEmbeddedCover); if (!tag->pictureList().isEmpty()) song->set_art_automatic(kEmbeddedCover);
#endif #endif
} }
if (TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File *>(fileref->file())) {
song->set_bitdepth(file->audioProperties()->bitsPerSample());
if ( file->xiphComment() ) {
ParseOggTag(file->xiphComment()->fieldListMap(), nullptr, &disc, &compilation, song);
#ifdef TAGLIB_HAS_FLAC_PICTURELIST
if (!file->pictureList().isEmpty()) {
song->set_art_automatic(kEmbeddedCover);
}
#endif
}
Decode(tag->comment(), nullptr, song->mutable_comment());
}
else if (TagLib::WavPack::File *file = dynamic_cast<TagLib::WavPack::File *>(fileref->file())) {
song->set_bitdepth(file->audioProperties()->bitsPerSample());
Decode(tag->comment(), nullptr, song->mutable_comment());
}
else if (TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
if (TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
if (file->ID3v2Tag()) { if (file->ID3v2Tag()) {
const TagLib::ID3v2::FrameListMap &map = file->ID3v2Tag()->frameListMap(); const TagLib::ID3v2::FrameListMap &map = file->ID3v2Tag()->frameListMap();
@ -245,22 +266,9 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
} }
} }
else if (TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File *>(fileref->file())) {
song->set_bitdepth(file->audioProperties()->bitsPerSample());
if ( file->xiphComment() ) {
ParseOggTag(file->xiphComment()->fieldListMap(), nullptr, &disc, &compilation, song);
#ifdef TAGLIB_HAS_FLAC_PICTURELIST
if (!file->pictureList().isEmpty()) {
song->set_art_automatic(kEmbeddedCover);
}
#endif
}
Decode(tag->comment(), nullptr, song->mutable_comment());
}
else if (TagLib::MP4::File *file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) { else if (TagLib::MP4::File *file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
song->set_bitdepth(file->audioProperties()->bitsPerSample()); song->set_bitdepth(file->audioProperties()->bitsPerSample());
if (file->tag()) { if (file->tag()) {
@ -347,6 +355,8 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
song->set_compilation(compilation.toInt() == 1); song->set_compilation(compilation.toInt() == 1);
} }
if (!lyrics.isEmpty()) song->set_lyrics(lyrics.toStdString());
// Set integer fields to -1 if they're not valid // Set integer fields to -1 if they're not valid
#define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); } #define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); }
SetDefault(track); SetDefault(track);
@ -438,6 +448,11 @@ void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCod
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat()); if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat());
if (!map["LYRICS"].isEmpty())
Decode(map["LYRICS"].front(), codec, song->mutable_lyrics());
else if (!map["UNSYNCEDLYRICS"].isEmpty())
Decode(map["UNSYNCEDLYRICS"].front(), codec, song->mutable_lyrics());
} }
void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const { void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const {
@ -453,6 +468,8 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, con
vorbis_comments->addField("ALBUMARTIST", StdStringToTaglibString(song.albumartist()), true); vorbis_comments->addField("ALBUMARTIST", StdStringToTaglibString(song.albumartist()), true);
vorbis_comments->removeField("ALBUM ARTIST"); vorbis_comments->removeField("ALBUM ARTIST");
vorbis_comments->addField("LYRICS", StdStringToTaglibString(song.lyrics()), true);
vorbis_comments->removeField("UNSYNCEDLYRICS");
} }
@ -481,15 +498,11 @@ pb::tagreader::SongMetadata_Type TagReader::GuessFileType(TagLib::FileRef *filer
} }
bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const { bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const {
if (filename.isNull()) return false;
if (filename.isNull() || filename.isEmpty()) return false;
qLog(Debug) << "Saving tags to" << filename; qLog(Debug) << "Saving tags to" << filename;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));;
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); if (!fileref || fileref->isNull()) return false;
if (!fileref || fileref->isNull()) // The file probably doesn't exist
return false;
fileref->tag()->setTitle(StdStringToTaglibString(song.title())); fileref->tag()->setTitle(StdStringToTaglibString(song.title()));
fileref->tag()->setArtist(StdStringToTaglibString(song.artist())); fileref->tag()->setArtist(StdStringToTaglibString(song.artist()));
@ -501,6 +514,7 @@ bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetad
if (TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) { if (TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
TagLib::ID3v2::Tag *tag = file->ID3v2Tag(true); TagLib::ID3v2::Tag *tag = file->ID3v2Tag(true);
if (!tag) return false;
SetTextFrame("TPOS", song.disc() <= 0 -1 ? QString() : QString::number(song.disc()), tag); SetTextFrame("TPOS", song.disc() <= 0 -1 ? QString() : QString::number(song.disc()), tag);
SetTextFrame("TCOM", song.composer(), tag); SetTextFrame("TCOM", song.composer(), tag);
SetTextFrame("TIT1", song.grouping(), tag); SetTextFrame("TIT1", song.grouping(), tag);
@ -508,6 +522,7 @@ bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetad
// Skip TPE1 (which is the artist) here because we already set it // Skip TPE1 (which is the artist) here because we already set it
SetTextFrame("TPE2", song.albumartist(), tag); SetTextFrame("TPE2", song.albumartist(), tag);
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag); SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);
SetUnsyncLyricsFrame(song.lyrics(), tag);
} }
else if (TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) { else if (TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
TagLib::Ogg::XiphComment *tag = file->xiphComment(); TagLib::Ogg::XiphComment *tag = file->xiphComment();
@ -589,10 +604,15 @@ void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::I
frames_buffer.push_back(frame.render()); frames_buffer.push_back(frame.render());
} }
// add frame takes ownership and clears the memory // Update and add the frames
TagLib::ID3v2::TextIdentificationFrame *frame; for (int lyrics_index = 0; lyrics_index < frames_buffer.size(); lyrics_index++) {
frame->setText(StdStringToTaglibString(value)); TagLib::ID3v2::TextIdentificationFrame* frame = new TagLib::ID3v2::TextIdentificationFrame(frames_buffer.at(lyrics_index));
tag->addFrame(frame); if (lyrics_index == 0) {
frame->setText(StdStringToTaglibString(value));
}
// add frame takes ownership and clears the memory
tag->addFrame(frame);
}
} }
@ -619,23 +639,26 @@ QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const {
if (ref.isNull() || !ref.file()) return QByteArray(); if (ref.isNull() || !ref.file()) return QByteArray();
// MP3 #ifdef TAGLIB_HAS_FLAC_PICTURELIST
TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(ref.file()); // FLAC
if (file && file->ID3v2Tag()) { TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(ref.file());
TagLib::ID3v2::FrameList apic_frames = file->ID3v2Tag()->frameListMap()["APIC"]; if (flac_file && flac_file->xiphComment()) {
if (apic_frames.isEmpty()) TagLib::List<TagLib::FLAC::Picture*> pics = flac_file->pictureList();
return QByteArray(); if (!pics.isEmpty()) {
// Use the first picture in the file - this could be made cleverer and
// pick the front cover if it's present.
TagLib::ID3v2::AttachedPictureFrame *pic = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front()); std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin();
TagLib::FLAC::Picture *picture = *it;
return QByteArray((const char*) pic->picture().data(), pic->picture().size()); return QByteArray(picture->data().data(), picture->data().size());
}
} }
#endif
// Ogg vorbis/speex // Ogg Vorbis / Speex
TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(ref.file()->tag()); TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(ref.file()->tag());
if (xiph_comment) { if (xiph_comment) {
TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap(); TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap();
#if TAGLIB_MAJOR_VERSION <= 1 && TAGLIB_MINOR_VERSION < 11 #if TAGLIB_MAJOR_VERSION <= 1 && TAGLIB_MINOR_VERSION < 11
@ -678,22 +701,17 @@ QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const {
return QByteArray::fromBase64(map["COVERART"].toString().toCString()); return QByteArray::fromBase64(map["COVERART"].toString().toCString());
} }
#ifdef TAGLIB_HAS_FLAC_PICTURELIST // MP3
// Flac TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File*>(ref.file());
TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(ref.file()); if (file && file->ID3v2Tag()) {
if (flac_file && flac_file->xiphComment()) { TagLib::ID3v2::FrameList apic_frames = file->ID3v2Tag()->frameListMap()["APIC"];
TagLib::List<TagLib::FLAC::Picture*> pics = flac_file->pictureList(); if (apic_frames.isEmpty())
if (!pics.isEmpty()) { return QByteArray();
// Use the first picture in the file - this could be made cleverer and
// pick the front cover if it's present.
std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin(); TagLib::ID3v2::AttachedPictureFrame *pic = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front());
TagLib::FLAC::Picture *picture = *it;
return QByteArray(picture->data().data(), picture->data().size()); return QByteArray((const char*) pic->picture().data(), pic->picture().size());
}
} }
#endif
// MP4/AAC // MP4/AAC
TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(ref.file()); TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(ref.file());
@ -715,3 +733,33 @@ QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const {
return QByteArray(); return QByteArray();
} }
void TagReader::SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const {
TagLib::ByteVector id_vector("USLT");
QVector<TagLib::ByteVector> frames_buffer;
// Store and clear existing frames
while (tag->frameListMap().contains(id_vector) && tag->frameListMap()[id_vector].size() != 0) {
frames_buffer.push_back(tag->frameListMap()[id_vector].front()->render());
tag->removeFrame(tag->frameListMap()[id_vector].front());
}
// If no frames stored create empty frame
if (frames_buffer.isEmpty()) {
TagLib::ID3v2::UnsynchronizedLyricsFrame frame(TagLib::String::UTF8);
frame.setDescription("Clementine editor");
frames_buffer.push_back(frame.render());
}
// Update and add the frames
for (int lyrics_index = 0; lyrics_index < frames_buffer.size(); lyrics_index++) {
TagLib::ID3v2::UnsynchronizedLyricsFrame* frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(frames_buffer.at(lyrics_index));
if (lyrics_index == 0) {
frame->setText(StdStringToTaglibString(value));
}
// add frame takes ownership and clears the memory
tag->addFrame(frame);
}
}

View File

@ -71,6 +71,7 @@ class TagReader {
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const; void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const; 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;
private: private:

View File

@ -26,7 +26,7 @@ message SongMetadata {
} }
optional bool valid = 1; optional bool valid = 1;
optional string title = 2; optional string title = 2;
optional string album = 3; optional string album = 3;
optional string artist = 4; optional string artist = 4;
@ -41,26 +41,27 @@ message SongMetadata {
optional string performer = 13; optional string performer = 13;
optional string grouping = 14; optional string grouping = 14;
optional string comment = 15; optional string comment = 15;
optional string lyrics = 16;
optional uint64 length_nanosec = 16;
optional int32 bitrate = 17;
optional int32 samplerate = 18;
optional int32 bitdepth = 19;
optional string url = 20; optional uint64 length_nanosec = 17;
optional string basefilename = 21;
optional Type filetype = 22;
optional int32 filesize = 23;
optional int32 mtime = 24;
optional int32 ctime = 25;
optional int32 playcount = 26; optional int32 bitrate = 18;
optional int32 skipcount = 27; optional int32 samplerate = 19;
optional int32 lastplayed = 28; optional int32 bitdepth = 20;
optional bool suspicious_tags = 29; optional string url = 21;
optional string art_automatic = 30; optional string basefilename = 22;
optional Type filetype = 23;
optional int32 filesize = 24;
optional int32 mtime = 25;
optional int32 ctime = 26;
optional int32 playcount = 27;
optional int32 skipcount = 28;
optional int32 lastplayed = 29;
optional bool suspicious_tags = 30;
optional string art_automatic = 31;
} }

View File

@ -192,7 +192,7 @@ void ContextView::SongChanged(const Song &song) {
image_previous_ = image_original_; image_previous_ = image_original_;
prev_artist_ = song_playing_.artist(); prev_artist_ = song_playing_.artist();
lyrics_ = QString(); lyrics_ = song.lyrics();
song_playing_ = song; song_playing_ = song;
song_ = song; song_ = song;
UpdateSong(); UpdateSong();

View File

@ -52,7 +52,7 @@
#include "scopedtransaction.h" #include "scopedtransaction.h"
const char *Database::kDatabaseFilename = "strawberry.db"; const char *Database::kDatabaseFilename = "strawberry.db";
const int Database::kSchemaVersion = 1; const int Database::kSchemaVersion = 2;
const char *Database::kMagicAllSongsTables = "%allsongstables"; const char *Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1; int Database::sNextConnectionId = 1;

View File

@ -65,6 +65,7 @@ const QStringList OrganiseFormat::kKnownTags = QStringList() << "title"
<< "extension" << "extension"
<< "performer" << "performer"
<< "grouping" << "grouping"
<< "lyrics"
<< "originalyear"; << "originalyear";
// From http://en.wikipedia.org/wiki/8.3_filename#Directory_table // From http://en.wikipedia.org/wiki/8.3_filename#Directory_table
@ -200,6 +201,8 @@ QString OrganiseFormat::TagValue(const QString &tag, const Song &song) const {
value = song.performer(); value = song.performer();
else if (tag == "grouping") else if (tag == "grouping")
value = song.grouping(); value = song.grouping();
else if (tag == "lyrics")
value = song.lyrics();
else if (tag == "genre") else if (tag == "genre")
value = song.genre(); value = song.genre();
else if (tag == "comment") else if (tag == "comment")

View File

@ -77,6 +77,7 @@ const QStringList Song::kColumns = QStringList() << "title"
<< "performer" << "performer"
<< "grouping" << "grouping"
<< "comment" << "comment"
<< "lyrics"
<< "beginning" << "beginning"
<< "length" << "length"
@ -157,6 +158,7 @@ struct Song::Private : public QSharedData {
QString performer_; QString performer_;
QString grouping_; QString grouping_;
QString comment_; QString comment_;
QString lyrics_;
qint64 beginning_; qint64 beginning_;
qint64 end_; qint64 end_;
@ -265,6 +267,7 @@ int Song::effective_originalyear() const {
} }
const QString &Song::genre() const { return d->genre_; } const QString &Song::genre() const { return d->genre_; }
const QString &Song::comment() const { return d->comment_; } const QString &Song::comment() const { return d->comment_; }
const QString &Song::lyrics() const { return d->lyrics_; }
bool Song::is_compilation() const { bool Song::is_compilation() const {
return (d->compilation_ || d->compilation_detected_ || d->compilation_on_) && ! d->compilation_off_; return (d->compilation_ || d->compilation_detected_ || d->compilation_on_) && ! d->compilation_off_;
} }
@ -318,6 +321,7 @@ void Song::set_composer(const QString &v) { d->composer_ = v; }
void Song::set_performer(const QString &v) { d->performer_ = v; } void Song::set_performer(const QString &v) { d->performer_ = v; }
void Song::set_grouping(const QString &v) { d->grouping_ = v; } void Song::set_grouping(const QString &v) { d->grouping_ = v; }
void Song::set_comment(const QString &v) { d->comment_ = v; } void Song::set_comment(const QString &v) { d->comment_ = v; }
void Song::set_lyrics(const QString &v) { d->lyrics_ = v; }
void Song::set_beginning_nanosec(qint64 v) { d->beginning_ = qMax(0ll, v); } void Song::set_beginning_nanosec(qint64 v) { d->beginning_ = qMax(0ll, v); }
void Song::set_end_nanosec(qint64 v) { d->end_ = v; } void Song::set_end_nanosec(qint64 v) { d->end_ = v; }
@ -463,6 +467,7 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata &pb) {
d->originalyear_ = pb.originalyear(); d->originalyear_ = pb.originalyear();
d->genre_ = QStringFromStdString(pb.genre()); d->genre_ = QStringFromStdString(pb.genre());
d->comment_ = QStringFromStdString(pb.comment()); d->comment_ = QStringFromStdString(pb.comment());
d->lyrics_ = QStringFromStdString(pb.lyrics());
d->compilation_ = pb.compilation(); d->compilation_ = pb.compilation();
d->skipcount_ = pb.skipcount(); d->skipcount_ = pb.skipcount();
d->lastplayed_ = pb.lastplayed(); d->lastplayed_ = pb.lastplayed();
@ -502,6 +507,7 @@ void Song::ToProtobuf(pb::tagreader::SongMetadata *pb) const {
pb->set_composer(DataCommaSizeFromQString(d->composer_)); pb->set_composer(DataCommaSizeFromQString(d->composer_));
pb->set_performer(DataCommaSizeFromQString(d->performer_)); pb->set_performer(DataCommaSizeFromQString(d->performer_));
pb->set_grouping(DataCommaSizeFromQString(d->grouping_)); pb->set_grouping(DataCommaSizeFromQString(d->grouping_));
pb->set_lyrics(DataCommaSizeFromQString(d->lyrics_));
pb->set_track(d->track_); pb->set_track(d->track_);
pb->set_disc(d->disc_); pb->set_disc(d->disc_);
pb->set_year(d->year_); pb->set_year(d->year_);
@ -590,6 +596,9 @@ void Song::InitFromQuery(const SqlRow &q, bool reliable_metadata, int col) {
else if (Song::kColumns.value(i) == "comment") { else if (Song::kColumns.value(i) == "comment") {
d->comment_ = tostr(x); d->comment_ = tostr(x);
} }
else if (Song::kColumns.value(i) == "lyrics") {
d->comment_ = tostr(x);
}
else if (Song::kColumns.value(i) == "beginning") { else if (Song::kColumns.value(i) == "beginning") {
d->beginning_ = q.value(x).isNull() ? 0 : q.value(x).toLongLong(); d->beginning_ = q.value(x).isNull() ? 0 : q.value(x).toLongLong();
@ -929,6 +938,7 @@ void Song::BindToQuery(QSqlQuery *query) const {
query->bindValue(":performer", strval(d->performer_)); query->bindValue(":performer", strval(d->performer_));
query->bindValue(":grouping", strval(d->grouping_)); query->bindValue(":grouping", strval(d->grouping_));
query->bindValue(":comment", strval(d->comment_)); query->bindValue(":comment", strval(d->comment_));
query->bindValue(":lyrics", strval(d->lyrics_));
query->bindValue(":beginning", d->beginning_); query->bindValue(":beginning", d->beginning_);
query->bindValue(":length", intval(length_nanosec())); query->bindValue(":length", intval(length_nanosec()));
@ -1062,6 +1072,7 @@ bool Song::IsMetadataEqual(const Song &other) const {
d->originalyear_ == other.d->originalyear_ && d->originalyear_ == other.d->originalyear_ &&
d->genre_ == other.d->genre_ && d->genre_ == other.d->genre_ &&
d->comment_ == other.d->comment_ && d->comment_ == other.d->comment_ &&
d->lyrics_ == other.d->lyrics_ &&
d->compilation_ == other.d->compilation_ && d->compilation_ == other.d->compilation_ &&
d->beginning_ == other.d->beginning_ && d->beginning_ == other.d->beginning_ &&
length_nanosec() == other.length_nanosec() && length_nanosec() == other.length_nanosec() &&

View File

@ -167,6 +167,7 @@ class Song {
const QString &performer() const; const QString &performer() const;
const QString &grouping() const; const QString &grouping() const;
const QString &comment() const; const QString &comment() const;
const QString &lyrics() const;
int playcount() const; int playcount() const;
int skipcount() const; int skipcount() const;
@ -250,7 +251,8 @@ class Song {
void set_performer(const QString &v); void set_performer(const QString &v);
void set_grouping(const QString &v); void set_grouping(const QString &v);
void set_comment(const QString &v); void set_comment(const QString &v);
void set_lyrics(const QString &v);
void set_beginning_nanosec(qint64 v); void set_beginning_nanosec(qint64 v);
void set_end_nanosec(qint64 v); void set_end_nanosec(qint64 v);
void set_length_nanosec(qint64 v); void set_length_nanosec(qint64 v);