Skip to content

Commit

Permalink
Added support for explcit tags (rtng, ITUNESADVISORY)
Browse files Browse the repository at this point in the history
  • Loading branch information
epoupon committed Jan 17, 2025
1 parent dd93a3f commit c7db5fc
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 13 deletions.
12 changes: 11 additions & 1 deletion src/libs/database/impl/Migration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 78 };
static constexpr Version LMS_DATABASE_VERSION{ 79 };
}

VersionInfo::VersionInfo()
Expand Down Expand Up @@ -1035,6 +1035,15 @@ FROM tracklist)");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings ADD COLUMN skip_single_release_playlists BOOLEAN NOT NULL DEFAULT(FALSE)");
}

void migrateFromV78(Session& session)
{
// added advisory tag support
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD COLUMN advisory INTEGER NOT NULL DEFAULT(0)"); // 0 means unset

// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}

bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
Expand Down Expand Up @@ -1089,6 +1098,7 @@ FROM tracklist)");
{ 75, migrateFromV75 },
{ 76, migrateFromV76 },
{ 77, migrateFromV77 },
{ 78, migrateFromV78 },
};

bool migrationPerformed{};
Expand Down
13 changes: 13 additions & 0 deletions src/libs/database/impl/Release.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,19 @@ namespace lms::db
utils::forEachQueryResult(query, _func);
}

core::EnumSet<Advisory> Release::getAdvisories() const
{
core::EnumSet<Advisory> res;

auto query{ session()->query<Advisory>("SELECT DISTINCT advisory FROM track t").where("t.release_id = ?").bind(getId()) };

utils::forEachQueryResult(query, [&](Advisory advisory) {
res.insert(advisory);
});

return res;
}

std::chrono::milliseconds Release::getDuration() const
{
assert(session());
Expand Down
1 change: 1 addition & 0 deletions src/libs/database/include/database/Release.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ namespace lms::db
std::vector<std::string> getLabelNames() const;
std::vector<std::string> getReleaseTypeNames() const;
void visitLabels(const std::function<void(const Label::pointer& label)>& _func) const;
core::EnumSet<Advisory> getAdvisories() const;
std::string_view getBarcode() const { return _barcode; }
ObjectPtr<Image> getImage() const;

Expand Down
11 changes: 4 additions & 7 deletions src/libs/database/include/database/Track.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,6 @@ namespace lms::db
}
};

struct PathResult
{
TrackId trackId;
std::filesystem::path path;
};

Track() = default;

// Find utility functions
Expand Down Expand Up @@ -244,6 +238,7 @@ namespace lms::db
void setRecordingMBID(const std::optional<core::UUID>& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; }
void setCopyright(std::string_view copyright);
void setCopyrightURL(std::string_view copyrightURL);
void setAdvisory(Advisory advisory) { _advisory = advisory; }
void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; }
void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc!
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
Expand Down Expand Up @@ -285,6 +280,7 @@ namespace lms::db
std::optional<core::UUID> getRecordingMBID() const { return core::UUID::fromString(_recordingMBID); }
std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const;
Advisory getAdvisory() const { return _advisory; }
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
std::string_view getArtistDisplayName() const { return _artistDisplayName; }
Expand Down Expand Up @@ -333,6 +329,7 @@ namespace lms::db
Wt::Dbo::field(a, _recordingMBID, "recording_mbid");
Wt::Dbo::field(a, _copyright, "copyright");
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
Wt::Dbo::field(a, _advisory, "advisory");
Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain");
Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?)
Wt::Dbo::field(a, _artistDisplayName, "artist_display_name");
Expand Down Expand Up @@ -381,11 +378,11 @@ namespace lms::db
std::string _recordingMBID;
std::string _copyright;
std::string _copyrightURL;
Advisory _advisory{ Advisory::UnSet };
std::optional<float> _trackReplayGain;
std::optional<float> _releaseReplayGain;
std::string _artistDisplayName;
std::string _comment;

Wt::Dbo::ptr<Release> _release;
Wt::Dbo::ptr<MediaLibrary> _mediaLibrary;
Wt::Dbo::ptr<Directory> _directory;
Expand Down
8 changes: 8 additions & 0 deletions src/libs/database/include/database/Types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,12 @@ namespace lms::db
PlayList = 0, // user controlled playlists
Internal = 1, // internal usage (current playqueue, history, ...)
};

enum class Advisory
{
UnSet = 0,
Unknown = 1,
Clean = 2,
Explicit = 3,
};
} // namespace lms::db
1 change: 1 addition & 0 deletions src/libs/metadata/impl/AvFormatTagReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace lms::metadata
// Mapping to internal avformat names and/or common alternative custom names
static const std::unordered_map<TagType, std::vector<std::string>> tagMapping{
{ TagType::AcoustID, { "ACOUSTID_ID", "ACOUSTID ID" } },
{ TagType::Advisory, { "ITUNESADVISORY" } },
{ TagType::Album, { "ALBUM", "TALB", "WM/ALBUMTITLE" } },
{ TagType::AlbumArtist, { "ALBUMARTIST", "ALBUM_ARTIST" } },
{ TagType::AlbumArtistSortOrder, { "ALBUMARTISTSORT", "TSO2" } },
Expand Down
3 changes: 2 additions & 1 deletion src/libs/metadata/impl/ITagReader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@

namespace lms::metadata
{
// using picard internal names
// prefer using picard internal names
// see https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html
enum class TagType
{
AcoustID,
AcoustIDFingerprint,
Advisory, // non standard
Album,
AlbumArtist,
AlbumArtists, // non standard
Expand Down
20 changes: 20 additions & 0 deletions src/libs/metadata/impl/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,25 @@ namespace lms::metadata

return artistDisplayName;
}

std::optional<Track::Advisory> getAdvisory(const ITagReader& tagReader)
{
if (const auto value{ getTagValueAs<int>(tagReader, TagType::Advisory) })
{
switch (*value)
{
case 1:
case 4:
return Track::Advisory::Explicit;
case 2:
return Track::Advisory::Clean;
case 0:
return Track::Advisory::Unknown;
}
}

return std::nullopt;
}
} // namespace

std::unique_ptr<IParser> createParser(ParserBackend parserBackend, ParserReadStyle parserReadStyle)
Expand Down Expand Up @@ -370,6 +389,7 @@ namespace lms::metadata
track.originalYear = utils::parseYear(*dateStr);
}

track.advisory = getAdvisory(tagReader);
track.lyrics = getLyrics(tagReader); // no custom delimiter on lyrics
track.comments = getTagValuesAs<std::string>(tagReader, TagType::Comment, {} /* no custom delimiter on comments */);
track.copyright = getTagValueAs<std::string>(tagReader, TagType::Copyright).value_or("");
Expand Down
17 changes: 13 additions & 4 deletions src/libs/metadata/impl/TagLibTagReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ namespace lms::metadata
// Mapping to internal taglib names and/or common alternative custom names
const std::unordered_map<TagType, std::vector<std::string>> tagMapping{
{ TagType::AcoustID, { "ACOUSTID_ID", "ACOUSTID ID" } },
{ TagType::Advisory, { "ITUNESADVISORY" } },
{ TagType::Album, { "ALBUM" } },
{ TagType::AlbumArtist, { "ALBUMARTIST" } },
{ TagType::AlbumArtistSortOrder, { "ALBUMARTISTSORT" } },
Expand Down Expand Up @@ -319,10 +320,18 @@ namespace lms::metadata
// MP4
else if (TagLib::MP4::File * mp4File{ dynamic_cast<TagLib::MP4::File*>(_file.file()) })
{
TagLib::MP4::Item coverItem{ mp4File->tag()->item("covr") };
TagLib::MP4::CoverArtList coverArtList{ coverItem.toCoverArtList() };
if (!coverArtList.isEmpty())
_hasEmbeddedCover = true;
if (const TagLib::MP4::Item coverItem{ mp4File->tag()->item("covr") }; coverItem.isValid())
{
if (coverItem.type() == TagLib::MP4::Item::Type::CoverArtList)
_hasEmbeddedCover = true;
}

// Taglib does not expose rtng in properties
if (const TagLib::MP4::Item rtngItem{ mp4File->tag()->item("rtng") }; rtngItem.isValid())
{
if (rtngItem.type() == TagLib::MP4::Item::Type::Byte)
_propertyMap["ITUNESADVISORY"] = TagLib::String{ std::to_string(rtngItem.toByte()) };
}

if (!_propertyMap.contains("ORIGINALDATE"))
{
Expand Down
7 changes: 7 additions & 0 deletions src/libs/metadata/include/metadata/Types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ namespace lms::metadata

struct Track
{
enum class Advisory
{
Unknown,
Explicit,
Clean,
};
AudioProperties audioProperties;
std::optional<core::UUID> mbid;
std::optional<core::UUID> recordingMBID;
Expand All @@ -117,6 +123,7 @@ namespace lms::metadata
Wt::WDate date;
std::optional<int> originalYear{};
Wt::WDate originalDate;
std::optional<Advisory> advisory;
bool hasCover{};
std::optional<core::UUID> acoustID;
std::string copyright;
Expand Down
30 changes: 30 additions & 0 deletions src/libs/metadata/test/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace lms::metadata
TestTagReader testTags{
{
{ TagType::AcoustID, { "e987a441-e134-4960-8019-274eddacc418" } },
{ TagType::Advisory, { "2" } },
{ TagType::Album, { "MyAlbum" } },
{ TagType::AlbumSortOrder, { "MyAlbumSortName" } },
{ TagType::Artist, { "MyArtist1 & MyArtist2" } },
Expand Down Expand Up @@ -99,6 +100,8 @@ namespace lms::metadata
}

EXPECT_EQ(track->acoustID, core::UUID::fromString("e987a441-e134-4960-8019-274eddacc418"));
ASSERT_TRUE(track->advisory.has_value());
EXPECT_EQ(track->advisory.value(), Track::Advisory::Clean);
EXPECT_EQ(track->artistDisplayName, "MyArtist1 & MyArtist2");
ASSERT_EQ(track->artists.size(), 2);
EXPECT_EQ(track->artists[0].name, "MyArtist1");
Expand Down Expand Up @@ -581,4 +584,31 @@ namespace lms::metadata
EXPECT_EQ(track->artists[1].mbid, std::nullopt);
EXPECT_EQ(track->artistDisplayName, "Artist1, Artist2"); // reconstruct the artist display name
}

TEST(Parser, advisory)
{
auto doTest = [](std::string_view value, std::optional<Track::Advisory> expectedValue) {
const TestTagReader testTags{
{
{ TagType::Advisory, { value } },
}
};

Parser parser;
std::unique_ptr<Track> track{ Parser{}.parse(testTags) };

ASSERT_EQ(track->advisory.has_value(), expectedValue.has_value()) << "Value = '" << value << "'";
if (track->advisory.has_value())
{
EXPECT_EQ(track->advisory.value(), expectedValue);
}
};

doTest("0", Track::Advisory::Unknown);
doTest("1", Track::Advisory::Explicit);
doTest("4", Track::Advisory::Explicit);
doTest("2", Track::Advisory::Clean);
doTest("", std::nullopt);
doTest("3", std::nullopt);
}
} // namespace lms::metadata
20 changes: 20 additions & 0 deletions src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "database/TrackArtistLink.hpp"
#include "database/TrackFeatures.hpp"
#include "database/TrackLyrics.hpp"
#include "database/Types.hpp"
#include "metadata/Exception.hpp"
#include "metadata/IParser.hpp"

Expand Down Expand Up @@ -309,6 +310,24 @@ namespace lms::scanner
return lyrics;
}

db::Advisory getAdvisory(std::optional<metadata::Track::Advisory> advisory)
{
if (!advisory)
return db::Advisory::UnSet;

switch (advisory.value())
{
case metadata::Track::Advisory::Clean:
return db::Advisory::Clean;
case metadata::Track::Advisory::Explicit:
return db::Advisory::Explicit;
case metadata::Track::Advisory::Unknown:
return db::Advisory::Unknown;
}

return db::Advisory::UnSet;
}

class AudioFileScanOperation : public IFileScanOperation
{
public:
Expand Down Expand Up @@ -552,6 +571,7 @@ namespace lms::scanner
track.modify()->setHasCover(_parsedTrack->hasCover);
track.modify()->setCopyright(_parsedTrack->copyright);
track.modify()->setCopyrightURL(_parsedTrack->copyrightURL);
track.modify()->setAdvisory(getAdvisory(_parsedTrack->advisory));
track.modify()->setComment(!_parsedTrack->comments.empty() ? _parsedTrack->comments.front() : ""); // only take the first one for now
track.modify()->setTrackReplayGain(_parsedTrack->replayGain);
track.modify()->setArtistDisplayName(_parsedTrack->artistDisplayName);
Expand Down
13 changes: 13 additions & 0 deletions src/libs/subsonic/impl/responses/Album.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "database/Image.hpp"
#include "database/Release.hpp"
#include "database/Track.hpp"
#include "database/Types.hpp"
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
Expand Down Expand Up @@ -223,6 +224,18 @@ namespace lms::api::subsonic
albumNode.addArrayChild("recordLabels", createRecordLabel(label));
});

auto advisoryToExplicitStatus = [&](const core::EnumSet<db::Advisory> advisories) -> std::string_view {
if (advisories.contains(db::Advisory::Explicit))
return "explicit";

if (advisories.contains(db::Advisory::Clean))
return "clean";

return "";
};

albumNode.setAttribute("explicitStatus", advisoryToExplicitStatus(release->getAdvisories()));

return albumNode;
}
} // namespace lms::api::subsonic
17 changes: 17 additions & 0 deletions src/libs/subsonic/impl/responses/Song.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "database/Release.hpp"
#include "database/Track.hpp"
#include "database/TrackArtistLink.hpp"
#include "database/Types.hpp"
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
Expand Down Expand Up @@ -227,6 +228,22 @@ namespace lms::api::subsonic
for (const auto& genre : genres)
trackResponse.addArrayChild("genres", createItemGenreNode(genre->getName()));

auto advisoryToExplicitStatus = [](db::Advisory advisory) -> std::string_view {
switch (advisory)
{
case db::Advisory::Clean:
return "clean";
case db::Advisory::Explicit:
return "expicit";
case db::Advisory::Unknown:
case db::Advisory::UnSet:
break;
}

return "";
};
trackResponse.setAttribute("explicitStatus", advisoryToExplicitStatus(track->getAdvisory()));

trackResponse.addChild("replayGain", createReplayGainNode(track));

return trackResponse;
Expand Down
Loading

0 comments on commit c7db5fc

Please sign in to comment.