mirror of
https://github.com/strawberrymusicplayer/strawberry
synced 2025-02-03 10:57:33 +01:00
Add support for saving playcounts and ratings to tags
This commit is contained in:
parent
ce7926cfa4
commit
3ab86543ad
@ -44,6 +44,9 @@ class TagReaderBase {
|
|||||||
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
||||||
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0;
|
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const std::string kEmbeddedCover;
|
static const std::string kEmbeddedCover;
|
||||||
|
|
||||||
|
@ -117,6 +117,24 @@ message SaveEmbeddedArtResponse {
|
|||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SaveSongPlaycountToFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
optional SongMetadata metadata = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveSongPlaycountToFileResponse {
|
||||||
|
optional bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveSongRatingToFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
optional SongMetadata metadata = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveSongRatingToFileResponse {
|
||||||
|
optional bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Message {
|
message Message {
|
||||||
optional int32 id = 1;
|
optional int32 id = 1;
|
||||||
|
|
||||||
@ -135,4 +153,10 @@ message Message {
|
|||||||
optional SaveEmbeddedArtRequest save_embedded_art_request = 10;
|
optional SaveEmbeddedArtRequest save_embedded_art_request = 10;
|
||||||
optional SaveEmbeddedArtResponse save_embedded_art_response = 11;
|
optional SaveEmbeddedArtResponse save_embedded_art_response = 11;
|
||||||
|
|
||||||
|
optional SaveSongPlaycountToFileRequest save_song_playcount_to_file_request = 12;
|
||||||
|
optional SaveSongPlaycountToFileResponse save_song_playcount_to_file_response = 13;
|
||||||
|
|
||||||
|
optional SaveSongRatingToFileRequest save_song_rating_to_file_request = 14;
|
||||||
|
optional SaveSongRatingToFileResponse save_song_rating_to_file_response = 15;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -233,9 +233,9 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
|||||||
|
|
||||||
// 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.
|
||||||
if (TagLib::Ogg::XiphComment *tag_ogg = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||||
ParseOggTag(tag_ogg->fieldListMap(), &disc, &compilation, song);
|
ParseOggTag(xiph_comment->fieldListMap(), &disc, &compilation, song);
|
||||||
if (!tag_ogg->pictureList().isEmpty()) {
|
if (!xiph_comment->pictureList().isEmpty()) {
|
||||||
song->set_art_automatic(kEmbeddedCover);
|
song->set_art_automatic(kEmbeddedCover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -319,7 +319,7 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!map["POPM"].isEmpty()) {
|
if (!map["POPM"].isEmpty()) {
|
||||||
const TagLib::ID3v2::PopularimeterFrame* frame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
|
const TagLib::ID3v2::PopularimeterFrame *frame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
|
||||||
if (frame) {
|
if (frame) {
|
||||||
if (song->playcount() <= 0 && frame->counter() > 0) {
|
if (song->playcount() <= 0 && frame->counter() > 0) {
|
||||||
song->set_playcount(frame->counter());
|
song->set_playcount(frame->counter());
|
||||||
@ -520,7 +520,7 @@ void TagReaderTagLib::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString
|
|||||||
if (!map["COVERART"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
|
if (!map["COVERART"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
|
||||||
if (!map["METADATA_BLOCK_PICTURE"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
|
if (!map["METADATA_BLOCK_PICTURE"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
|
||||||
|
|
||||||
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["FMPS_RATING"].isEmpty() && song->rating() <= 0) song->set_rating(TStringToQString(map["FMPS_RATING"].front()).trimmed().toFloat());
|
if (!map["FMPS_RATING"].isEmpty() && song->rating() <= 0) song->set_rating(TStringToQString(map["FMPS_RATING"].front()).trimmed().toFloat());
|
||||||
|
|
||||||
if (!map["LYRICS"].isEmpty()) Decode(map["LYRICS"].front(), song->mutable_lyrics());
|
if (!map["LYRICS"].isEmpty()) Decode(map["LYRICS"].front(), song->mutable_lyrics());
|
||||||
@ -663,8 +663,8 @@ bool TagReaderTagLib::SaveFile(const QString &filename, const spb::tagreader::So
|
|||||||
|
|
||||||
// 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 above.
|
// apart, so we keep specific behavior for some formats by adding another "else if" block above.
|
||||||
if (TagLib::Ogg::XiphComment *tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||||
SetVorbisComments(tag, song);
|
SetVorbisComments(xiph_comment, song);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = fileref->save();
|
result = fileref->save();
|
||||||
@ -728,6 +728,31 @@ void TagReaderTagLib::SetTextFrame(const char *id, const std::string &value, Tag
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TagReaderTagLib::SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const {
|
||||||
|
|
||||||
|
const QByteArray descr_utf8(description.toUtf8());
|
||||||
|
const QByteArray value_utf8(value.toUtf8());
|
||||||
|
qLog(Debug) << "Setting FMPSFrame:" << description << ", " << value;
|
||||||
|
SetUserTextFrame(std::string(descr_utf8.constData(), descr_utf8.length()), std::string(value_utf8.constData(), value_utf8.length()), tag);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagReaderTagLib::SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const {
|
||||||
|
|
||||||
|
const TagLib::String t_description = StdStringToTaglibString(description);
|
||||||
|
TagLib::ID3v2::UserTextIdentificationFrame *frame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, t_description);
|
||||||
|
if (frame) {
|
||||||
|
tag->removeFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and add a new frame
|
||||||
|
frame = new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8);
|
||||||
|
frame->setDescription(t_description);
|
||||||
|
frame->setText(StdStringToTaglibString(value));
|
||||||
|
tag->addFrame(frame);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void TagReaderTagLib::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");
|
TagLib::ByteVector id_vector("USLT");
|
||||||
@ -1003,3 +1028,176 @@ int TagReaderTagLib::ConvertToPOPMRating(const float rating) {
|
|||||||
return 0xFF;
|
return 0xFF;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
|
if (filename.isEmpty()) return false;
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving song playcount to" << filename;
|
||||||
|
|
||||||
|
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
|
if (!fileref || fileref->isNull()) return false;
|
||||||
|
|
||||||
|
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
||||||
|
TagLib::Ogg::XiphComment *vorbis_comments = flac_file->xiphComment(true);
|
||||||
|
if (vorbis_comments) {
|
||||||
|
if (song.playcount() > 0) {
|
||||||
|
vorbis_comments->addField("FMPS_PLAYCOUNT", TagLib::String::number(song.playcount()), true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vorbis_comments->removeFields("FMPS_PLAYCOUNT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(fileref->file())) {
|
||||||
|
TagLib::APE::Tag *tag = wavpack_file->APETag(true);
|
||||||
|
if (tag && song.playcount() > 0) {
|
||||||
|
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(song.playcount())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(fileref->file())) {
|
||||||
|
TagLib::APE::Tag *tag = ape_file->APETag(true);
|
||||||
|
if (tag && song.playcount() > 0) {
|
||||||
|
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(song.playcount())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||||
|
if (song.playcount() > 0) {
|
||||||
|
xiph_comment->addField("FMPS_PLAYCOUNT", TagLib::String::number(song.playcount()), true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
xiph_comment->removeFields("FMPS_PLAYCOUNT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::MPEG::File *mpeg_file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
|
||||||
|
TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true);
|
||||||
|
if (tag && song.playcount() > 0) {
|
||||||
|
SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag);
|
||||||
|
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
|
||||||
|
if (frame) {
|
||||||
|
frame->setCounter(song.playcount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (TagLib::MP4::File *mp4_file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
||||||
|
TagLib::MP4::Tag *tag = mp4_file->tag();
|
||||||
|
if (tag && song.playcount() > 0) {
|
||||||
|
tag->setItem(kMP4_FMPS_Playcount_ID, TagLib::MP4::Item(TagLib::String::number(song.playcount())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
|
||||||
|
TagLib::APE::Tag *tag = mpc_file->APETag(true);
|
||||||
|
if (tag && song.playcount() > 0) {
|
||||||
|
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(song.playcount())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::ASF::File *asf_file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
|
||||||
|
TagLib::ASF::Tag *tag = asf_file->tag();
|
||||||
|
if (tag && song.playcount() > 0) {
|
||||||
|
tag->addAttribute("FMPS/Playcount", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.playcount()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = fileref->save();
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
if (ret) {
|
||||||
|
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
|
||||||
|
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
|
||||||
|
}
|
||||||
|
#endif // Q_OS_LINUX
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReaderTagLib::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
|
if (filename.isNull()) return false;
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving song rating to" << filename;
|
||||||
|
|
||||||
|
if (song.rating() < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
|
|
||||||
|
if (!fileref || fileref->isNull()) return false;
|
||||||
|
|
||||||
|
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
||||||
|
TagLib::Ogg::XiphComment *vorbis_comments = flac_file->xiphComment(true);
|
||||||
|
if (vorbis_comments) {
|
||||||
|
if (song.rating() > 0) {
|
||||||
|
vorbis_comments->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())), true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vorbis_comments->removeFields("FMPS_RATING");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(fileref->file())) {
|
||||||
|
TagLib::APE::Tag *tag = wavpack_file->APETag(true);
|
||||||
|
if (tag) {
|
||||||
|
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(fileref->file())) {
|
||||||
|
TagLib::APE::Tag *tag = ape_file->APETag(true);
|
||||||
|
if (tag) {
|
||||||
|
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||||
|
if (song.rating() > 0) {
|
||||||
|
xiph_comment->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())), true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
xiph_comment->removeFields("FMPS_RATING");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::MPEG::File *mpeg_file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
|
||||||
|
TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true);
|
||||||
|
if (tag) {
|
||||||
|
SetUserTextFrame("FMPS_Rating", QString::number(song.rating()), tag);
|
||||||
|
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
|
||||||
|
if (frame) {
|
||||||
|
frame->setRating(ConvertToPOPMRating(song.rating()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::MP4::File *mp4_file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
||||||
|
TagLib::MP4::Tag *tag = mp4_file->tag();
|
||||||
|
if (tag) {
|
||||||
|
tag->setItem(kMP4_FMPS_Rating_ID, TagLib::StringList(QStringToTaglibString(QString::number(song.rating()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::ASF::File *asf_file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
|
||||||
|
TagLib::ASF::Tag *tag = asf_file->tag();
|
||||||
|
if (tag) {
|
||||||
|
tag->addAttribute("FMPS/Rating", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.rating()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
|
||||||
|
TagLib::APE::Tag *tag = mpc_file->APETag(true);
|
||||||
|
if (tag) {
|
||||||
|
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = fileref->save();
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
if (ret) {
|
||||||
|
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
|
||||||
|
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
|
||||||
|
}
|
||||||
|
#endif // Q_OS_LINUX
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -56,6 +56,9 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) 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;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||||
|
|
||||||
@ -70,6 +73,8 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
|
|
||||||
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 SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
|
void SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
void SetUnsyncLyricsFrame(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;
|
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
|
||||||
|
@ -422,3 +422,62 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const {}
|
||||||
|
|
||||||
|
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
|
if (filename.isEmpty()) return false;
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving song rating 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(false);
|
||||||
|
|
||||||
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTracks(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::Rating, TagParser::TagValue(song.rating()));
|
||||||
|
}
|
||||||
|
taginfo.applyChanges(diag, progress);
|
||||||
|
taginfo.close();
|
||||||
|
|
||||||
|
for (const TagParser::DiagMessage &msg : diag) {
|
||||||
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch(...) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -45,6 +45,9 @@ class TagReaderTagParser : public TagReaderBase {
|
|||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) 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;
|
||||||
|
|
||||||
Q_DISABLE_COPY(TagReaderTagParser)
|
Q_DISABLE_COPY(TagReaderTagParser)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,6 +51,13 @@ void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
|
|||||||
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(), message.save_embedded_art_request().data().size())));
|
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(), 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()));
|
||||||
|
}
|
||||||
|
|
||||||
SendReply(message, &reply);
|
SendReply(message, &reply);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,12 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QtConcurrentRun>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "core/taskmanager.h"
|
||||||
#include "core/database.h"
|
#include "core/database.h"
|
||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
@ -41,6 +44,7 @@
|
|||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
#include "scrobbler/lastfmimport.h"
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
#include "settings/collectionsettingspage.h"
|
||||||
|
|
||||||
const char *SCollection::kSongsTable = "songs";
|
const char *SCollection::kSongsTable = "songs";
|
||||||
const char *SCollection::kFtsTable = "songs_fts";
|
const char *SCollection::kFtsTable = "songs_fts";
|
||||||
@ -54,7 +58,9 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||||||
model_(nullptr),
|
model_(nullptr),
|
||||||
watcher_(nullptr),
|
watcher_(nullptr),
|
||||||
watcher_thread_(nullptr),
|
watcher_thread_(nullptr),
|
||||||
original_thread_(nullptr) {
|
original_thread_(nullptr),
|
||||||
|
save_playcounts_to_files_(false),
|
||||||
|
save_ratings_to_files_(false) {
|
||||||
|
|
||||||
original_thread_ = thread();
|
original_thread_ = thread();
|
||||||
|
|
||||||
@ -100,6 +106,9 @@ void SCollection::Init() {
|
|||||||
QObject::connect(backend_, &CollectionBackend::Error, this, &SCollection::Error);
|
QObject::connect(backend_, &CollectionBackend::Error, this, &SCollection::Error);
|
||||||
QObject::connect(backend_, &CollectionBackend::DirectoryDiscovered, watcher_, &CollectionWatcher::AddDirectory);
|
QObject::connect(backend_, &CollectionBackend::DirectoryDiscovered, watcher_, &CollectionWatcher::AddDirectory);
|
||||||
QObject::connect(backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
|
QObject::connect(backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
|
||||||
|
QObject::connect(backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged);
|
||||||
|
QObject::connect(backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged);
|
||||||
|
|
||||||
QObject::connect(watcher_, &CollectionWatcher::NewOrUpdatedSongs, backend_, &CollectionBackend::AddOrUpdateSongs);
|
QObject::connect(watcher_, &CollectionWatcher::NewOrUpdatedSongs, backend_, &CollectionBackend::AddOrUpdateSongs);
|
||||||
QObject::connect(watcher_, &CollectionWatcher::SongsMTimeUpdated, backend_, &CollectionBackend::UpdateMTimesOnly);
|
QObject::connect(watcher_, &CollectionWatcher::SongsMTimeUpdated, backend_, &CollectionBackend::UpdateMTimesOnly);
|
||||||
QObject::connect(watcher_, &CollectionWatcher::SongsDeleted, backend_, &CollectionBackend::DeleteSongs);
|
QObject::connect(watcher_, &CollectionWatcher::SongsDeleted, backend_, &CollectionBackend::DeleteSongs);
|
||||||
@ -164,4 +173,53 @@ void SCollection::ReloadSettings() {
|
|||||||
watcher_->ReloadSettingsAsync();
|
watcher_->ReloadSettingsAsync();
|
||||||
model_->ReloadSettings();
|
model_->ReloadSettings();
|
||||||
|
|
||||||
|
QSettings s;
|
||||||
|
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
|
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
||||||
|
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
||||||
|
s.endGroup();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCollection::SyncPlaycountAndRatingToFilesAsync() {
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
(void)QtConcurrent::run(&SCollection::SyncPlaycountAndRatingToFiles, this);
|
||||||
|
#else
|
||||||
|
(void)QtConcurrent::run(this, &SCollection::SyncPlaycountAndRatingToFiles);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCollection::SyncPlaycountAndRatingToFiles() {
|
||||||
|
|
||||||
|
const int task_id = app_->task_manager()->StartTask(tr("Saving playcounts and ratings"));
|
||||||
|
app_->task_manager()->SetTaskBlocksCollectionScans(task_id);
|
||||||
|
|
||||||
|
const SongList songs = backend_->GetAllSongs();
|
||||||
|
const int nb_songs = songs.size();
|
||||||
|
int i = 0;
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
TagReaderClient::Instance()->UpdateSongPlaycountBlocking(song);
|
||||||
|
TagReaderClient::Instance()->UpdateSongRatingBlocking(song);
|
||||||
|
app_->task_manager()->SetTaskProgress(task_id, ++i, nb_songs);
|
||||||
|
}
|
||||||
|
app_->task_manager()->SetTaskFinished(task_id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCollection::SongsPlaycountChanged(const SongList &songs) {
|
||||||
|
|
||||||
|
if (save_playcounts_to_files_) {
|
||||||
|
app_->tag_reader_client()->UpdateSongsPlaycount(songs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SCollection::SongsRatingChanged(const SongList &songs, const bool save_tags) {
|
||||||
|
|
||||||
|
if (save_tags || save_ratings_to_files_) {
|
||||||
|
app_->tag_reader_client()->UpdateSongsRating(songs);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,9 +59,10 @@ class SCollection : public QObject {
|
|||||||
|
|
||||||
QString full_rescan_reason(int schema_version) const { return full_rescan_revisions_.value(schema_version, QString()); }
|
QString full_rescan_reason(int schema_version) const { return full_rescan_revisions_.value(schema_version, QString()); }
|
||||||
|
|
||||||
int Total_Albums = 0;
|
void SyncPlaycountAndRatingToFilesAsync();
|
||||||
int total_songs_ = 0;
|
|
||||||
int Total_Artists = 0;
|
private:
|
||||||
|
void SyncPlaycountAndRatingToFiles();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
@ -77,6 +78,8 @@ class SCollection : public QObject {
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ExitReceived();
|
void ExitReceived();
|
||||||
|
void SongsPlaycountChanged(const SongList &songs);
|
||||||
|
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void Error(QString);
|
void Error(QString);
|
||||||
@ -95,6 +98,9 @@ class SCollection : public QObject {
|
|||||||
QHash<int, QString> full_rescan_revisions_;
|
QHash<int, QString> full_rescan_revisions_;
|
||||||
|
|
||||||
QList<QObject*> wait_for_exit_;
|
QList<QObject*> wait_for_exit_;
|
||||||
|
|
||||||
|
bool save_playcounts_to_files_;
|
||||||
|
bool save_ratings_to_files_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -509,6 +509,28 @@ void CollectionBackend::AddOrUpdateSubdirs(const SubdirectoryList &subdirs) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SongList CollectionBackend::GetAllSongs() {
|
||||||
|
|
||||||
|
QMutexLocker l(db_->Mutex());
|
||||||
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
SqlQuery q(db);
|
||||||
|
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1").arg(songs_table_));
|
||||||
|
if (!q.Exec()) {
|
||||||
|
db_->ReportErrors(q);
|
||||||
|
return SongList();
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList songs;
|
||||||
|
while (q.next()) {
|
||||||
|
Song song;
|
||||||
|
song.InitFromQuery(q, true);
|
||||||
|
songs << song;
|
||||||
|
}
|
||||||
|
return songs;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void CollectionBackend::AddOrUpdateSongsAsync(const SongList &songs) {
|
void CollectionBackend::AddOrUpdateSongsAsync(const SongList &songs) {
|
||||||
QMetaObject::invokeMethod(this, "AddOrUpdateSongs", Qt::QueuedConnection, Q_ARG(SongList, songs));
|
QMetaObject::invokeMethod(this, "AddOrUpdateSongs", Qt::QueuedConnection, Q_ARG(SongList, songs));
|
||||||
}
|
}
|
||||||
@ -1924,17 +1946,15 @@ void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &ti
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateSongRating(const int id, const double rating) {
|
void CollectionBackend::UpdateSongRating(const int id, const double rating, const bool save_tags) {
|
||||||
|
|
||||||
if (id == -1) return;
|
if (id == -1) return;
|
||||||
|
|
||||||
QList<int> id_list;
|
UpdateSongsRating(QList<int>() << id, rating, save_tags);
|
||||||
id_list << id;
|
|
||||||
UpdateSongsRating(id_list, rating);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const double rating) {
|
void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const double rating, const bool save_tags) {
|
||||||
|
|
||||||
if (id_list.isEmpty()) return;
|
if (id_list.isEmpty()) return;
|
||||||
|
|
||||||
@ -1957,16 +1977,16 @@ void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const doubl
|
|||||||
|
|
||||||
SongList new_song_list = GetSongsById(id_str_list, db);
|
SongList new_song_list = GetSongsById(id_str_list, db);
|
||||||
|
|
||||||
emit SongsRatingChanged(new_song_list);
|
emit SongsRatingChanged(new_song_list, save_tags);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateSongRatingAsync(const int id, const double rating) {
|
void CollectionBackend::UpdateSongRatingAsync(const int id, const double rating, const bool save_tags) {
|
||||||
QMetaObject::invokeMethod(this, "UpdateSongRating", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(double, rating));
|
QMetaObject::invokeMethod(this, "UpdateSongRating", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(double, rating), Q_ARG(bool, save_tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateSongsRatingAsync(const QList<int> &ids, const double rating) {
|
void CollectionBackend::UpdateSongsRatingAsync(const QList<int> &ids, const double rating, const bool save_tags) {
|
||||||
QMetaObject::invokeMethod(this, "UpdateSongsRating", Qt::QueuedConnection, Q_ARG(QList<int>, ids), Q_ARG(double, rating));
|
QMetaObject::invokeMethod(this, "UpdateSongsRating", Qt::QueuedConnection, Q_ARG(QList<int>, ids), Q_ARG(double, rating), Q_ARG(bool, save_tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days) {
|
void CollectionBackend::UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days) {
|
||||||
@ -2014,3 +2034,4 @@ void CollectionBackend::ExpireSongs(const int directory_id, const int expire_una
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,6 +92,8 @@ class CollectionBackendInterface : public QObject {
|
|||||||
virtual DirectoryList GetAllDirectories() = 0;
|
virtual DirectoryList GetAllDirectories() = 0;
|
||||||
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
|
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
|
||||||
|
|
||||||
|
virtual SongList GetAllSongs() = 0;
|
||||||
|
|
||||||
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
virtual SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
@ -157,6 +159,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
DirectoryList GetAllDirectories() override;
|
DirectoryList GetAllDirectories() override;
|
||||||
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
|
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
|
||||||
|
|
||||||
|
SongList GetAllSongs() override;
|
||||||
|
|
||||||
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
|
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
|
||||||
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
|
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
|
||||||
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
|
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
|
||||||
@ -208,8 +212,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void AddOrUpdateSongsAsync(const SongList &songs);
|
void AddOrUpdateSongsAsync(const SongList &songs);
|
||||||
void UpdateSongsBySongIDAsync(const SongMap &new_songs);
|
void UpdateSongsBySongIDAsync(const SongMap &new_songs);
|
||||||
|
|
||||||
void UpdateSongRatingAsync(const int id, const double rating);
|
void UpdateSongRatingAsync(const int id, const double rating, const bool save_tags = false);
|
||||||
void UpdateSongsRatingAsync(const QList<int> &ids, const double rating);
|
void UpdateSongsRatingAsync(const QList<int> &ids, const double rating, const bool save_tags = false);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void Exit();
|
void Exit();
|
||||||
@ -236,8 +240,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
|
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
|
||||||
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
||||||
|
|
||||||
void UpdateSongRating(const int id, const double rating);
|
void UpdateSongRating(const int id, const double rating, const bool save_tags = false);
|
||||||
void UpdateSongsRating(const QList<int> &id_list, const double rating);
|
void UpdateSongsRating(const QList<int> &id_list, const double rating, const bool save_tags = false);
|
||||||
|
|
||||||
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
|
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
|
||||||
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
||||||
@ -255,7 +259,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void TotalSongCountUpdated(int);
|
void TotalSongCountUpdated(int);
|
||||||
void TotalArtistCountUpdated(int);
|
void TotalArtistCountUpdated(int);
|
||||||
void TotalAlbumCountUpdated(int);
|
void TotalAlbumCountUpdated(int);
|
||||||
void SongsRatingChanged(SongList);
|
void SongsRatingChanged(SongList, bool);
|
||||||
|
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
|
|
||||||
|
@ -128,6 +128,48 @@ TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TagReaderReply *TagReaderClient::UpdateSongPlaycount(const Song &metadata) {
|
||||||
|
|
||||||
|
spb::tagreader::Message message;
|
||||||
|
spb::tagreader::SaveSongPlaycountToFileRequest *req = message.mutable_save_song_playcount_to_file_request();
|
||||||
|
|
||||||
|
req->set_filename(DataCommaSizeFromQString(metadata.url().toLocalFile()));
|
||||||
|
metadata.ToProtobuf(req->mutable_metadata());
|
||||||
|
|
||||||
|
return worker_pool_->SendMessageWithReply(&message);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagReaderClient::UpdateSongsPlaycount(const SongList &songs) {
|
||||||
|
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
TagReaderReply *reply = UpdateSongPlaycount(song);
|
||||||
|
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagReaderReply *TagReaderClient::UpdateSongRating(const Song &metadata) {
|
||||||
|
|
||||||
|
spb::tagreader::Message message;
|
||||||
|
spb::tagreader::SaveSongRatingToFileRequest *req = message.mutable_save_song_rating_to_file_request();
|
||||||
|
|
||||||
|
req->set_filename(DataCommaSizeFromQString(metadata.url().toLocalFile()));
|
||||||
|
metadata.ToProtobuf(req->mutable_metadata());
|
||||||
|
|
||||||
|
return worker_pool_->SendMessageWithReply(&message);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagReaderClient::UpdateSongsRating(const SongList &songs) {
|
||||||
|
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
TagReaderReply *reply = UpdateSongRating(song);
|
||||||
|
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
|
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
|
||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() != thread());
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
@ -210,14 +252,46 @@ bool TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const QBy
|
|||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() != thread());
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
bool ret = false;
|
bool success = false;
|
||||||
|
|
||||||
TagReaderReply *reply = SaveEmbeddedArt(filename, data);
|
TagReaderReply *reply = SaveEmbeddedArt(filename, data);
|
||||||
if (reply->WaitForFinished()) {
|
if (reply->WaitForFinished()) {
|
||||||
ret = reply->message().save_embedded_art_response().success();
|
success = reply->message().save_embedded_art_response().success();
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||||
|
|
||||||
return ret;
|
return success;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReaderClient::UpdateSongPlaycountBlocking(const Song &metadata) {
|
||||||
|
|
||||||
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
TagReaderReply *reply = UpdateSongPlaycount(metadata);
|
||||||
|
if (reply->WaitForFinished()) {
|
||||||
|
success = reply->message().save_song_playcount_to_file_response().success();
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
return success;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReaderClient::UpdateSongRatingBlocking(const Song &metadata) {
|
||||||
|
|
||||||
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
TagReaderReply *reply = UpdateSongRating(metadata);
|
||||||
|
if (reply->WaitForFinished()) {
|
||||||
|
success = reply->message().save_song_rating_to_file_response().success();
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
return success;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,8 @@ class TagReaderClient : public QObject {
|
|||||||
ReplyType *IsMediaFile(const QString &filename);
|
ReplyType *IsMediaFile(const QString &filename);
|
||||||
ReplyType *LoadEmbeddedArt(const QString &filename);
|
ReplyType *LoadEmbeddedArt(const QString &filename);
|
||||||
ReplyType *SaveEmbeddedArt(const QString &filename, const QByteArray &data);
|
ReplyType *SaveEmbeddedArt(const QString &filename, const QByteArray &data);
|
||||||
|
ReplyType* UpdateSongPlaycount(const Song &metadata);
|
||||||
|
ReplyType* UpdateSongRating(const Song &metadata);
|
||||||
|
|
||||||
// Convenience functions that call the above functions and wait for a response.
|
// Convenience functions that call the above functions and wait for a response.
|
||||||
// These block the calling thread with a semaphore, and must NOT be called from the TagReaderClient's thread.
|
// These block the calling thread with a semaphore, and must NOT be called from the TagReaderClient's thread.
|
||||||
@ -67,6 +69,8 @@ class TagReaderClient : public QObject {
|
|||||||
QByteArray LoadEmbeddedArtBlocking(const QString &filename);
|
QByteArray LoadEmbeddedArtBlocking(const QString &filename);
|
||||||
QImage LoadEmbeddedArtAsImageBlocking(const QString &filename);
|
QImage LoadEmbeddedArtAsImageBlocking(const QString &filename);
|
||||||
bool SaveEmbeddedArtBlocking(const QString &filename, const QByteArray &data);
|
bool SaveEmbeddedArtBlocking(const QString &filename, const QByteArray &data);
|
||||||
|
bool UpdateSongPlaycountBlocking(const Song &metadata);
|
||||||
|
bool UpdateSongRatingBlocking(const Song &metadata);
|
||||||
|
|
||||||
// TODO: Make this not a singleton
|
// TODO: Make this not a singleton
|
||||||
static TagReaderClient *Instance() { return sInstance; }
|
static TagReaderClient *Instance() { return sInstance; }
|
||||||
@ -78,6 +82,10 @@ class TagReaderClient : public QObject {
|
|||||||
void Exit();
|
void Exit();
|
||||||
void WorkerFailedToStart();
|
void WorkerFailedToStart();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void UpdateSongsPlaycount(const SongList &songs);
|
||||||
|
void UpdateSongsRating(const SongList &songs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static TagReaderClient *sInstance;
|
static TagReaderClient *sInstance;
|
||||||
|
|
||||||
|
@ -166,6 +166,9 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
|
|||||||
QObject::connect(checkbox, &QCheckBox::stateChanged, this, &EditTagDialog::FieldValueEdited);
|
QObject::connect(checkbox, &QCheckBox::stateChanged, this, &EditTagDialog::FieldValueEdited);
|
||||||
QObject::connect(checkbox, &CheckBox::Reset, this, &EditTagDialog::ResetField);
|
QObject::connect(checkbox, &CheckBox::Reset, this, &EditTagDialog::ResetField);
|
||||||
}
|
}
|
||||||
|
else if (RatingBox *ratingbox = qobject_cast<RatingBox*>(widget)) {
|
||||||
|
QObject::connect(ratingbox, &RatingWidget::RatingChanged, this, &EditTagDialog::FieldValueEdited);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +187,7 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
|
|||||||
QObject::connect(ui_->song_list->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditTagDialog::SelectionChanged);
|
QObject::connect(ui_->song_list->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditTagDialog::SelectionChanged);
|
||||||
QObject::connect(ui_->button_box, &QDialogButtonBox::clicked, this, &EditTagDialog::ButtonClicked);
|
QObject::connect(ui_->button_box, &QDialogButtonBox::clicked, this, &EditTagDialog::ButtonClicked);
|
||||||
QObject::connect(ui_->playcount_reset, &QPushButton::clicked, this, &EditTagDialog::ResetPlayCounts);
|
QObject::connect(ui_->playcount_reset, &QPushButton::clicked, this, &EditTagDialog::ResetPlayCounts);
|
||||||
|
QObject::connect(ui_->rating, &RatingWidget::RatingChanged, this, &EditTagDialog::SongRated);
|
||||||
#ifdef HAVE_MUSICBRAINZ
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
QObject::connect(ui_->fetch_tag, &QPushButton::clicked, this, &EditTagDialog::FetchTag);
|
QObject::connect(ui_->fetch_tag, &QPushButton::clicked, this, &EditTagDialog::FetchTag);
|
||||||
#endif
|
#endif
|
||||||
@ -478,6 +482,7 @@ QVariant EditTagDialog::Data::value(const Song &song, const QString &id) {
|
|||||||
if (id == "disc") return song.disc();
|
if (id == "disc") return song.disc();
|
||||||
if (id == "year") return song.year();
|
if (id == "year") return song.year();
|
||||||
if (id == "compilation") return song.compilation();
|
if (id == "compilation") return song.compilation();
|
||||||
|
if (id == "rating") { return song.rating(); }
|
||||||
qLog(Warning) << "Unknown ID" << id;
|
qLog(Warning) << "Unknown ID" << id;
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
@ -499,6 +504,7 @@ void EditTagDialog::Data::set_value(const QString &id, const QVariant &value) {
|
|||||||
else if (id == "disc") current_.set_disc(value.toInt());
|
else if (id == "disc") current_.set_disc(value.toInt());
|
||||||
else if (id == "year") current_.set_year(value.toInt());
|
else if (id == "year") current_.set_year(value.toInt());
|
||||||
else if (id == "compilation") current_.set_compilation(value.toBool());
|
else if (id == "compilation") current_.set_compilation(value.toBool());
|
||||||
|
else if (id == "rating") { current_.set_rating(value.toDouble()); }
|
||||||
else qLog(Warning) << "Unknown ID" << id;
|
else qLog(Warning) << "Unknown ID" << id;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -832,7 +838,6 @@ void EditTagDialog::UpdateStatisticsTab(const Song &song) {
|
|||||||
|
|
||||||
ui_->playcount->setText(QString::number(qMax(0, song.playcount())));
|
ui_->playcount->setText(QString::number(qMax(0, song.playcount())));
|
||||||
ui_->skipcount->setText(QString::number(qMax(0, song.skipcount())));
|
ui_->skipcount->setText(QString::number(qMax(0, song.skipcount())));
|
||||||
|
|
||||||
ui_->lastplayed->setText(song.lastplayed() <= 0 ? tr("Never") : QDateTime::fromSecsSinceEpoch(song.lastplayed()).toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
|
ui_->lastplayed->setText(song.lastplayed() <= 0 ? tr("Never") : QDateTime::fromSecsSinceEpoch(song.lastplayed()).toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1094,6 +1099,10 @@ void EditTagDialog::SaveData() {
|
|||||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_); }, Qt::QueuedConnection);
|
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_); }, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ref.current_.rating() != ref.original_.rating() && ref.current_.is_collection_song()) {
|
||||||
|
app_->collection_backend()->UpdateSongRatingAsync(ref.current_.id(), ref.current_.rating(), true);
|
||||||
|
}
|
||||||
|
|
||||||
QString embedded_cover_from_file;
|
QString embedded_cover_from_file;
|
||||||
// If embedded album cover is selected and it isn't saved to the tags, then save it even if no action was done.
|
// If embedded album cover is selected and it isn't saved to the tags, then save it even if no action was done.
|
||||||
if (ui_->checkbox_embedded_cover->isChecked() && ref.cover_action_ == UpdateCoverAction_None && !ref.original_.has_embedded_cover() && ref.original_.save_embedded_cover_supported()) {
|
if (ui_->checkbox_embedded_cover->isChecked() && ref.cover_action_ == UpdateCoverAction_None && !ref.original_.has_embedded_cover() && ref.original_.save_embedded_cover_supported()) {
|
||||||
@ -1256,6 +1265,18 @@ void EditTagDialog::ResetPlayCounts() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditTagDialog::SongRated(const float rating) {
|
||||||
|
|
||||||
|
const QModelIndexList indexes = ui_->song_list->selectionModel()->selectedIndexes();
|
||||||
|
if (indexes.isEmpty()) return;
|
||||||
|
|
||||||
|
for (const QModelIndex &idx : indexes) {
|
||||||
|
if (!data_[idx.row()].current_.is_valid() || data_[idx.row()].current_.id() == -1) return;
|
||||||
|
data_[idx.row()].current_.set_rating(rating);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void EditTagDialog::FetchTag() {
|
void EditTagDialog::FetchTag() {
|
||||||
|
|
||||||
#ifdef HAVE_MUSICBRAINZ
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
|
@ -116,6 +116,7 @@ class EditTagDialog : public QDialog {
|
|||||||
void ResetField();
|
void ResetField();
|
||||||
void ButtonClicked(QAbstractButton *button);
|
void ButtonClicked(QAbstractButton *button);
|
||||||
void ResetPlayCounts();
|
void ResetPlayCounts();
|
||||||
|
void SongRated(const float rating);
|
||||||
void FetchTag();
|
void FetchTag();
|
||||||
void FetchTagSongChosen(const Song &original_song, const Song &new_metadata);
|
void FetchTagSongChosen(const Song &original_song, const Song &new_metadata);
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>800</width>
|
<width>800</width>
|
||||||
<height>843</height>
|
<height>889</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -125,10 +125,10 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="html">
|
<property name="html">
|
||||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
||||||
p, li { white-space: pre-wrap; }
|
p, li { white-space: pre-wrap; }
|
||||||
</style></head><body style=" font-family:'Verdana'; font-size:10pt; font-weight:400; font-style:normal;">
|
</style></head><body style=" font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Noto Sans';"><br /></p></body></html></string>
|
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="textInteractionFlags">
|
<property name="textInteractionFlags">
|
||||||
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||||
@ -611,10 +611,10 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
<property name="html">
|
<property name="html">
|
||||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
||||||
p, li { white-space: pre-wrap; }
|
p, li { white-space: pre-wrap; }
|
||||||
</style></head><body style=" font-family:'Verdana'; font-size:10pt; font-weight:400; font-style:normal;">
|
</style></head><body style=" font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Noto Sans';"><br /></p></body></html></string>
|
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"><br /></p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -627,8 +627,24 @@ p, li { white-space: pre-wrap; }
|
|||||||
<property name="sizeConstraint">
|
<property name="sizeConstraint">
|
||||||
<enum>QLayout::SetMinimumSize</enum>
|
<enum>QLayout::SetMinimumSize</enum>
|
||||||
</property>
|
</property>
|
||||||
<item row="13" column="2">
|
<item row="4" column="3">
|
||||||
<widget class="LineEdit" name="grouping">
|
<widget class="SpinBox" name="year">
|
||||||
|
<property name="correctionMode">
|
||||||
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>9999</number>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="14" column="1">
|
||||||
|
<widget class="LineEdit" name="genre">
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="has_reset_button" stdset="0">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -637,66 +653,8 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="3" column="2">
|
||||||
<widget class="QLabel" name="label_title">
|
<widget class="QLabel" name="label_disc">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Title</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>title</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="15" column="2">
|
|
||||||
<widget class="CheckBox" name="compilation">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="14" column="0">
|
|
||||||
<widget class="QLabel" name="genre_label">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Genre</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>genre</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="21" column="2">
|
|
||||||
<widget class="TextEdit" name="comment">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="3">
|
|
||||||
<widget class="QLabel" name="disc_label">
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Disc</string>
|
<string>Disc</string>
|
||||||
</property>
|
</property>
|
||||||
@ -705,157 +663,8 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="8" column="1">
|
||||||
<widget class="QLabel" name="label_albumartist">
|
<widget class="LineEdit" name="composer">
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Album artist</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>albumartist</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="15" column="0">
|
|
||||||
<widget class="QLabel" name="label_compilation">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Compilation</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>compilation</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="2">
|
|
||||||
<widget class="LineEdit" name="album">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QLabel" name="label_album">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Album</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>album</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="label_artist">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Artist</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>artist</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="21" column="0">
|
|
||||||
<widget class="QLabel" name="label_comment">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Comment</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>comment</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="8" column="0">
|
|
||||||
<widget class="QLabel" name="label_composer">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Composer</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>composer</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="20" column="2">
|
|
||||||
<widget class="QPushButton" name="fetch_tag">
|
|
||||||
<property name="text">
|
|
||||||
<string>Complete tags automatically</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../data/data.qrc">
|
|
||||||
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>38</width>
|
|
||||||
<height>22</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="9" column="0">
|
|
||||||
<widget class="QLabel" name="label_performer">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Performer</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>performer</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="2">
|
|
||||||
<widget class="LineEdit" name="artist">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="14" column="2">
|
|
||||||
<widget class="LineEdit" name="genre">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="has_reset_button" stdset="0">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -880,24 +689,24 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="4">
|
<item row="5" column="0">
|
||||||
<widget class="SpinBox" name="disc">
|
<widget class="QLabel" name="label_albumartist">
|
||||||
<property name="correctionMode">
|
<property name="minimumSize">
|
||||||
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="text">
|
||||||
<number>9999</number>
|
<string>Album artist</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="has_clear_button" stdset="0">
|
<property name="buddy">
|
||||||
<bool>false</bool>
|
<cstring>albumartist</cstring>
|
||||||
</property>
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="2">
|
<item row="13" column="1">
|
||||||
<widget class="LineEdit" name="composer">
|
<widget class="LineEdit" name="grouping">
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="has_reset_button" stdset="0">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -906,7 +715,27 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
<item row="9" column="1">
|
||||||
|
<widget class="LineEdit" name="performer">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="LineEdit" name="artist">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
<widget class="LineEdit" name="title">
|
<widget class="LineEdit" name="title">
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="has_reset_button" stdset="0">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -916,7 +745,7 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="4">
|
<item row="0" column="3">
|
||||||
<widget class="SpinBox" name="track">
|
<widget class="SpinBox" name="track">
|
||||||
<property name="correctionMode">
|
<property name="correctionMode">
|
||||||
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
@ -932,28 +761,99 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="2">
|
<item row="4" column="0">
|
||||||
<widget class="LineEdit" name="albumartist">
|
<widget class="QLabel" name="label_album">
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="minimumSize">
|
||||||
<bool>true</bool>
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="has_clear_button" stdset="0">
|
<property name="text">
|
||||||
<bool>false</bool>
|
<string>Album</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>album</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="2">
|
<item row="4" column="2">
|
||||||
<widget class="LineEdit" name="performer">
|
<widget class="QLabel" name="label_year">
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="text">
|
||||||
<bool>true</bool>
|
<string>Year</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="has_clear_button" stdset="0">
|
<property name="buddy">
|
||||||
<bool>false</bool>
|
<cstring>year</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="4">
|
<item row="0" column="0">
|
||||||
<widget class="SpinBox" name="year">
|
<widget class="QLabel" name="label_title">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Title</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>title</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_artist">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Artist</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>artist</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="label_composer">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Composer</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>composer</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="21" column="1">
|
||||||
|
<widget class="QPushButton" name="fetch_tag">
|
||||||
|
<property name="text">
|
||||||
|
<string>Complete tags automatically</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../data/data.qrc">
|
||||||
|
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>38</width>
|
||||||
|
<height>22</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="3">
|
||||||
|
<widget class="SpinBox" name="disc">
|
||||||
<property name="correctionMode">
|
<property name="correctionMode">
|
||||||
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -968,7 +868,117 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="3">
|
<item row="15" column="1">
|
||||||
|
<widget class="CheckBox" name="compilation">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="14" column="0">
|
||||||
|
<widget class="QLabel" name="label_genre">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Genre</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>genre</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="22" column="0">
|
||||||
|
<widget class="QLabel" name="label_comment">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Comment</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>comment</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="LineEdit" name="album">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="LineEdit" name="albumartist">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="22" column="1">
|
||||||
|
<widget class="TextEdit" name="comment">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<widget class="QLabel" name="label_performer">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Performer</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>performer</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="15" column="0">
|
||||||
|
<widget class="QLabel" name="label_compilation">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Compilation</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>compilation</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
<widget class="QLabel" name="label_track">
|
<widget class="QLabel" name="label_track">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Track</string>
|
<string>Track</string>
|
||||||
@ -978,16 +988,19 @@ p, li { white-space: pre-wrap; }
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="3">
|
<item row="16" column="0">
|
||||||
<widget class="QLabel" name="label_year">
|
<widget class="QLabel" name="label_rating">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Year</string>
|
<string>Rating</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>year</cstring>
|
<cstring>rating</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="16" column="1">
|
||||||
|
<widget class="RatingBox" name="rating" native="true"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -1088,6 +1101,12 @@ p, li { white-space: pre-wrap; }
|
|||||||
<extends>QCheckBox</extends>
|
<extends>QCheckBox</extends>
|
||||||
<header>widgets/lineedit.h</header>
|
<header>widgets/lineedit.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>RatingBox</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>widgets/lineedit.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>song_list</tabstop>
|
<tabstop>song_list</tabstop>
|
||||||
|
@ -40,10 +40,12 @@
|
|||||||
#include <QRadioButton>
|
#include <QRadioButton>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
|
#include "collection/collection.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
#include "collection/collectiondirectorymodel.h"
|
#include "collection/collectiondirectorymodel.h"
|
||||||
#include "collectionsettingspage.h"
|
#include "collectionsettingspage.h"
|
||||||
@ -94,6 +96,8 @@ CollectionSettingsPage::CollectionSettingsPage(SettingsDialog *dialog, QWidget *
|
|||||||
QObject::connect(ui_->combobox_cache_size, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &CollectionSettingsPage::CacheSizeUnitChanged);
|
QObject::connect(ui_->combobox_cache_size, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &CollectionSettingsPage::CacheSizeUnitChanged);
|
||||||
QObject::connect(ui_->combobox_disk_cache_size, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &CollectionSettingsPage::DiskCacheSizeUnitChanged);
|
QObject::connect(ui_->combobox_disk_cache_size, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &CollectionSettingsPage::DiskCacheSizeUnitChanged);
|
||||||
|
|
||||||
|
QObject::connect(ui_->button_save_stats, &QPushButton::clicked, this, &CollectionSettingsPage::WriteAllSongsStatisticsToFiles);
|
||||||
|
|
||||||
#ifndef HAVE_SONGFINGERPRINTING
|
#ifndef HAVE_SONGFINGERPRINTING
|
||||||
ui_->song_tracking->hide();
|
ui_->song_tracking->hide();
|
||||||
#endif
|
#endif
|
||||||
@ -110,7 +114,7 @@ void CollectionSettingsPage::Add() {
|
|||||||
QString path(s.value("last_path", QStandardPaths::writableLocation(QStandardPaths::MusicLocation)).toString());
|
QString path(s.value("last_path", QStandardPaths::writableLocation(QStandardPaths::MusicLocation)).toString());
|
||||||
path = QFileDialog::getExistingDirectory(this, tr("Add directory..."), path);
|
path = QFileDialog::getExistingDirectory(this, tr("Add directory..."), path);
|
||||||
|
|
||||||
if (!path.isNull()) {
|
if (!path.isEmpty()) {
|
||||||
dialog()->collection_directory_model()->AddDirectory(path);
|
dialog()->collection_directory_model()->AddDirectory(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,6 +210,9 @@ void CollectionSettingsPage::Load() {
|
|||||||
ui_->combobox_disk_cache_size->setCurrentIndex(s.value(kSettingsDiskCacheSizeUnit, static_cast<int>(CacheSizeUnit_MB)).toInt());
|
ui_->combobox_disk_cache_size->setCurrentIndex(s.value(kSettingsDiskCacheSizeUnit, static_cast<int>(CacheSizeUnit_MB)).toInt());
|
||||||
if (ui_->combobox_disk_cache_size->currentIndex() == -1) ui_->combobox_cache_size->setCurrentIndex(static_cast<int>(CacheSizeUnit_MB));
|
if (ui_->combobox_disk_cache_size->currentIndex() == -1) ui_->combobox_cache_size->setCurrentIndex(static_cast<int>(CacheSizeUnit_MB));
|
||||||
|
|
||||||
|
ui_->checkbox_save_playcounts->setChecked(s.value("save_playcounts", false).toBool());
|
||||||
|
ui_->checkbox_save_ratings->setChecked(s.value("save_ratings", false).toBool());
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
ui_->checkbox_delete_files->setChecked(s.value("delete_files", false).toBool());
|
ui_->checkbox_delete_files->setChecked(s.value("delete_files", false).toBool());
|
||||||
#else
|
#else
|
||||||
@ -270,6 +277,9 @@ void CollectionSettingsPage::Save() {
|
|||||||
s.setValue(kSettingsDiskCacheSize, ui_->spinbox_disk_cache_size->value());
|
s.setValue(kSettingsDiskCacheSize, ui_->spinbox_disk_cache_size->value());
|
||||||
s.setValue(kSettingsDiskCacheSizeUnit, ui_->combobox_disk_cache_size->currentIndex());
|
s.setValue(kSettingsDiskCacheSizeUnit, ui_->combobox_disk_cache_size->currentIndex());
|
||||||
|
|
||||||
|
s.setValue("save_playcounts", ui_->checkbox_save_playcounts->isChecked());
|
||||||
|
s.setValue("save_ratings", ui_->checkbox_save_ratings->isChecked());
|
||||||
|
|
||||||
s.setValue("delete_files", ui_->checkbox_delete_files->isChecked());
|
s.setValue("delete_files", ui_->checkbox_delete_files->isChecked());
|
||||||
|
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@ -334,3 +344,14 @@ void CollectionSettingsPage::DiskCacheSizeUnitChanged(int index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollectionSettingsPage::WriteAllSongsStatisticsToFiles() {
|
||||||
|
|
||||||
|
QMessageBox confirmation_dialog(QMessageBox::Question, tr("Write all playcounts and ratings to files"), tr("Are you sure you want to write song playcounts and ratings to file for all songs in your collection?"), QMessageBox::Yes | QMessageBox::Cancel);
|
||||||
|
if (confirmation_dialog.exec() != QMessageBox::Yes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog()->app()->collection()->SyncPlaycountAndRatingToFilesAsync();
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -82,6 +82,7 @@ class CollectionSettingsPage : public SettingsPage {
|
|||||||
void ClearPixmapDiskCache();
|
void ClearPixmapDiskCache();
|
||||||
void CacheSizeUnitChanged(int index);
|
void CacheSizeUnitChanged(int index);
|
||||||
void DiskCacheSizeUnitChanged(int index);
|
void DiskCacheSizeUnitChanged(int index);
|
||||||
|
void WriteAllSongsStatisticsToFiles();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui_CollectionSettingsPage *ui_;
|
Ui_CollectionSettingsPage *ui_;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>516</width>
|
<width>516</width>
|
||||||
<height>1167</height>
|
<height>1339</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -540,6 +540,53 @@ If there are no matches then it will use the largest image in the directory.</st
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupbox_song_playcounts_and_ratings">
|
||||||
|
<property name="title">
|
||||||
|
<string>Song playcounts and ratings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkbox_save_playcounts">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save playcounts to song tags when possible</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkbox_save_ratings">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save ratings to song tags when possible</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="layout_statistics_button">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="button_save_stats">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save playcounts and ratings to files now</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="spacer_save_stats">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkbox_delete_files">
|
<widget class="QCheckBox" name="checkbox_delete_files">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -260,3 +260,12 @@ QString SpinBox::textFromValue(int val) const {
|
|||||||
return QSpinBox::textFromValue(val);
|
return QSpinBox::textFromValue(val);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RatingBox::RatingBox(QWidget *parent)
|
||||||
|
: RatingWidget(parent),
|
||||||
|
ExtendedEditor(this) {
|
||||||
|
|
||||||
|
clear_button_->hide();
|
||||||
|
reset_button_->hide();
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -33,6 +33,8 @@
|
|||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
|
||||||
|
#include "ratingwidget.h"
|
||||||
|
|
||||||
class QToolButton;
|
class QToolButton;
|
||||||
class QPaintDevice;
|
class QPaintDevice;
|
||||||
class QPaintEvent;
|
class QPaintEvent;
|
||||||
@ -231,4 +233,25 @@ class CheckBox : public QCheckBox, public ExtendedEditor {
|
|||||||
void Reset();
|
void Reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class RatingBox : public RatingWidget, public ExtendedEditor {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(QString hint READ hint WRITE set_hint)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RatingBox(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void set_enabled(bool enabled) override { RatingWidget::setEnabled(enabled); }
|
||||||
|
|
||||||
|
QVariant value() const override { return RatingWidget::rating(); }
|
||||||
|
void set_value(const QVariant &value) override { RatingWidget::set_rating(value.toDouble()); }
|
||||||
|
|
||||||
|
void set_partially() override { RatingWidget::set_rating(0.0); }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void set_focus() override { RatingWidget::setFocus(); }
|
||||||
|
void clear() override {}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
#endif // LINEEDIT_H
|
#endif // LINEEDIT_H
|
||||||
|
@ -94,6 +94,28 @@ class TagReaderTest : public ::testing::Test {
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void WriteSongPlaycountToFile(const Song &song, const QString &filename) {
|
||||||
|
#if defined(USE_TAGLIB)
|
||||||
|
TagReaderTagLib tag_reader;
|
||||||
|
#elif defined(USE_TAGPARSER)
|
||||||
|
TagReaderTagParser tag_reader;
|
||||||
|
#endif
|
||||||
|
spb::tagreader::SongMetadata pb_song;
|
||||||
|
song.ToProtobuf(&pb_song);
|
||||||
|
tag_reader.SaveSongPlaycountToFile(filename, pb_song);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteSongRatingToFile(const Song &song, const QString &filename) {
|
||||||
|
#if defined(USE_TAGLIB)
|
||||||
|
TagReaderTagLib tag_reader;
|
||||||
|
#elif defined(USE_TAGPARSER)
|
||||||
|
TagReaderTagParser tag_reader;
|
||||||
|
#endif
|
||||||
|
spb::tagreader::SongMetadata pb_song;
|
||||||
|
song.ToProtobuf(&pb_song);
|
||||||
|
tag_reader.SaveSongRatingToFile(filename, pb_song);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(TagReaderTest, TestFLACAudioFileTagging) {
|
TEST_F(TagReaderTest, TestFLACAudioFileTagging) {
|
||||||
@ -1691,4 +1713,314 @@ TEST_F(TagReaderTest, TestM4AAudioFileTagging) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef USE_TAGPARSER
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestFLACAudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.flac");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestWavPackAudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.wv");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggFLACAudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.oga");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggVorbisAudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.ogg");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggOpusAudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.opus");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggSpeexAudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.spx");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggASFAudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.asf");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggMP3AudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.mp3");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggMP4AudioFilePlaycount) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.m4a");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_playcount(4);
|
||||||
|
WriteSongPlaycountToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_EQ(4, song.playcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_TAGPARSER
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestFLACAudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.flac");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestWavPackAudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.wv");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggFLACAudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.oga");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggVorbisAudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.ogg");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggOpusAudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.opus");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestOggSpeexAudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.spx");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestASFAudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.asf");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestMP3AudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.mp3");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TagReaderTest, TestMP4AudioFileRating) {
|
||||||
|
|
||||||
|
TemporaryResource r(":/audio/strawberry.m4a");
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song;
|
||||||
|
song.set_rating(0.4);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_NE(0.4, song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
Loading…
x
Reference in New Issue
Block a user