Added support for record labels, fixes #502
This commit is contained in:
@@ -24,6 +24,7 @@ The following extra fields are implemented:
|
||||
* `moods`
|
||||
* `musicBrainzId`
|
||||
* `originalReleaseDate`
|
||||
* `recordLabels`
|
||||
* `releaseTypes`
|
||||
* `userRating`
|
||||
* `Child` response:
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 64 };
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 65 };
|
||||
}
|
||||
|
||||
VersionInfo::VersionInfo()
|
||||
@@ -714,6 +714,28 @@ SELECT
|
||||
session.getDboSession()->execute("DROP INDEX IF EXISTS listen_user_backend_date_time");
|
||||
}
|
||||
|
||||
void migrateFromV64(Session& session)
|
||||
{
|
||||
session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "label" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"name" text not null
|
||||
))");
|
||||
|
||||
session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "release_label" (
|
||||
"label_id" bigint,
|
||||
"release_id" bigint,
|
||||
primary key ("label_id", "release_id"),
|
||||
constraint "fk_release_label_key1" foreign key ("label_id") references "label" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_release_label_key2" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred
|
||||
))");
|
||||
session.getDboSession()->execute(R"(CREATE INDEX "release_label_label" on "release_label" ("label_id"))");
|
||||
session.getDboSession()->execute(R"(CREATE INDEX "release_label_release" on "release_label" ("release_id"))");
|
||||
|
||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||
session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1");
|
||||
}
|
||||
|
||||
bool doDbMigration(Session& session)
|
||||
{
|
||||
static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||
@@ -754,6 +776,7 @@ SELECT
|
||||
{ 61, migrateFromV61 },
|
||||
{ 62, migrateFromV62 },
|
||||
{ 63, migrateFromV63 },
|
||||
{ 64, migrateFromV64 },
|
||||
};
|
||||
|
||||
bool migrationPerformed{};
|
||||
|
||||
@@ -216,6 +216,36 @@ namespace lms::db
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Label::Label(std::string_view name)
|
||||
: _name{ name }
|
||||
{
|
||||
// As we use the name to uniquely identoify release type, we must throw (and not truncate)
|
||||
if (name.size() > _maxNameLength)
|
||||
throw Exception{ "Label name is too long: " + std::string{ name } + "'" };
|
||||
}
|
||||
|
||||
Label::pointer Label::create(Session& session, std::string_view name)
|
||||
{
|
||||
return session.getDboSession()->add(std::unique_ptr<Label>{ new Label{ name } });
|
||||
}
|
||||
|
||||
Label::pointer Label::find(Session& session, LabelId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Label>>("SELECT l from label l").where("l.id = ?").bind(id));
|
||||
}
|
||||
|
||||
Label::pointer Label::find(Session& session, std::string_view name)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
if (name.size() > _maxNameLength)
|
||||
throw Exception{ "Requeted Label name is too long: " + std::string{ name } + "'" };
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Label>>("SELECT l from label l").where("l.name = ?").bind(name));
|
||||
}
|
||||
|
||||
ReleaseType::ReleaseType(std::string_view name)
|
||||
: _name{ name }
|
||||
{
|
||||
@@ -504,11 +534,21 @@ namespace lms::db
|
||||
return utils::fetchQueryResults<Release::pointer>(query);
|
||||
}
|
||||
|
||||
void Release::clearLabels()
|
||||
{
|
||||
_labels.clear();
|
||||
}
|
||||
|
||||
void Release::clearReleaseTypes()
|
||||
{
|
||||
_releaseTypes.clear();
|
||||
}
|
||||
|
||||
void Release::addLabel(ObjectPtr<Label> label)
|
||||
{
|
||||
_labels.insert(getDboPtr(label));
|
||||
}
|
||||
|
||||
void Release::addReleaseType(ObjectPtr<ReleaseType> releaseType)
|
||||
{
|
||||
_releaseTypes.insert(getDboPtr(releaseType));
|
||||
@@ -537,6 +577,16 @@ namespace lms::db
|
||||
return utils::fetchQueryResults<ReleaseType::pointer>(_releaseTypes.find());
|
||||
}
|
||||
|
||||
std::vector<std::string> Release::getLabelNames() const
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
for (const auto& label : _labels)
|
||||
res.push_back(std::string{ label->getName() });
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::string> Release::getReleaseTypeNames() const
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
@@ -547,6 +597,13 @@ namespace lms::db
|
||||
return res;
|
||||
}
|
||||
|
||||
void Release::visitLabels(const std::function<void(const Label::pointer& label)>& _func) const
|
||||
{
|
||||
assert(session());
|
||||
auto query{ _labels.find() };
|
||||
utils::forEachQueryResult(query, _func);
|
||||
}
|
||||
|
||||
std::chrono::milliseconds Release::getDuration() const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
@@ -99,6 +99,7 @@ namespace lms::db
|
||||
_session.mapClass<ClusterType>("cluster_type");
|
||||
_session.mapClass<Directory>("directory");
|
||||
_session.mapClass<Image>("image");
|
||||
_session.mapClass<Label>("label");
|
||||
_session.mapClass<Listen>("listen");
|
||||
_session.mapClass<MediaLibrary>("media_library");
|
||||
_session.mapClass<RatedArtist>("rated_artist");
|
||||
@@ -197,6 +198,8 @@ namespace lms::db
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS image_path_idx ON image(absolute_file_path)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS image_stem_idx ON image(stem)");
|
||||
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS label_name_idx ON label(name)");
|
||||
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS listen_backend_idx ON listen(backend)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS listen_id_idx ON listen(id)");
|
||||
_session.execute("CREATE INDEX IF NOT EXISTS listen_user_backend_idx ON listen(user_id,backend)");
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(LabelId)
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/ClusterId.hpp"
|
||||
#include "database/DirectoryId.hpp"
|
||||
#include "database/LabelId.hpp"
|
||||
#include "database/MediaLibraryId.hpp"
|
||||
#include "database/Object.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
@@ -51,6 +52,34 @@ namespace lms::db
|
||||
class Track;
|
||||
class User;
|
||||
|
||||
class Label final : public Object<Label, LabelId>
|
||||
{
|
||||
public:
|
||||
Label() = default;
|
||||
static pointer find(Session& session, LabelId id);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
|
||||
// Accessors
|
||||
std::string_view getName() const { return _name; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::hasMany(a, _releases, Wt::Dbo::ManyToMany, "release_label", "", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t _maxNameLength{ 512 };
|
||||
|
||||
friend class Session;
|
||||
Label(std::string_view name);
|
||||
static pointer create(Session& session, std::string_view name);
|
||||
|
||||
std::string _name;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Release>> _releases; // releases that match this label
|
||||
};
|
||||
|
||||
class ReleaseType final : public Object<ReleaseType, ReleaseTypeId>
|
||||
{
|
||||
public:
|
||||
@@ -201,7 +230,9 @@ namespace lms::db
|
||||
std::string_view getArtistDisplayName() const { return _artistDisplayName; }
|
||||
std::size_t getTrackCount() const;
|
||||
std::vector<ObjectPtr<ReleaseType>> getReleaseTypes() const;
|
||||
std::vector<std::string> getLabelNames() const;
|
||||
std::vector<std::string> getReleaseTypeNames() const;
|
||||
void visitLabels(const std::function<void(const Label::pointer& label)>& _func) const;
|
||||
|
||||
// Setters
|
||||
void setName(std::string_view name) { _name = name; }
|
||||
@@ -210,7 +241,9 @@ namespace lms::db
|
||||
void setGroupMBID(const std::optional<core::UUID>& mbid) { _groupMBID = mbid ? mbid->getAsString() : ""; }
|
||||
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
|
||||
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
|
||||
void clearLabels();
|
||||
void clearReleaseTypes();
|
||||
void addLabel(ObjectPtr<Label> releaseType);
|
||||
void addReleaseType(ObjectPtr<ReleaseType> releaseType);
|
||||
|
||||
// Get the artists of this release
|
||||
@@ -230,6 +263,8 @@ namespace lms::db
|
||||
Wt::Dbo::field(a, _totalDisc, "total_disc");
|
||||
Wt::Dbo::field(a, _artistDisplayName, "artist_display_name");
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
||||
|
||||
Wt::Dbo::hasMany(a, _labels, Wt::Dbo::ManyToMany, "release_label", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _releaseTypes, Wt::Dbo::ManyToMany, "release_release_type", "", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
@@ -250,8 +285,9 @@ namespace lms::db
|
||||
std::optional<int> _totalDisc{};
|
||||
std::string _artistDisplayName;
|
||||
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<ReleaseType>> _releaseTypes; // Release types
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Label>> _labels;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<ReleaseType>> _releaseTypes;
|
||||
};
|
||||
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -337,11 +337,13 @@ VALUES
|
||||
EXPECT_FALSE(ClusterType::find(session, ClusterTypeId{}));
|
||||
EXPECT_FALSE(Directory::find(session, DirectoryId{}));
|
||||
EXPECT_FALSE(Image::find(session, ImageId{}));
|
||||
EXPECT_FALSE(Label::find(session, LabelId{}));
|
||||
EXPECT_FALSE(Listen::find(session, ListenId{}));
|
||||
EXPECT_FALSE(RatedArtist::find(session, RatedArtistId{}));
|
||||
EXPECT_FALSE(RatedRelease::find(session, RatedReleaseId{}));
|
||||
EXPECT_FALSE(RatedTrack::find(session, RatedTrackId{}));
|
||||
EXPECT_FALSE(Release::find(session, ReleaseId{}));
|
||||
EXPECT_FALSE(ReleaseType::find(session, ReleaseTypeId{}));
|
||||
EXPECT_FALSE(StarredArtist::find(session, StarredArtistId{}));
|
||||
EXPECT_FALSE(StarredRelease::find(session, StarredReleaseId{}));
|
||||
EXPECT_FALSE(StarredTrack::find(session, StarredTrackId{}));
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
using ScopedLabel = ScopedEntity<db::Label>;
|
||||
using ScopedReleaseType = ScopedEntity<db::ReleaseType>;
|
||||
|
||||
TEST_F(DatabaseFixture, Release)
|
||||
@@ -744,6 +745,23 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Label)
|
||||
{
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
Label::pointer res{ Label::find(session, "label") };
|
||||
EXPECT_EQ(res, Label::pointer{});
|
||||
}
|
||||
|
||||
ScopedLabel label{ session, "MyLabel" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
Label::pointer res{ Label::find(session, "MyLabel") };
|
||||
EXPECT_EQ(res, label.get());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, ReleaseType)
|
||||
{
|
||||
{
|
||||
|
||||
@@ -290,7 +290,6 @@ namespace lms::metadata
|
||||
track.genres = getTagValuesAs<std::string>(tagReader, TagType::Genre, _defaultTagDelimiters);
|
||||
track.moods = getTagValuesAs<std::string>(tagReader, TagType::Mood, _defaultTagDelimiters);
|
||||
track.groupings = getTagValuesAs<std::string>(tagReader, TagType::Grouping, _defaultTagDelimiters);
|
||||
track.labels = getTagValuesAs<std::string>(tagReader, TagType::RecordLabel, _defaultTagDelimiters);
|
||||
track.languages = getTagValuesAs<std::string>(tagReader, TagType::Language, _defaultTagDelimiters);
|
||||
|
||||
std::vector<std::string_view> artistDelimiters{};
|
||||
@@ -390,6 +389,7 @@ namespace lms::metadata
|
||||
release->groupMBID = getTagValueAs<core::UUID>(tagReader, TagType::MusicBrainzReleaseGroupID);
|
||||
release->artists = getArtists(tagReader, { TagType::AlbumArtists, TagType::AlbumArtist }, { TagType::AlbumArtistsSortOrder, TagType::AlbumArtistSortOrder }, { TagType::MusicBrainzReleaseArtistID }, _artistTagDelimiters);
|
||||
release->mediumCount = getTagValueAs<std::size_t>(tagReader, TagType::TotalDiscs);
|
||||
release->labels = getTagValuesAs<std::string>(tagReader, TagType::RecordLabel, _defaultTagDelimiters);
|
||||
if (!release->mediumCount)
|
||||
{
|
||||
// mediumCount may be encoded as "position/count"
|
||||
|
||||
@@ -63,6 +63,7 @@ namespace lms::metadata
|
||||
std::string artistDisplayName;
|
||||
std::vector<Artist> artists;
|
||||
std::optional<std::size_t> mediumCount;
|
||||
std::vector<std::string> labels;
|
||||
std::vector<std::string> releaseTypes;
|
||||
|
||||
auto operator<=>(const Release&) const = default;
|
||||
@@ -106,7 +107,6 @@ namespace lms::metadata
|
||||
std::vector<std::string> groupings;
|
||||
std::vector<std::string> genres;
|
||||
std::vector<std::string> moods;
|
||||
std::vector<std::string> labels;
|
||||
std::vector<std::string> languages;
|
||||
Tags userExtraTags;
|
||||
std::optional<int> year{};
|
||||
|
||||
@@ -128,9 +128,6 @@ namespace lms::metadata
|
||||
ASSERT_EQ(track->groupings.size(), 2);
|
||||
EXPECT_EQ(track->groupings[0], "Grouping1");
|
||||
EXPECT_EQ(track->groupings[1], "Grouping2");
|
||||
ASSERT_EQ(track->labels.size(), 2);
|
||||
EXPECT_EQ(track->labels[0], "Label1");
|
||||
EXPECT_EQ(track->labels[1], "Label2");
|
||||
ASSERT_EQ(track->languages.size(), 2);
|
||||
EXPECT_EQ(track->languages[0], "Language1");
|
||||
EXPECT_EQ(track->languages[1], "Language2");
|
||||
@@ -201,6 +198,9 @@ namespace lms::metadata
|
||||
EXPECT_EQ(track->medium->release->artists[1].name, "MyAlbumArtist2");
|
||||
EXPECT_EQ(track->medium->release->artists[1].sortName, "MyAlbumArtist2SortName");
|
||||
EXPECT_EQ(track->medium->release->artists[1].mbid, core::UUID::fromString("5ed3d6b3-2aed-4a03-828c-3c4d4f7406e1"));
|
||||
ASSERT_EQ(track->medium->release->labels.size(), 2);
|
||||
EXPECT_EQ(track->medium->release->labels[0], "Label1");
|
||||
EXPECT_EQ(track->medium->release->labels[1], "Label2");
|
||||
ASSERT_TRUE(track->medium->release->mbid.has_value());
|
||||
EXPECT_EQ(track->medium->release->mbid.value(), core::UUID::fromString("3fa39992-b786-4585-a70e-85d5cc15ef69"));
|
||||
EXPECT_EQ(track->medium->release->groupMBID.value(), core::UUID::fromString("5b1a5a44-8420-4426-9b86-d25dc8d04838"));
|
||||
|
||||
@@ -206,6 +206,15 @@ namespace lms::scanner
|
||||
return releaseType;
|
||||
}
|
||||
|
||||
Label::pointer getOrCreateLabel(Session& session, std::string_view name)
|
||||
{
|
||||
Label::pointer label{ Label::find(session, name) };
|
||||
if (!label)
|
||||
label = session.create<Label>(name);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
void updateReleaseIfNeeded(Session& session, Release::pointer release, const metadata::Release& releaseInfo)
|
||||
{
|
||||
if (release->getName() != releaseInfo.name)
|
||||
@@ -224,6 +233,13 @@ namespace lms::scanner
|
||||
for (std::string_view releaseType : releaseInfo.releaseTypes)
|
||||
release.modify()->addReleaseType(getOrCreateReleaseType(session, releaseType));
|
||||
}
|
||||
|
||||
if (release->getLabelNames() != releaseInfo.labels)
|
||||
{
|
||||
release.modify()->clearLabels();
|
||||
for (std::string_view label : releaseInfo.labels)
|
||||
release.modify()->addLabel(getOrCreateLabel(session, label));
|
||||
}
|
||||
}
|
||||
|
||||
Release::pointer getOrCreateRelease(Session& session, const metadata::Release& releaseInfo, const std::filesystem::path& expectedReleaseDirectory)
|
||||
|
||||
@@ -19,6 +19,7 @@ add_library(lmssubsonic SHARED
|
||||
impl/responses/ItemGenre.cpp
|
||||
impl/responses/Genre.cpp
|
||||
impl/responses/Playlist.cpp
|
||||
impl/responses/RecordLabel.cpp
|
||||
impl/responses/ReplayGain.cpp
|
||||
impl/responses/Song.cpp
|
||||
impl/responses/User.cpp
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "responses/DiscTitle.hpp"
|
||||
#include "responses/ItemDate.hpp"
|
||||
#include "responses/ItemGenre.hpp"
|
||||
#include "responses/RecordLabel.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
@@ -186,7 +187,6 @@ namespace lms::api::subsonic
|
||||
albumNode.setAttribute("isCompilation", isCompilation);
|
||||
}
|
||||
|
||||
// disc titles
|
||||
albumNode.createEmptyArrayChild("discTitles");
|
||||
for (const DiscInfo& discInfo : release->getDiscs())
|
||||
{
|
||||
@@ -194,6 +194,12 @@ namespace lms::api::subsonic
|
||||
albumNode.addArrayChild("discTitles", createDiscTitle(discInfo));
|
||||
}
|
||||
|
||||
albumNode.createEmptyArrayChild("recordLabels");
|
||||
release->visitLabels([&](const Label::pointer& label)
|
||||
{
|
||||
albumNode.addArrayChild("recordLabels", createRecordLabel(label));
|
||||
});
|
||||
|
||||
return albumNode;
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "responses/RecordLabel.hpp"
|
||||
|
||||
#include "database/Release.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node createRecordLabel(const db::ObjectPtr<db::Label>& label)
|
||||
{
|
||||
Response::Node recordLabelNode;
|
||||
|
||||
recordLabelNode.setAttribute("name", label->getName());
|
||||
|
||||
return recordLabelNode;
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Label;
|
||||
}
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node createRecordLabel(const db::ObjectPtr<db::Label>& label);
|
||||
}
|
||||
@@ -63,6 +63,9 @@ namespace lms::metadata
|
||||
os << " '" << release.sortName << "'";
|
||||
os << std::endl;
|
||||
|
||||
for (std::string_view label : release.labels)
|
||||
std::cout << "Label: " << label << std::endl;
|
||||
|
||||
if (release.mbid)
|
||||
os << "\tRelease MBID = " << release.mbid->getAsString() << std::endl;
|
||||
|
||||
@@ -180,9 +183,6 @@ namespace lms::metadata
|
||||
for (std::string_view language : track->languages)
|
||||
std::cout << "Language: " << language << std::endl;
|
||||
|
||||
for (std::string_view label : track->labels)
|
||||
std::cout << "Label: " << label << std::endl;
|
||||
|
||||
for (const auto& [tag, values] : track->userExtraTags)
|
||||
{
|
||||
std::cout << "Tag: " << tag << std::endl;
|
||||
|
||||
Reference in New Issue
Block a user