Sanitize MBID usage: do not trust MBID coming from files' metadata
This commit is contained in:
+41
-33
@@ -29,35 +29,54 @@ namespace MetaData
|
||||
|
||||
using MetadataMap = std::map<std::string, std::string>;
|
||||
|
||||
std::optional<std::string>
|
||||
findFirstValueOf(const MetadataMap& metadataMap, std::initializer_list<std::string> tags)
|
||||
template <typename T>
|
||||
std::optional<T>
|
||||
findFirstValueOfAs(const 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 stringTrim(it->second);
|
||||
return readAs<T>(stringTrim(it->second));
|
||||
}
|
||||
|
||||
template <>
|
||||
std::optional<std::vector<UUID>>
|
||||
findFirstValueOfAs(const MetadataMap& metadataMap, std::initializer_list<std::string> tags)
|
||||
{
|
||||
std::optional<std::string> str {findFirstValueOfAs<std::string>(metadataMap, tags)};
|
||||
if (!str)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<std::string> strUuids = splitString(*str, "/");
|
||||
std::vector<UUID> res;
|
||||
|
||||
for (const std::string strUuid : strUuids)
|
||||
{
|
||||
std::optional<UUID> uuid {readAs<UUID>(strUuid)};
|
||||
if (!uuid)
|
||||
return std::nullopt;
|
||||
|
||||
res.push_back(std::move(*uuid));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
std::optional<Album>
|
||||
getAlbum(const MetadataMap& metadataMap)
|
||||
{
|
||||
std::optional<Album> res;
|
||||
|
||||
auto album {findFirstValueOf(metadataMap, {"ALBUM"})};
|
||||
auto album {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM"})};
|
||||
if (!album)
|
||||
return res;
|
||||
|
||||
res = Album{*album, ""};
|
||||
auto albumMBID {findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"})};
|
||||
|
||||
auto albumMBID {findFirstValueOf(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"})};
|
||||
if (!albumMBID)
|
||||
return res;
|
||||
|
||||
res->musicBrainzAlbumID = *albumMBID;
|
||||
|
||||
return res;
|
||||
return Album{*album, albumMBID};
|
||||
}
|
||||
|
||||
static
|
||||
@@ -66,17 +85,13 @@ getAlbumArtists(const MetadataMap& metadataMap)
|
||||
{
|
||||
std::vector<Artist> res;
|
||||
|
||||
auto name {findFirstValueOf(metadataMap, {"ALBUM_ARTIST"})};
|
||||
auto name {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM_ARTIST"})};
|
||||
if (!name)
|
||||
return res;
|
||||
|
||||
Artist artist {*name, ""};
|
||||
auto mbid {findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"})};
|
||||
|
||||
auto mbid {findFirstValueOf(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"})};
|
||||
if (mbid)
|
||||
artist.musicBrainzArtistID = *mbid;
|
||||
|
||||
return {std::move(artist)};
|
||||
return {Artist {*name, mbid} };
|
||||
}
|
||||
|
||||
static
|
||||
@@ -95,21 +110,14 @@ getArtists(const MetadataMap& metadataMap)
|
||||
artistNames = {metadataMap.find("ARTIST")->second};
|
||||
}
|
||||
|
||||
std::vector<std::string> artistMBIDs;
|
||||
{
|
||||
auto mbids {findFirstValueOf(metadataMap, {"MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ/ARTIST ID"})};
|
||||
if (mbids)
|
||||
artistMBIDs = splitString(*mbids, "/");
|
||||
}
|
||||
auto artistMBIDs {findFirstValueOfAs<std::vector<UUID>>(metadataMap, {"MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ/ARTIST ID"})};
|
||||
|
||||
for (std::size_t i {}; i < artistNames.size(); ++i)
|
||||
{
|
||||
Artist artist{std::move(artistNames[i]), ""};
|
||||
|
||||
if (artistNames.size() == artistMBIDs.size())
|
||||
artist.musicBrainzArtistID = std::move(artistMBIDs[i]);
|
||||
|
||||
artists.emplace_back(std::move(artist));
|
||||
if (artistMBIDs && artistNames.size() == artistMBIDs->size())
|
||||
artists.emplace_back(Artist {artistNames[i], (*artistMBIDs)[i]});
|
||||
else
|
||||
artists.emplace_back(Artist {artistNames[i], {}});
|
||||
}
|
||||
|
||||
return artists;
|
||||
@@ -191,14 +199,14 @@ AvFormat::parse(const std::filesystem::path& p, bool debug)
|
||||
}
|
||||
else if (tag == "ACOUSTID ID")
|
||||
{
|
||||
track.acoustID = value;
|
||||
track.acoustID = readAs<UUID>(value);
|
||||
}
|
||||
else if (tag == "MUSICBRAINZ RELEASE TRACK ID"
|
||||
|| tag == "MUSICBRAINZ_RELEASETRACKID"
|
||||
|| tag == "MUSICBRAINZ_TRACKID"
|
||||
|| tag == "MUSICBRAINZ/TRACK ID")
|
||||
{
|
||||
track.musicBrainzTrackID = value;
|
||||
track.musicBrainzTrackID = readAs<UUID>(value);
|
||||
}
|
||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "MetaData.hpp"
|
||||
|
||||
#include "utils/Utils.hpp"
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
|
||||
|
||||
} // namespace MetaData
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/Utils.hpp"
|
||||
#include "utils/UUID.hpp"
|
||||
|
||||
namespace MetaData
|
||||
{
|
||||
using Clusters = std::map<std::string /* type */, std::set<std::string> /* names */>;
|
||||
@@ -33,13 +36,13 @@ namespace MetaData
|
||||
struct Artist
|
||||
{
|
||||
std::string name;
|
||||
std::string musicBrainzArtistID;
|
||||
std::optional<UUID> musicBrainzArtistID;
|
||||
};
|
||||
|
||||
struct Album
|
||||
{
|
||||
std::string name;
|
||||
std::string musicBrainzAlbumID;
|
||||
std::optional<UUID> musicBrainzAlbumID;
|
||||
};
|
||||
|
||||
struct AudioStream
|
||||
@@ -52,8 +55,8 @@ namespace MetaData
|
||||
std::vector<Artist> artists;
|
||||
std::vector<Artist> albumArtists;
|
||||
std::string title;
|
||||
std::string musicBrainzTrackID;
|
||||
std::string musicBrainzRecordID;
|
||||
std::optional<UUID> musicBrainzTrackID;
|
||||
std::optional<UUID> musicBrainzRecordID;
|
||||
std::optional<Album> album;
|
||||
Clusters clusters;
|
||||
std::chrono::milliseconds duration {};
|
||||
@@ -65,7 +68,7 @@ namespace MetaData
|
||||
std::optional<int> originalYear;
|
||||
bool hasCover {false};
|
||||
std::vector<AudioStream> audioStreams;
|
||||
std::string acoustID;
|
||||
std::optional<UUID> acoustID;
|
||||
std::string copyright;
|
||||
std::string copyrightURL;
|
||||
};
|
||||
|
||||
@@ -33,10 +33,11 @@
|
||||
namespace MetaData
|
||||
{
|
||||
|
||||
std::vector<std::string>
|
||||
getPropertyValuesFirstMatch(const TagLib::PropertyMap& properties, const std::set<std::string>& keys)
|
||||
template<typename T>
|
||||
std::vector<T>
|
||||
getPropertyValuesFirstMatchAs(const TagLib::PropertyMap& properties, const std::set<std::string>& keys)
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
std::vector<T> res;
|
||||
|
||||
for (const std::string& key : keys)
|
||||
{
|
||||
@@ -45,7 +46,15 @@ getPropertyValuesFirstMatch(const TagLib::PropertyMap& properties, const std::se
|
||||
continue;
|
||||
|
||||
res.reserve(values.size());
|
||||
std::transform(std::cbegin(values), std::cend(values), std::back_inserter(res), [](const auto& value) { return stringTrim(value.to8Bit(true)); });
|
||||
|
||||
for (const auto& value : values)
|
||||
{
|
||||
auto val {readAs<T>(stringTrim(value.to8Bit(true)))};
|
||||
if (!val)
|
||||
continue;
|
||||
|
||||
res.emplace_back(std::move(*val));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -53,10 +62,11 @@ getPropertyValuesFirstMatch(const TagLib::PropertyMap& properties, const std::se
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
getPropertyValues(const TagLib::PropertyMap& properties, const std::string& key)
|
||||
template <typename T>
|
||||
std::vector<T>
|
||||
getPropertyValuesAs(const TagLib::PropertyMap& properties, const std::string& key)
|
||||
{
|
||||
return getPropertyValuesFirstMatch(properties, {std::move(key)});
|
||||
return getPropertyValuesFirstMatchAs<T>(properties, {std::move(key)});
|
||||
}
|
||||
|
||||
static
|
||||
@@ -78,24 +88,24 @@ getArtists(const TagLib::PropertyMap& properties)
|
||||
{
|
||||
std::vector<Artist> res;
|
||||
|
||||
std::vector<std::string> artistNames {getPropertyValues(properties, "ARTISTS")};
|
||||
std::vector<std::string> artistNames {getPropertyValuesAs<std::string>(properties, "ARTISTS")};
|
||||
if (artistNames.empty())
|
||||
artistNames = getPropertyValues(properties, "ARTIST");
|
||||
artistNames = getPropertyValuesAs<std::string>(properties, "ARTIST");
|
||||
|
||||
if (artistNames.empty())
|
||||
return res;
|
||||
|
||||
const std::vector<std::string> artistsMBID {getPropertyValuesFirstMatch(properties, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID"})};
|
||||
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID"})};
|
||||
|
||||
if (artistNames.size() == artistsMBID.size())
|
||||
{
|
||||
std::transform(std::cbegin(artistNames), std::cend(artistNames), std::cbegin(artistsMBID), std::back_inserter(res),
|
||||
[&](const std::string& name, const std::string& mbid) { return Artist{name, mbid}; });
|
||||
[&](const std::string& name, const UUID& mbid) { return Artist {name, mbid}; });
|
||||
}
|
||||
else
|
||||
{
|
||||
std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(res),
|
||||
[&](const std::string& name) { return Artist{name, ""}; });
|
||||
[&](const std::string& name) { return Artist{name, {}}; });
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -107,21 +117,21 @@ getAlbumArtists(const TagLib::PropertyMap& properties)
|
||||
{
|
||||
std::vector<Artist> res;
|
||||
|
||||
std::vector<std::string> artistNames {getPropertyValues(properties, "ALBUMARTIST")};
|
||||
std::vector<std::string> artistNames {getPropertyValuesAs<std::string>(properties, "ALBUMARTIST")};
|
||||
if (artistNames.empty())
|
||||
return res;
|
||||
|
||||
const std::vector<std::string> artistsMBID {getPropertyValuesFirstMatch(properties, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID"})};
|
||||
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID"})};
|
||||
|
||||
if (artistNames.size() == artistsMBID.size())
|
||||
{
|
||||
std::transform(std::cbegin(artistNames), std::cend(artistNames), std::cbegin(artistsMBID), std::back_inserter(res),
|
||||
[&](const std::string& name, const std::string& mbid) { return Artist{name, mbid}; });
|
||||
[&](const std::string& name, const UUID& mbid) { return Artist{name, mbid}; });
|
||||
}
|
||||
else
|
||||
{
|
||||
std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(res),
|
||||
[&](const std::string& name) { return Artist{name, ""}; });
|
||||
[&](const std::string& name) { return Artist{name, {}}; });
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -131,20 +141,16 @@ static
|
||||
std::optional<Album>
|
||||
getAlbum(const TagLib::PropertyMap& properties)
|
||||
{
|
||||
std::optional<Album> res;
|
||||
|
||||
std::vector<std::string> albumName {getPropertyValues(properties, "ALBUM")};
|
||||
std::vector<std::string> albumName {getPropertyValuesAs<std::string>(properties, "ALBUM")};
|
||||
if (albumName.empty())
|
||||
return res;
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<std::string> albumMBID {getPropertyValuesFirstMatch(properties, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID"})};
|
||||
const std::vector<UUID> albumMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID"})};
|
||||
|
||||
res = Album{std::move(albumName.front()), ""};
|
||||
|
||||
if (!albumMBID.empty())
|
||||
res->musicBrainzAlbumID = std::move(albumMBID.front());
|
||||
|
||||
return res;
|
||||
if (albumMBID.empty())
|
||||
return Album {std::move(albumName.front()), {}};
|
||||
else
|
||||
return Album {std::move(albumName.front()), albumMBID.front()};
|
||||
}
|
||||
|
||||
std::optional<Track>
|
||||
@@ -231,13 +237,13 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
||||
else if (tag == "MUSICBRAINZ_RELEASETRACKID"
|
||||
|| tag == "MUSICBRAINZ RELEASE TRACK ID")
|
||||
{
|
||||
track.musicBrainzTrackID = value;
|
||||
track.musicBrainzTrackID = readAs<UUID>(value);
|
||||
}
|
||||
else if (tag == "MUSICBRAINZ_TRACKID"
|
||||
|| tag == "MUSICBRAINZ TRACK ID")
|
||||
track.musicBrainzRecordID = value;
|
||||
track.musicBrainzRecordID = readAs<UUID>(value);
|
||||
else if (tag == "ACOUSTID_ID")
|
||||
track.acoustID = value;
|
||||
track.acoustID = readAs<UUID>(value);
|
||||
else if (tag == "TRACKTOTAL")
|
||||
{
|
||||
auto totalTrack = readAs<std::size_t>(value);
|
||||
|
||||
Reference in New Issue
Block a user