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; 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 static
std::vector<Artist> std::vector<Artist>
getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap) getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap)
@@ -126,6 +108,75 @@ getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
return artists; 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> std::optional<Track>
AvFormatParser::parse(const std::filesystem::path& p, bool debug) 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()}; const Av::IAudioFile::MetadataMap metadataMap {mediaFile->getMetaData()};
track.artists = getArtists(metadataMap); track.artists = getArtists(metadataMap);
track.release = getRelease(metadataMap); track.medium = getMedium(metadataMap);
if (track.release)
track.release->artists = getReleaseArtists(metadataMap);
auto getOrCreateMedium = [&]() -> Medium&
{
if (!track.medium)
track.medium.emplace();
return *track.medium;
};
for (const auto& [tag, value] : metadataMap) for (const auto& [tag, value] : metadataMap)
{ {
@@ -175,30 +217,11 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
else if (tag == "TRACK") else if (tag == "TRACK")
{ {
// Expecting 'Number/Total' // Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(value, "/") }; track.position = StringUtils::readAs<std::size_t>(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]);
}
} }
else if (tag == "DATE" else if (tag == "DATE"
|| tag == "YEAR" || tag == "YEAR"
|| tag == "WM/Year") || tag == "WM/YEAR")
{ {
track.date = Utils::parseDate(value); track.date = Utils::parseDate(value);
} }
@@ -221,16 +244,6 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
{ {
track.recordingMBID = UUID::fromString(value); 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()) else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{ {
const std::vector<std::string_view> clusterNames {StringUtils::splitString(value, "/,;")}; 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; 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> template <typename T>
std::vector<T> std::vector<T>
getPropertyValuesAs(const TagMap& tags, const std::string& key) 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}); 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 static
std::vector<std::string_view> std::vector<std::string_view>
splitAndTrimString(std::string_view str, std::string_view delimiters) splitAndTrimString(std::string_view str, std::string_view delimiters)
@@ -185,20 +204,70 @@ getRelease(const TagMap& tags)
{ {
std::optional<Release> release; std::optional<Release> release;
std::vector<std::string> releaseName {getPropertyValuesAs<std::string>(tags, "ALBUM")}; auto releaseName {getPropertyValueAs<std::string>(tags, "ALBUM")};
if (releaseName.empty()) if (!releaseName)
return release; return release;
const std::vector<UUID> releaseMBID {getPropertyValuesFirstMatchAs<UUID>(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"})};
release.emplace(); release.emplace();
release->name = std::move(releaseName.front()); release->name = std::move(*releaseName);
if (!releaseMBID.empty()) release->mbid = getPropertyValueFirstMatchAs<UUID>(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"});
release->mbid = releaseMBID.front(); 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; 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 static
TagLib::AudioProperties::ReadStyle TagLib::AudioProperties::ReadStyle
readStyleToTagLibReadStyle(ParserReadStyle readStyle) readStyleToTagLibReadStyle(ParserReadStyle readStyle)
@@ -227,13 +296,6 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector
if (tag.empty() || values.empty()) if (tag.empty() || values.empty())
return; return;
auto getOrCreateMedium = [&]() -> Medium&
{
if (!track.medium)
track.medium.emplace();
return *track.medium;
};
std::string_view value {values.front()}; std::string_view value {values.front()};
if (tag == "TITLE") if (tag == "TITLE")
@@ -250,41 +312,10 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector
track.recordingMBID = UUID::fromString(value); track.recordingMBID = UUID::fromString(value);
else if (tag == "ACOUSTID_ID") else if (tag == "ACOUSTID_ID")
track.acoustID = UUID::fromString(value); track.acoustID = UUID::fromString(value);
else if (tag == "TRACKTOTAL")
{
getOrCreateMedium().trackCount = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "TRACKNUMBER") else if (tag == "TRACKNUMBER")
{ {
// Expecting 'Number/Total' // Expecting 'Number/Total'
std::vector<std::string_view> strings {splitAndTrimString(value, "/")}; track.position = StringUtils::readAs<std::size_t>(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]);
}
} }
else if (tag == "DATE") else if (tag == "DATE")
{ {
@@ -314,14 +345,8 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector
track.copyright = value; track.copyright = value;
else if (tag == "COPYRIGHTURL") else if (tag == "COPYRIGHTURL")
track.copyrightURL = value; track.copyrightURL = value;
else if (tag == "REPLAYGAIN_ALBUM_GAIN")
getOrCreateMedium().replayGain = StringUtils::readAs<float>(value);
else if (tag == "REPLAYGAIN_TRACK_GAIN") else if (tag == "REPLAYGAIN_TRACK_GAIN")
track.replayGain = StringUtils::readAs<float>(value); 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()) else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{ {
std::set<std::string> clusterNames; 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()}; track.duration = std::chrono::milliseconds {properties->lengthInMilliseconds()};
MetaData::AudioStream audioStream {static_cast<unsigned>(properties->bitrate() * 1000)}; MetaData::AudioStream audioStream {static_cast<unsigned>(properties->bitrate() * 1000)};
track.audioStreams = {std::move(audioStream)}; track.audioStreams = {audioStream};
} }
TagMap tags {constructTagMap(f.file()->properties())}; TagMap tags {constructTagMap(f.file()->properties())};
@@ -492,9 +517,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
track.hasCover = true; track.hasCover = true;
} }
track.release = getRelease(tags); track.medium = getMedium(tags);
if (track.release)
track.release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"});
track.artists = getArtists(tags, {"ARTISTS", "ARTIST"}, {"ARTISTSORT"}, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID"}); track.artists = getArtists(tags, {"ARTISTS", "ARTIST"}, {"ARTISTSORT"}, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID"});
track.conductorArtists = getArtists(tags, {"CONDUCTORS", "CONDUCTOR"}, {"CONDUCTORSSORT", "CONDUCTORSORT"}, {}); track.conductorArtists = getArtists(tags, {"CONDUCTORS", "CONDUCTOR"}, {"CONDUCTORSSORT", "CONDUCTORSORT"}, {});
track.composerArtists = getArtists(tags, {"COMPOSERS", "COMPOSER"}, {"COMPOSERSSORT", "COMPOSERSORT"}, {}); track.composerArtists = getArtists(tags, {"COMPOSERS", "COMPOSER"}, {"COMPOSERSSORT", "COMPOSERSORT"}, {});
+1
View File
@@ -38,5 +38,6 @@ namespace MetaData::Utils
// format is "artist name (role)" // format is "artist name (role)"
PerformerArtist extractPerformerAndRole(std::string_view entry); PerformerArtist extractPerformerAndRole(std::string_view entry);
} }
@@ -35,6 +35,8 @@ namespace MetaData
{ {
using Tags = std::map<std::string /* type */, std::set<std::string> /* names */>; 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 struct Artist
{ {
std::optional<UUID> mbid; std::optional<UUID> mbid;
@@ -59,6 +61,7 @@ namespace MetaData
{ {
std::string type; std::string type;
std::string name; std::string name;
std::optional<Release> release;
std::optional<std::size_t> position; // in release std::optional<std::size_t> position; // in release
std::optional<std::size_t> trackCount; std::optional<std::size_t> trackCount;
std::optional<float> replayGain; std::optional<float> replayGain;
@@ -76,7 +79,6 @@ namespace MetaData
std::string title; std::string title;
std::optional<Medium> medium; std::optional<Medium> medium;
std::optional<std::size_t> position; // in medium std::optional<std::size_t> position; // in medium
std::optional<Release> release;
Tags tags; Tags tags;
std::chrono::milliseconds duration; std::chrono::milliseconds duration;
Wt::WDate date; Wt::WDate date;
@@ -393,9 +393,9 @@ namespace Scanner
for (const Artist::pointer& artist : getOrCreateArtists(dbSession, trackInfo->artists, false)) for (const Artist::pointer& artist : getOrCreateArtists(dbSession, trackInfo->artists, false))
track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, artist, TrackArtistLinkType::Artist)); 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)); 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()->addArtistLink(TrackArtistLink::create(dbSession, track, remixer, TrackArtistLinkType::Remixer));
track.modify()->setScanVersion(_settings.scanVersion); track.modify()->setScanVersion(_settings.scanVersion);
if (trackInfo->release) if (trackInfo->medium && trackInfo->medium->release)
track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->release)); track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->medium->release));
else else
track.modify()->setRelease({}); track.modify()->setRelease({});
track.modify()->setTotalTrack(trackInfo->medium ? trackInfo->medium->trackCount : std::nullopt); 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) operator<<(std::ostream& os, const MetaData::Medium& medium)
{ {
if (!medium.name.empty()) if (!medium.name.empty())
os << medium.name << std::endl; os << medium.name;
os << std::endl;
if (medium.position) if (medium.position)
os << "\tPosition: " << *medium.position << std::endl; os << "\tPosition: " << *medium.position << std::endl;
@@ -81,6 +82,9 @@ operator<<(std::ostream& os, const MetaData::Medium& medium)
if (medium.replayGain) if (medium.replayGain)
std::cout << "\tReplay gain: " << *medium.replayGain << std::endl; std::cout << "\tReplay gain: " << *medium.replayGain << std::endl;
if (medium.release)
std::cout << "Release: " << *medium.release << std::endl;
return os; 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 << "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) for (const Artist& artist : track->artists)
std::cout << "Artist: " << artist << std::endl; 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) for (const Artist& artist : track->remixerArtists)
std::cout << "Remixer: " << artist << std::endl; std::cout << "Remixer: " << artist << std::endl;
if (track->release)
std::cout << "Release: " << *track->release;
if (track->medium) if (track->medium)
std::cout << "Medium: " << *track->medium; std::cout << "Medium: " << *track->medium;