IDv3 tag lyrics support.

Squashed the following commits:

5c723ad commit: Fix: Includes alpha sort
15ac350 commit: Fix: Updated Database::kSchemaVersion to 49.
767a26a commit: Fixed small code style issue. Added schema-49 to data.qrc instead schema-48
bf6aa64 commit: fixup! Modified async handling of CollapsibleInfoPane as recommended by Andreas. Display of IDv2 tag lyrics works now.
c1f97e9 commit: fixup! Added support to read/display the ID tag lyrics in MP3 files:
c946b1d commit: Added support to read/display the ID tag lyrics in MP3 files:

-Added schema to the database to store it
-Added readers/writers for ID tags
-Added readers/writers for the database to the song class
-Added the taglyricsinfoprovider to show the lyrics in songinfo
This commit is contained in:
Martin Babutzka 2015-04-10 21:05:07 +02:00
parent f533b2998c
commit 258ae281d8
18 changed files with 138 additions and 27 deletions

View File

@ -387,6 +387,7 @@
<file>schema/schema-46.sql</file>
<file>schema/schema-47.sql</file>
<file>schema/schema-48.sql</file>
<file>schema/schema-49.sql</file>
<file>schema/schema-4.sql</file>
<file>schema/schema-5.sql</file>
<file>schema/schema-6.sql</file>

View File

@ -0,0 +1,3 @@
ALTER TABLE %allsongstables ADD COLUMN lyrics TEXT;
UPDATE schema_version SET version=49;

View File

@ -143,6 +143,7 @@ void TagReader::ReadFile(const QString& filename,
QString disc;
QString compilation;
QString lyrics;
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same
// way;
@ -188,6 +189,12 @@ void TagReader::ReadFile(const QString& filename,
compilation =
TStringToQString(map["TCMP"].front()->toString()).trimmed();
if (!map["USLT"].isEmpty()) {
lyrics = TStringToQString((map["USLT"].front())->toString()).trimmed();
qLog(Debug) << "Read ULST lyrics " << lyrics;
} else if (!map["SYLT"].isEmpty())
lyrics = TStringToQString((map["SYLT"].front())->toString()).trimmed();
if (!map["APIC"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
// Find a suitable comment tag. For now we ignore iTunNORM comments.
@ -369,6 +376,8 @@ void TagReader::ReadFile(const QString& filename,
song->set_compilation(compilation.toInt() == 1);
}
if (!lyrics.isEmpty()) song->set_lyrics(lyrics.toStdString());
if (fileref->audioProperties()) {
song->set_bitrate(fileref->audioProperties()->bitrate());
song->set_samplerate(fileref->audioProperties()->sampleRate());
@ -617,6 +626,7 @@ bool TagReader::SaveFile(const QString& filename,
SetTextFrame("TCOM", song.composer(), tag);
SetTextFrame("TIT1", song.grouping(), tag);
SetTextFrame("TOPE", song.performer(), tag);
SetTextFrame("USLT", song.lyrics(), tag);
// Skip TPE1 (which is the artist) here because we already set it
SetTextFrame("TPE2", song.albumartist(), tag);
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);

View File

@ -51,6 +51,7 @@ message SongMetadata {
optional string etag = 30;
optional string performer = 31;
optional string grouping = 32;
optional string lyrics = 33;
}
message ReadFileRequest {

View File

@ -313,6 +313,7 @@ set(SOURCES
songinfo/songkickconcerts.cpp
songinfo/songkickconcertwidget.cpp
songinfo/songplaystats.cpp
songinfo/taglyricsinfoprovider.cpp
songinfo/ultimatelyricslyric.cpp
songinfo/ultimatelyricsprovider.cpp
songinfo/ultimatelyricsreader.cpp
@ -605,6 +606,7 @@ set(HEADERS
songinfo/songkickconcerts.h
songinfo/songkickconcertwidget.h
songinfo/songplaystats.h
songinfo/taglyricsinfoprovider.h
songinfo/ultimatelyricslyric.h
songinfo/ultimatelyricsprovider.h
songinfo/ultimatelyricsreader.h

View File

@ -47,7 +47,7 @@
#include <QVariant>
const char* Database::kDatabaseFilename = "clementine.db";
const int Database::kSchemaVersion = 48;
const int Database::kSchemaVersion = 49;
const char* Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1;

View File

@ -32,11 +32,12 @@
#include "globalsearch/searchprovider.h"
#include "internet/digitally/digitallyimportedclient.h"
#include "internet/core/geolocator.h"
#include "internet/podcasts/podcastepisode.h"
#include "internet/podcasts/podcast.h"
#include "internet/somafm/somafmservice.h"
#include "library/directory.h"
#include "playlist/playlist.h"
#include "internet/podcasts/podcastepisode.h"
#include "internet/podcasts/podcast.h"
#include "songinfo/collapsibleinfopane.h"
#include "ui/equalizer.h"
#ifdef HAVE_VK
@ -53,6 +54,7 @@ class GstEnginePipeline;
class QNetworkReply;
void RegisterMetaTypes() {
qRegisterMetaType<CollapsibleInfoPane::Data>("CollapsibleInfoPane::Data");
qRegisterMetaType<ColumnAlignmentMap>("ColumnAlignmentMap");
qRegisterMetaType<const char*>("const char*");
qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
@ -74,16 +76,16 @@ void RegisterMetaTypes() {
qRegisterMetaType<PlaylistItemPtr>("PlaylistItemPtr");
qRegisterMetaType<PodcastEpisodeList>("PodcastEpisodeList");
qRegisterMetaType<PodcastList>("PodcastList");
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
qRegisterMetaType<QList<PlaylistItemPtr> >("QList<PlaylistItemPtr>");
qRegisterMetaType<QList<CoverSearchResult>>("QList<CoverSearchResult>");
qRegisterMetaType<QList<PlaylistItemPtr>>("QList<PlaylistItemPtr>");
qRegisterMetaType<PlaylistSequence::RepeatMode>(
"PlaylistSequence::RepeatMode");
qRegisterMetaType<PlaylistSequence::ShuffleMode>(
"PlaylistSequence::ShuffleMode");
qRegisterMetaType<QList<PodcastEpisode> >("QList<PodcastEpisode>");
qRegisterMetaType<QList<Podcast> >("QList<Podcast>");
qRegisterMetaType<QList<QNetworkCookie> >("QList<QNetworkCookie>");
qRegisterMetaType<QList<Song> >("QList<Song>");
qRegisterMetaType<QList<PodcastEpisode>>("QList<PodcastEpisode>");
qRegisterMetaType<QList<Podcast>>("QList<Podcast>");
qRegisterMetaType<QList<QNetworkCookie>>("QList<QNetworkCookie>");
qRegisterMetaType<QList<Song>>("QList<Song>");
qRegisterMetaType<QNetworkCookie>("QNetworkCookie");
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
@ -97,12 +99,12 @@ void RegisterMetaTypes() {
qRegisterMetaTypeStreamOperators<DigitallyImportedClient::Channel>(
"DigitallyImportedClient::Channel");
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
qRegisterMetaTypeStreamOperators<QMap<int, int> >("ColumnAlignmentMap");
qRegisterMetaTypeStreamOperators<QMap<int, int>>("ColumnAlignmentMap");
qRegisterMetaTypeStreamOperators<SomaFMService::Stream>(
"SomaFMService::Stream");
qRegisterMetaType<SubdirectoryList>("SubdirectoryList");
qRegisterMetaType<Subdirectory>("Subdirectory");
qRegisterMetaType<QList<QUrl> >("QList<QUrl>");
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
#ifdef HAVE_VK
qRegisterMetaType<MusicOwner>("MusicOwner");
@ -113,7 +115,7 @@ void RegisterMetaTypes() {
qDBusRegisterMetaType<QImage>();
qDBusRegisterMetaType<TrackMetadata>();
qDBusRegisterMetaType<TrackIds>();
qDBusRegisterMetaType<QList<QByteArray> >();
qDBusRegisterMetaType<QList<QByteArray>>();
qDBusRegisterMetaType<MprisPlaylist>();
qDBusRegisterMetaType<MaybePlaylist>();
qDBusRegisterMetaType<MprisPlaylistList>();

View File

@ -331,6 +331,7 @@ QVariantMap Mpris1::GetMetadata(const Song& song) {
AddMetadata("composer", song.composer(), &ret);
AddMetadata("performer", song.performer(), &ret);
AddMetadata("grouping", song.grouping(), &ret);
AddMetadata("lyrics", song.lyrics(), &ret);
if (song.rating() != -1.0) {
AddMetadata("rating", song.rating() * 5, &ret);
}

View File

@ -50,7 +50,8 @@ const QStringList OrganiseFormat::kKnownTags = QStringList() << "title"
<< "samplerate"
<< "extension"
<< "performer"
<< "grouping";
<< "grouping"
<< "lyrics";
// From http://en.wikipedia.org/wiki/8.3_filename#Directory_table
const char OrganiseFormat::kInvalidFatCharacters[] = "\"*/\\:<>?|";
@ -191,6 +192,8 @@ QString OrganiseFormat::TagValue(const QString& tag, const Song& song) const {
value = song.performer();
else if (tag == "grouping")
value = song.grouping();
else if (tag == "lyrics")
value = song.lyrics();
else if (tag == "genre")
value = song.genre();
else if (tag == "comment")

View File

@ -111,7 +111,8 @@ const QStringList Song::kColumns = QStringList() << "title"
<< "effective_albumartist"
<< "etag"
<< "performer"
<< "grouping";
<< "grouping"
<< "lyrics";
const QString Song::kColumnSpec = Song::kColumns.join(", ");
const QString Song::kBindSpec =
@ -151,6 +152,7 @@ struct Song::Private : public QSharedData {
QString composer_;
QString performer_;
QString grouping_;
QString lyrics_;
int track_;
int disc_;
float bpm_;
@ -278,6 +280,7 @@ const QString& Song::playlist_albumartist() const {
const QString& Song::composer() const { return d->composer_; }
const QString& Song::performer() const { return d->performer_; }
const QString& Song::grouping() const { return d->grouping_; }
const QString& Song::lyrics() const { return d->lyrics_; }
int Song::track() const { return d->track_; }
int Song::disc() const { return d->disc_; }
float Song::bpm() const { return d->bpm_; }
@ -334,6 +337,7 @@ void Song::set_albumartist(const QString& v) { d->albumartist_ = v; }
void Song::set_composer(const QString& v) { d->composer_ = v; }
void Song::set_performer(const QString& v) { d->performer_ = v; }
void Song::set_grouping(const QString& v) { d->grouping_ = v; }
void Song::set_lyrics(const QString& v) { d->lyrics_ = v; }
void Song::set_track(int v) { d->track_ = v; }
void Song::set_disc(int v) { d->disc_ = v; }
void Song::set_bpm(float v) { d->bpm_ = v; }
@ -490,6 +494,7 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata& pb) {
d->composer_ = QStringFromStdString(pb.composer());
d->performer_ = QStringFromStdString(pb.performer());
d->grouping_ = QStringFromStdString(pb.grouping());
d->lyrics_ = QStringFromStdString(pb.lyrics());
d->track_ = pb.track();
d->disc_ = pb.disc();
d->bpm_ = pb.bpm();
@ -535,6 +540,7 @@ void Song::ToProtobuf(pb::tagreader::SongMetadata* pb) const {
pb->set_composer(DataCommaSizeFromQString(d->composer_));
pb->set_performer(DataCommaSizeFromQString(d->performer_));
pb->set_grouping(DataCommaSizeFromQString(d->grouping_));
pb->set_lyrics(DataCommaSizeFromQString(d->lyrics_));
pb->set_track(d->track_);
pb->set_disc(d->disc_);
pb->set_bpm(d->bpm_);
@ -557,7 +563,7 @@ void Song::ToProtobuf(pb::tagreader::SongMetadata* pb) const {
pb->set_filesize(d->filesize_);
pb->set_suspicious_tags(d->suspicious_tags_);
pb->set_art_automatic(DataCommaSizeFromQString(d->art_automatic_));
pb->set_type(static_cast< ::pb::tagreader::SongMetadata_Type>(d->filetype_));
pb->set_type(static_cast<::pb::tagreader::SongMetadata_Type>(d->filetype_));
}
void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
@ -625,6 +631,7 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
d->performer_ = tostr(col + 38);
d->grouping_ = tostr(col + 39);
d->lyrics_ = tostr(col + 40);
InitArtManual();
@ -910,9 +917,8 @@ void Song::BindToQuery(QSqlQuery* query) const {
if (Application::kIsPortable &&
Utilities::UrlOnSameDriveAsClementine(d->url_)) {
query->bindValue(":filename",
Utilities::
GetRelativePathToClementineBin(d->url_).toEncoded());
query->bindValue(":filename", Utilities::GetRelativePathToClementineBin(
d->url_).toEncoded());
} else {
query->bindValue(":filename", d->url_.toEncoded());
}
@ -950,6 +956,7 @@ void Song::BindToQuery(QSqlQuery* query) const {
query->bindValue(":performer", strval(d->performer_));
query->bindValue(":grouping", strval(d->grouping_));
query->bindValue(":lyrics", strval(d->lyrics_));
#undef intval
#undef notnullintval
@ -1058,7 +1065,8 @@ bool Song::IsMetadataEqual(const Song& other) const {
d->samplerate_ == other.d->samplerate_ &&
d->art_automatic_ == other.d->art_automatic_ &&
d->art_manual_ == other.d->art_manual_ &&
d->cue_path_ == other.d->cue_path_;
d->cue_path_ == other.d->cue_path_ &&
d->lyrics_ == other.d->lyrics_;
}
bool Song::IsEditable() const {

View File

@ -171,6 +171,7 @@ class Song {
const QString& composer() const;
const QString& performer() const;
const QString& grouping() const;
const QString& lyrics() const;
int track() const;
int disc() const;
float bpm() const;
@ -249,6 +250,7 @@ class Song {
void set_composer(const QString& v);
void set_performer(const QString& v);
void set_grouping(const QString& v);
void set_lyrics(const QString& v);
void set_track(int v);
void set_disc(int v);
void set_bpm(float v);

View File

@ -32,10 +32,13 @@ SongInfoFetcher::SongInfoFetcher(QObject* parent)
void SongInfoFetcher::AddProvider(SongInfoProvider* provider) {
providers_ << provider;
connect(provider, SIGNAL(ImageReady(int, QUrl)), SLOT(ImageReady(int, QUrl)));
connect(provider, SIGNAL(ImageReady(int, QUrl)), SLOT(ImageReady(int, QUrl)),
Qt::QueuedConnection);
connect(provider, SIGNAL(InfoReady(int, CollapsibleInfoPane::Data)),
SLOT(InfoReady(int, CollapsibleInfoPane::Data)));
connect(provider, SIGNAL(Finished(int)), SLOT(ProviderFinished(int)));
SLOT(InfoReady(int, CollapsibleInfoPane::Data)),
Qt::QueuedConnection);
connect(provider, SIGNAL(Finished(int)), SLOT(ProviderFinished(int)),
Qt::QueuedConnection);
}
int SongInfoFetcher::FetchInfo(const Song& metadata) {

View File

@ -18,6 +18,7 @@
#include "config.h"
#include "songinfoprovider.h"
#include "songinfoview.h"
#include "taglyricsinfoprovider.h"
#include "ultimatelyricsprovider.h"
#include "ultimatelyricsreader.h"
@ -48,6 +49,7 @@ SongInfoView::SongInfoView(QWidget* parent)
#ifdef HAVE_LIBLASTFM
fetcher_->AddProvider(new LastfmTrackInfoProvider);
#endif
fetcher_->AddProvider(new TagLyricsInfoProvider);
}
SongInfoView::~SongInfoView() {}

View File

@ -0,0 +1,39 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "songinfotextview.h"
#include "taglyricsinfoprovider.h"
#include "core/logging.h"
void TagLyricsInfoProvider::FetchInfo(int id, const Song& metadata) {
QString lyrics;
lyrics = metadata.lyrics();
if (!lyrics.isEmpty()) {
CollapsibleInfoPane::Data data;
data.id_ = "tag/lyrics";
data.title_ = tr("Lyrics from the ID3v2 tag");
data.type_ = CollapsibleInfoPane::Data::Type_Lyrics;
SongInfoTextView* editor = new SongInfoTextView;
editor->setPlainText(lyrics);
data.contents_ = editor;
emit InfoReady(id, data);
}
emit Finished(id);
}

View File

@ -0,0 +1,30 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TAGLYRICSINFOPROVIDER_H
#define TAGLYRICSINFOPROVIDER_H
#include "songinfoprovider.h"
class TagLyricsInfoProvider : public SongInfoProvider {
Q_OBJECT
public:
void FetchInfo(int id, const Song& metadata);
};
#endif // TAGLYRICSINFOPROVIDER_H

View File

@ -2675,6 +2675,7 @@ void MainWindow::HandleNotificationPreview(OSD::Behaviour type, QString line1,
fake.set_genre("Classical");
fake.set_composer("Anonymous");
fake.set_performer("Anonymous");
fake.set_lyrics("None");
fake.set_track(1);
fake.set_disc(1);
fake.set_year(2011);

View File

@ -65,6 +65,7 @@ OrganiseDialog::OrganiseDialog(TaskManager* task_manager, QWidget* parent)
tags[tr("Composer")] = "composer";
tags[tr("Performer")] = "performer";
tags[tr("Grouping")] = "grouping";
tags[tr("Lyrics")] = "lyrics";
tags[tr("Track")] = "track";
tags[tr("Disc")] = "disc";
tags[tr("BPM")] = "bpm";
@ -315,7 +316,8 @@ void OrganiseDialog::showEvent(QShowEvent*) {
ui_->replace_spaces->setChecked(s.value("replace_spaces", false).toBool());
ui_->replace_the->setChecked(s.value("replace_the", false).toBool());
ui_->overwrite->setChecked(s.value("overwrite", false).toBool());
ui_->mark_as_listened->setChecked(s.value("mark_as_listened", false).toBool());
ui_->mark_as_listened->setChecked(
s.value("mark_as_listened", false).toBool());
ui_->eject_after->setChecked(s.value("eject_after", false).toBool());
QString destination = s.value("destination").toString();
@ -349,12 +351,11 @@ void OrganiseDialog::accept() {
const bool copy = ui_->aftercopying->currentIndex() == 0;
Organise* organise = new Organise(
task_manager_, storage, format_, copy, ui_->overwrite->isChecked(),
ui_->mark_as_listened->isChecked(),
new_songs_info_, ui_->eject_after->isChecked());
ui_->mark_as_listened->isChecked(), new_songs_info_,
ui_->eject_after->isChecked());
connect(organise, SIGNAL(Finished(QStringList)),
SLOT(OrganiseFinished(QStringList)));
connect(organise, SIGNAL(FileCopied(int)),
this, SIGNAL(FileCopied(int)));
connect(organise, SIGNAL(FileCopied(int)), this, SIGNAL(FileCopied(int)));
organise->Start();
QDialog::accept();

View File

@ -337,6 +337,8 @@ QString OSD::ReplaceVariable(const QString& variable, const Song& song) {
return song.performer();
} else if (variable == "%grouping%") {
return song.grouping();
} else if (variable == "%lyrics%") {
return song.lyrics();
} else if (variable == "%length%") {
return song.PrettyLength();
} else if (variable == "%disc%") {