Medium now part of a release
This commit is contained in:
@@ -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, "/,;")};
|
||||
|
||||
@@ -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"}, {});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user