Refactored how filter tags are handled in the scan settings
This commit is contained in:
@@ -29,234 +29,222 @@
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
std::optional<T> findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list<std::string> tags)
|
||||
{
|
||||
auto it = std::find_first_of(std::cbegin(metadataMap), std::cend(metadataMap), std::cbegin(tags), std::cend(tags), [](const auto& it, const auto& str) { return it.first == str; });
|
||||
if (it == std::cend(metadataMap))
|
||||
return std::nullopt;
|
||||
|
||||
template <typename T>
|
||||
std::optional<T>
|
||||
findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list<std::string> tags)
|
||||
{
|
||||
auto it = std::find_first_of(std::cbegin(metadataMap), std::cend(metadataMap), std::cbegin(tags), std::cend(tags), [](const auto& it, const auto& str) { return it.first == str; });
|
||||
if (it == std::cend(metadataMap))
|
||||
return std::nullopt;
|
||||
return StringUtils::readAs<T>(StringUtils::stringTrim(it->second));
|
||||
}
|
||||
|
||||
return StringUtils::readAs<T>(StringUtils::stringTrim(it->second));
|
||||
}
|
||||
template <>
|
||||
std::optional<std::vector<UUID>> findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list<std::string> tags)
|
||||
{
|
||||
std::optional<std::string> str{ findFirstValueOfAs<std::string>(metadataMap, tags) };
|
||||
if (!str)
|
||||
return std::nullopt;
|
||||
|
||||
template <>
|
||||
std::optional<std::vector<UUID>>
|
||||
findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list<std::string> tags)
|
||||
{
|
||||
std::optional<std::string> str {findFirstValueOfAs<std::string>(metadataMap, tags)};
|
||||
if (!str)
|
||||
return std::nullopt;
|
||||
const std::vector<std::string_view> strUuids{ StringUtils::splitString(*str, "/") };
|
||||
std::vector<UUID> res;
|
||||
|
||||
const std::vector<std::string_view> strUuids {StringUtils::splitString(*str, "/")};
|
||||
std::vector<UUID> res;
|
||||
for (std::string_view strUuid : strUuids)
|
||||
{
|
||||
std::optional<UUID> uuid{ UUID::fromString(strUuid) };
|
||||
if (!uuid)
|
||||
return std::nullopt;
|
||||
|
||||
for (std::string_view strUuid : strUuids)
|
||||
{
|
||||
std::optional<UUID> uuid {UUID::fromString(strUuid)};
|
||||
if (!uuid)
|
||||
return std::nullopt;
|
||||
res.push_back(std::move(*uuid));
|
||||
}
|
||||
|
||||
res.push_back(std::move(*uuid));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
std::vector<Artist> getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap)
|
||||
{
|
||||
std::vector<Artist> res;
|
||||
|
||||
static
|
||||
std::vector<Artist>
|
||||
getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap)
|
||||
{
|
||||
std::vector<Artist> res;
|
||||
auto name{ findFirstValueOfAs<std::string>(metadataMap, {"ALBUM_ARTIST"}) };
|
||||
if (!name)
|
||||
return res;
|
||||
|
||||
auto name {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM_ARTIST"})};
|
||||
if (!name)
|
||||
return res;
|
||||
auto mbid{ findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"}) };
|
||||
|
||||
auto mbid {findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"})};
|
||||
return { Artist {mbid, *name, std::nullopt} };
|
||||
}
|
||||
|
||||
return {Artist {mbid, *name, std::nullopt} };
|
||||
}
|
||||
std::vector<Artist> getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
|
||||
{
|
||||
std::vector<Artist> artists;
|
||||
|
||||
static
|
||||
std::vector<Artist>
|
||||
getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
|
||||
{
|
||||
std::vector<Artist> artists;
|
||||
std::vector<std::string_view> artistNames;
|
||||
if (metadataMap.find("ARTISTS") != metadataMap.end())
|
||||
{
|
||||
artistNames = StringUtils::splitString(metadataMap.find("ARTISTS")->second, "/;");
|
||||
}
|
||||
else if (metadataMap.find("ARTIST") != metadataMap.end())
|
||||
{
|
||||
artistNames = { metadataMap.find("ARTIST")->second };
|
||||
}
|
||||
|
||||
std::vector<std::string_view> artistNames;
|
||||
if (metadataMap.find("ARTISTS") != metadataMap.end())
|
||||
{
|
||||
artistNames = StringUtils::splitString(metadataMap.find("ARTISTS")->second, "/;");
|
||||
}
|
||||
else if (metadataMap.find("ARTIST") != metadataMap.end())
|
||||
{
|
||||
artistNames = {metadataMap.find("ARTIST")->second};
|
||||
}
|
||||
auto artistMBIDs{ findFirstValueOfAs<std::vector<UUID>>(metadataMap, {"MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ/ARTIST ID"}) };
|
||||
|
||||
auto artistMBIDs {findFirstValueOfAs<std::vector<UUID>>(metadataMap, {"MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ/ARTIST ID"})};
|
||||
for (std::size_t i{}; i < artistNames.size(); ++i)
|
||||
{
|
||||
if (artistMBIDs && artistNames.size() == artistMBIDs->size())
|
||||
artists.emplace_back(Artist{ (*artistMBIDs)[i], artistNames[i], std::nullopt });
|
||||
else
|
||||
artists.emplace_back(Artist{ std::nullopt, artistNames[i], std::nullopt });
|
||||
}
|
||||
|
||||
for (std::size_t i {}; i < artistNames.size(); ++i)
|
||||
{
|
||||
if (artistMBIDs && artistNames.size() == artistMBIDs->size())
|
||||
artists.emplace_back(Artist {(*artistMBIDs)[i], artistNames[i], std::nullopt});
|
||||
else
|
||||
artists.emplace_back(Artist {std::nullopt, artistNames[i], std::nullopt});
|
||||
}
|
||||
return artists;
|
||||
}
|
||||
|
||||
return artists;
|
||||
}
|
||||
std::optional<Release> getRelease(const Av::IAudioFile::MetadataMap& metadataMap)
|
||||
{
|
||||
std::optional<Release> 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", "TALB", "WM/ALBUMTITLE"}) };
|
||||
if (!releaseName)
|
||||
return 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]);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
std::optional<Medium> getMedium(const Av::IAudioFile::MetadataMap& metadataMap)
|
||||
{
|
||||
std::optional<Medium> res;
|
||||
res.emplace();
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
if (res->type.empty()
|
||||
&& res->name.empty()
|
||||
&& !res->trackCount
|
||||
&& !res->position
|
||||
&& !res->release
|
||||
&& !res->replayGain)
|
||||
{
|
||||
res.reset();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
std::optional<Track> AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
||||
{
|
||||
Track track;
|
||||
|
||||
std::optional<Track>
|
||||
AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
||||
{
|
||||
Track track;
|
||||
try
|
||||
{
|
||||
const auto mediaFile{ Av::parseAudioFile(p) };
|
||||
|
||||
try
|
||||
{
|
||||
const auto mediaFile {Av::parseAudioFile(p)};
|
||||
Av::ContainerInfo info{ mediaFile->getContainerInfo() };
|
||||
track.duration = info.duration;
|
||||
track.bitrate = info.bitrate;
|
||||
track.hasCover = mediaFile->hasAttachedPictures();
|
||||
|
||||
Av::ContainerInfo info{ mediaFile->getContainerInfo() };
|
||||
track.duration = info.duration;
|
||||
track.bitrate = info.bitrate;
|
||||
track.hasCover = mediaFile->hasAttachedPictures();
|
||||
MetaData::Tags tags;
|
||||
|
||||
MetaData::Tags tags;
|
||||
const Av::IAudioFile::MetadataMap metadataMap{ mediaFile->getMetaData() };
|
||||
|
||||
const Av::IAudioFile::MetadataMap metadataMap {mediaFile->getMetaData()};
|
||||
track.artists = getArtists(metadataMap);
|
||||
track.medium = getMedium(metadataMap);
|
||||
|
||||
track.artists = getArtists(metadataMap);
|
||||
track.medium = getMedium(metadataMap);
|
||||
for (const auto& [tag, value] : metadataMap)
|
||||
{
|
||||
if (debug)
|
||||
std::cout << "TAG = " << tag << ", VAL = " << value << std::endl;
|
||||
|
||||
for (const auto& [tag, value] : metadataMap)
|
||||
{
|
||||
if (debug)
|
||||
std::cout << "TAG = " << tag << ", VAL = " << value << std::endl;
|
||||
if (tag == "TITLE")
|
||||
track.title = value;
|
||||
else if (tag == "TRACK")
|
||||
{
|
||||
// Expecting 'Number/Total'
|
||||
track.position = StringUtils::readAs<std::size_t>(value);
|
||||
}
|
||||
else if (tag == "DATE"
|
||||
|| tag == "YEAR"
|
||||
|| tag == "WM/YEAR")
|
||||
{
|
||||
track.date = Utils::parseDate(value);
|
||||
}
|
||||
else if (tag == "TDOR" // Original release time (ID3v2 2.4)
|
||||
|| tag == "TORY") // Original release year
|
||||
{
|
||||
track.originalDate = Utils::parseDate(value);
|
||||
}
|
||||
else if (tag == "ACOUSTID ID")
|
||||
{
|
||||
track.acoustID = UUID::fromString(value);
|
||||
}
|
||||
else if (tag == "MUSICBRAINZ RELEASE TRACK ID"
|
||||
|| tag == "MUSICBRAINZ_RELEASETRACKID")
|
||||
{
|
||||
track.mbid = UUID::fromString(value);
|
||||
}
|
||||
else if (tag == "MUSICBRAINZ_TRACKID"
|
||||
|| tag == "MUSICBRAINZ/TRACK ID")
|
||||
{
|
||||
track.recordingMBID = UUID::fromString(value);
|
||||
}
|
||||
else if (std::find(std::cbegin(_extraTags), std::cend(_extraTags), tag) != std::cend(_extraTags))
|
||||
{
|
||||
const std::vector<std::string_view> tagValues{ StringUtils::splitString(value, "/,;") };
|
||||
|
||||
if (tag == "TITLE")
|
||||
track.title = value;
|
||||
else if (tag == "TRACK")
|
||||
{
|
||||
// Expecting 'Number/Total'
|
||||
track.position = StringUtils::readAs<std::size_t>(value);
|
||||
}
|
||||
else if (tag == "DATE"
|
||||
|| tag == "YEAR"
|
||||
|| tag == "WM/YEAR")
|
||||
{
|
||||
track.date = Utils::parseDate(value);
|
||||
}
|
||||
else if (tag == "TDOR" // Original release time (ID3v2 2.4)
|
||||
|| tag == "TORY") // Original release year
|
||||
{
|
||||
track.originalDate = Utils::parseDate(value);
|
||||
}
|
||||
else if (tag == "ACOUSTID ID")
|
||||
{
|
||||
track.acoustID = UUID::fromString(value);
|
||||
}
|
||||
else if (tag == "MUSICBRAINZ RELEASE TRACK ID"
|
||||
|| tag == "MUSICBRAINZ_RELEASETRACKID")
|
||||
{
|
||||
track.mbid = UUID::fromString(value);
|
||||
}
|
||||
else if (tag == "MUSICBRAINZ_TRACKID"
|
||||
|| tag == "MUSICBRAINZ/TRACK ID")
|
||||
{
|
||||
track.recordingMBID = UUID::fromString(value);
|
||||
}
|
||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||
{
|
||||
const std::vector<std::string_view> clusterNames {StringUtils::splitString(value, "/,;")};
|
||||
if (!tagValues.empty())
|
||||
{
|
||||
std::set<std::string> values;
|
||||
std::transform(std::cbegin(tagValues), std::cend(tagValues), std::inserter(values, std::begin(values)), [](std::string_view v) { return std::string{ v }; });
|
||||
track.tags[tag] = std::move(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Av::Exception& e)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!clusterNames.empty())
|
||||
{
|
||||
std::set<std::string> values;
|
||||
std::transform(std::cbegin(clusterNames), std::cend(clusterNames),
|
||||
std::inserter(values, std::begin(values)),
|
||||
[](std::string_view clusterName) { return std::string {clusterName}; });
|
||||
track.tags[tag] = std::move(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Av::Exception& e)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
} // namespace MetaData
|
||||
|
||||
|
||||
Reference in New Issue
Block a user