Medium now part of a release

This commit is contained in:
emeric
2023-03-20 21:24:28 +01:00
parent 87df73990b
commit a5de51c915
6 changed files with 165 additions and 125 deletions
+72 -59
View File
@@ -64,24 +64,6 @@ findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initiali
return res;
}
static
std::optional<Release>
getRelease(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional<Release> res;
std::optional<std::string> releaseName {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM"})};
if (!releaseName)
return res;
res.emplace();
res->name = *releaseName;
res->mbid = findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"});
return res;
}
static
std::vector<Artist>
getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap)
@@ -126,6 +108,75 @@ getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
return artists;
}
static
std::optional<Release>
getRelease(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional<Release> res;
std::optional<std::string> releaseName {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM", "TALB", "WM/ALBUMTITLE"})};
if (!releaseName)
return res;
res.emplace();
res->name = std::move(*releaseName);
res->mbid = findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"});
res->artists = getReleaseArtists(metadataMap);
res->mediumCount = findFirstValueOfAs<std::size_t>(metadataMap, {"TOTALDISCS", "DISCTOTAL"});
if (!res->mediumCount)
{
// mediumCount may be encoded as position/count
if (const auto value {findFirstValueOfAs<std::string>(metadataMap, {"TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET"})})
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
res->mediumCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
return res;
}
static
std::optional<Medium>
getMedium(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional<Medium> res;
res.emplace();
res->type = findFirstValueOfAs<std::string>(metadataMap, {"TMED", "MEDIA", "WM/MEDIA"}).value_or("");
res->name = findFirstValueOfAs<std::string>(metadataMap, {"TSST", "DISCSUBTITLE", "SETSUBTITLE"}).value_or("");
res->trackCount = findFirstValueOfAs<std::size_t>(metadataMap, {"TOTALTRACKS", "TRACKTOTAL"});
if (!res->trackCount)
{
// totalTracks may be encoded as "position/count"
if (const auto value {findFirstValueOfAs<std::string>(metadataMap, {"TRCK", "TRACK", "TRACKNUMBER", "TRKN", "WM/TRACKNUMBER"})})
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
res->trackCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
// position may be encoded in TPOS/DISC/DISK as "position/count". Expecting 'Number[/Total]'
res->position = findFirstValueOfAs<std::size_t>(metadataMap, {"TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET"});
res->release = getRelease(metadataMap);
if (res->type.empty()
&& res->name.empty()
&& !res->trackCount
&& !res->position
&& !res->release
&& !res->replayGain)
{
res.reset();
}
return res;
}
std::optional<Track>
AvFormatParser::parse(const std::filesystem::path& p, bool debug)
{
@@ -154,16 +205,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
const Av::IAudioFile::MetadataMap metadataMap {mediaFile->getMetaData()};
track.artists = getArtists(metadataMap);
track.release = getRelease(metadataMap);
if (track.release)
track.release->artists = getReleaseArtists(metadataMap);
auto getOrCreateMedium = [&]() -> Medium&
{
if (!track.medium)
track.medium.emplace();
return *track.medium;
};
track.medium = getMedium(metadataMap);
for (const auto& [tag, value] : metadataMap)
{
@@ -175,30 +217,11 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
else if (tag == "TRACK")
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(value, "/") };
if (strings.size() > 0)
{
track.position = StringUtils::readAs<std::size_t>(strings[0]);
if (strings.size() > 1)
getOrCreateMedium().trackCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
else if (tag == "DISC")
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(value, "/")};
if (strings.size() > 0)
{
getOrCreateMedium().position = StringUtils::readAs<std::size_t>(strings[0]);
if (strings.size() > 1 && track.release)
track.release->mediumCount = StringUtils::readAs<std::size_t>(strings[1]);
}
track.position = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "DATE"
|| tag == "YEAR"
|| tag == "WM/Year")
|| tag == "WM/YEAR")
{
track.date = Utils::parseDate(value);
}
@@ -221,16 +244,6 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
{
track.recordingMBID = UUID::fromString(value);
}
else if (tag == "MEDIA")
{
getOrCreateMedium().type = value;
}
else if (tag == "TSST"
|| tag == "DISCSUBTITLE"
|| tag == "SETSUBTITLE")
{
getOrCreateMedium().name = value;
}
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{
const std::vector<std::string_view> clusterNames {StringUtils::splitString(value, "/,;")};
+79 -56
View File
@@ -81,6 +81,18 @@ getPropertyValuesFirstMatchAs(const TagMap& tags, const std::vector<std::string_
return res;
}
template <typename T>
std::optional<T>
getPropertyValueFirstMatchAs(const TagMap& tags, const std::vector<std::string_view>& keys)
{
std::optional<T> res;
std::vector<T> values {getPropertyValuesFirstMatchAs<T>(tags, keys)};
if (!values.empty())
res = std::move(values.front());
return res;
}
template <typename T>
std::vector<T>
getPropertyValuesAs(const TagMap& tags, const std::string& key)
@@ -88,6 +100,13 @@ getPropertyValuesAs(const TagMap& tags, const std::string& key)
return getPropertyValuesFirstMatchAs<T>(tags, {key});
}
template <typename T>
std::optional<T>
getPropertyValueAs(const TagMap& tags, const std::string& key)
{
return getPropertyValueFirstMatchAs<T>(tags, {key});
}
static
std::vector<std::string_view>
splitAndTrimString(std::string_view str, std::string_view delimiters)
@@ -185,20 +204,70 @@ getRelease(const TagMap& tags)
{
std::optional<Release> release;
std::vector<std::string> releaseName {getPropertyValuesAs<std::string>(tags, "ALBUM")};
if (releaseName.empty())
auto releaseName {getPropertyValueAs<std::string>(tags, "ALBUM")};
if (!releaseName)
return release;
const std::vector<UUID> releaseMBID {getPropertyValuesFirstMatchAs<UUID>(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"})};
release.emplace();
release->name = std::move(releaseName.front());
if (!releaseMBID.empty())
release->mbid = releaseMBID.front();
release->name = std::move(*releaseName);
release->mbid = getPropertyValueFirstMatchAs<UUID>(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"});
release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"});
release->mediumCount = getPropertyValueAs<std::size_t>(tags, "DISCTOTAL");
if (!release->mediumCount)
{
// mediumCount may be encoded as "position/count"
if (const auto value {getPropertyValueAs<std::string>(tags, "DISCNUMBER")})
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
release->mediumCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
return release;
}
static
std::optional<Medium>
getMedium(const TagMap& tags)
{
std::optional<Medium> medium;
medium.emplace();
medium->type = getPropertyValueAs<std::string>(tags, "MEDIA").value_or("");
medium->name = getPropertyValueFirstMatchAs<std::string>(tags, {"DISCSUBTITLE", "SETSUBTITLE"}).value_or("");
medium->trackCount = getPropertyValueAs<std::size_t>(tags, "TRACKTOTAL");
if (!medium->trackCount)
{
// totalTracks may be encoded as "position/count"
if (const auto value {getPropertyValueAs<std::string>(tags, "TRACKNUMBER")})
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
medium->trackCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
// Expecting 'Number[/Total]'
medium->position = getPropertyValueAs<std::size_t>(tags, "DISCNUMBER");
medium->release = getRelease(tags);
medium->replayGain = getPropertyValueAs<float>(tags, "REPLAYGAIN_ALBUM_GAIN");
if (medium->type.empty()
&& medium->name.empty()
&& !medium->trackCount
&& !medium->position
&& !medium->release
&& !medium->replayGain)
{
medium.reset();
}
return medium;
}
static
TagLib::AudioProperties::ReadStyle
readStyleToTagLibReadStyle(ParserReadStyle readStyle)
@@ -227,13 +296,6 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector
if (tag.empty() || values.empty())
return;
auto getOrCreateMedium = [&]() -> Medium&
{
if (!track.medium)
track.medium.emplace();
return *track.medium;
};
std::string_view value {values.front()};
if (tag == "TITLE")
@@ -250,41 +312,10 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector
track.recordingMBID = UUID::fromString(value);
else if (tag == "ACOUSTID_ID")
track.acoustID = UUID::fromString(value);
else if (tag == "TRACKTOTAL")
{
getOrCreateMedium().trackCount = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "TRACKNUMBER")
{
// Expecting 'Number/Total'
std::vector<std::string_view> strings {splitAndTrimString(value, "/")};
if (!strings.empty())
{
track.position = StringUtils::readAs<std::size_t>(strings[0]);
// Lower priority than TRACKTOTAL
if (strings.size() > 1 && !getOrCreateMedium().trackCount)
getOrCreateMedium().trackCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
else if (tag == "DISCTOTAL")
{
if (track.release)
track.release->mediumCount = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "DISCNUMBER")
{
// Expecting 'Number/Total'
std::vector<std::string_view> strings {StringUtils::splitString(value, "/")};
if (!strings.empty())
{
getOrCreateMedium().position = StringUtils::readAs<std::size_t>(strings[0]);
// Lower priority than DISCTOTAL
if (strings.size() > 1 && track.release && !track.release->mediumCount)
track.release->mediumCount = StringUtils::readAs<std::size_t>(strings[1]);
}
track.position = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "DATE")
{
@@ -314,14 +345,8 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector
track.copyright = value;
else if (tag == "COPYRIGHTURL")
track.copyrightURL = value;
else if (tag == "REPLAYGAIN_ALBUM_GAIN")
getOrCreateMedium().replayGain = StringUtils::readAs<float>(value);
else if (tag == "REPLAYGAIN_TRACK_GAIN")
track.replayGain = StringUtils::readAs<float>(value);
else if (tag == "DISCSUBTITLE" || tag == "SETSUBTITLE")
getOrCreateMedium().name = value;
else if (tag == "MEDIA")
getOrCreateMedium().type = value;
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{
std::set<std::string> clusterNames;
@@ -395,7 +420,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
track.duration = std::chrono::milliseconds {properties->lengthInMilliseconds()};
MetaData::AudioStream audioStream {static_cast<unsigned>(properties->bitrate() * 1000)};
track.audioStreams = {std::move(audioStream)};
track.audioStreams = {audioStream};
}
TagMap tags {constructTagMap(f.file()->properties())};
@@ -492,9 +517,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
track.hasCover = true;
}
track.release = getRelease(tags);
if (track.release)
track.release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"});
track.medium = getMedium(tags);
track.artists = getArtists(tags, {"ARTISTS", "ARTIST"}, {"ARTISTSORT"}, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID"});
track.conductorArtists = getArtists(tags, {"CONDUCTORS", "CONDUCTOR"}, {"CONDUCTORSSORT", "CONDUCTORSORT"}, {});
track.composerArtists = getArtists(tags, {"COMPOSERS", "COMPOSER"}, {"COMPOSERSSORT", "COMPOSERSORT"}, {});
+1
View File
@@ -38,5 +38,6 @@ namespace MetaData::Utils
// format is "artist name (role)"
PerformerArtist extractPerformerAndRole(std::string_view entry);
}
@@ -35,6 +35,8 @@ namespace MetaData
{
using Tags = std::map<std::string /* type */, std::set<std::string> /* names */>;
// Very simplified version of https://musicbrainz.org/doc/MusicBrainz_Database/Schema
struct Artist
{
std::optional<UUID> mbid;
@@ -59,6 +61,7 @@ namespace MetaData
{
std::string type;
std::string name;
std::optional<Release> release;
std::optional<std::size_t> position; // in release
std::optional<std::size_t> trackCount;
std::optional<float> replayGain;
@@ -76,7 +79,6 @@ namespace MetaData
std::string title;
std::optional<Medium> medium;
std::optional<std::size_t> position; // in medium
std::optional<Release> release;
Tags tags;
std::chrono::milliseconds duration;
Wt::WDate date;
@@ -393,9 +393,9 @@ namespace Scanner
for (const Artist::pointer& artist : getOrCreateArtists(dbSession, trackInfo->artists, false))
track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, artist, TrackArtistLinkType::Artist));
if (trackInfo->release)
if (trackInfo->medium && trackInfo->medium->release)
{
for (const Artist::pointer& releaseArtist : getOrCreateArtists(dbSession, trackInfo->release->artists, false))
for (const Artist::pointer& releaseArtist : getOrCreateArtists(dbSession, trackInfo->medium->release->artists, false))
track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, releaseArtist, TrackArtistLinkType::ReleaseArtist));
}
@@ -426,8 +426,8 @@ namespace Scanner
track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, remixer, TrackArtistLinkType::Remixer));
track.modify()->setScanVersion(_settings.scanVersion);
if (trackInfo->release)
track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->release));
if (trackInfo->medium && trackInfo->medium->release)
track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->medium->release));
else
track.modify()->setRelease({});
track.modify()->setTotalTrack(trackInfo->medium ? trackInfo->medium->trackCount : std::nullopt);
+6 -5
View File
@@ -67,7 +67,8 @@ std::ostream&
operator<<(std::ostream& os, const MetaData::Medium& medium)
{
if (!medium.name.empty())
os << medium.name << std::endl;
os << medium.name;
os << std::endl;
if (medium.position)
os << "\tPosition: " << *medium.position << std::endl;
@@ -81,6 +82,9 @@ operator<<(std::ostream& os, const MetaData::Medium& medium)
if (medium.replayGain)
std::cout << "\tReplay gain: " << *medium.replayGain << std::endl;
if (medium.release)
std::cout << "Release: " << *medium.release << std::endl;
return os;
}
@@ -101,7 +105,7 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
std::cout << "Parsing time: " << std::fixed << std::setprecision(2) << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() / 1000. << "ms" << std::endl;
std::cout << "Track metadata:" << std::endl;
std::cout << "Parsed metadata:" << std::endl;
for (const Artist& artist : track->artists)
std::cout << "Artist: " << artist << std::endl;
@@ -134,9 +138,6 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
for (const Artist& artist : track->remixerArtists)
std::cout << "Remixer: " << artist << std::endl;
if (track->release)
std::cout << "Release: " << *track->release;
if (track->medium)
std::cout << "Medium: " << *track->medium;