/*
* Copyright (C) 2013 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 .
*/
#include "AvFormatParser.hpp"
#include
#include
#include "av/IAudioFile.hpp"
#include "utils/ILogger.hpp"
#include "utils/String.hpp"
#include "Utils.hpp"
namespace MetaData
{
namespace
{
template
std::optional findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list 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(StringUtils::stringTrim(it->second));
}
template <>
std::optional> findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list tags)
{
std::optional str{ findFirstValueOfAs(metadataMap, tags) };
if (!str)
return std::nullopt;
const std::vector strUuids{ StringUtils::splitString(*str, "/") };
std::vector res;
for (std::string_view strUuid : strUuids)
{
std::optional uuid{ UUID::fromString(strUuid) };
if (!uuid)
return std::nullopt;
res.push_back(std::move(*uuid));
}
return res;
}
std::vector getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::vector res;
auto name{ findFirstValueOfAs(metadataMap, {"ALBUM_ARTIST"}) };
if (!name)
return res;
auto mbid{ findFirstValueOfAs(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"}) };
return { Artist {mbid, *name, std::nullopt} };
}
std::vector getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::vector artists;
std::vector 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>(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 });
}
return artists;
}
std::optional getRelease(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional res;
std::optional releaseName{ findFirstValueOfAs(metadataMap, {"ALBUM", "TALB", "WM/ALBUMTITLE"}) };
if (!releaseName)
return res;
res.emplace();
res->name = std::move(*releaseName);
res->mbid = findFirstValueOfAs(metadataMap, { "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID" });
res->artists = getReleaseArtists(metadataMap);
res->mediumCount = findFirstValueOfAs(metadataMap, { "TOTALDISCS", "DISCTOTAL" });
if (!res->mediumCount)
{
// mediumCount may be encoded as position/count
if (const auto value{ findFirstValueOfAs(metadataMap, {"TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET"}) })
{
// Expecting 'Number/Total'
const std::vector strings{ StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
res->mediumCount = StringUtils::readAs(strings[1]);
}
}
return res;
}
std::optional getMedium(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional res;
res.emplace();
res->type = findFirstValueOfAs(metadataMap, { "TMED", "MEDIA", "WM/MEDIA" }).value_or("");
res->name = findFirstValueOfAs(metadataMap, { "TSST", "DISCSUBTITLE", "SETSUBTITLE" }).value_or("");
res->trackCount = findFirstValueOfAs(metadataMap, { "TOTALTRACKS", "TRACKTOTAL" });
if (!res->trackCount)
{
// totalTracks may be encoded as "position/count"
if (const auto value{ findFirstValueOfAs(metadataMap, {"TRCK", "TRACK", "TRACKNUMBER", "TRKN", "WM/TRACKNUMBER"}) })
{
// Expecting 'Number/Total'
const std::vector strings{ StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
res->trackCount = StringUtils::readAs(strings[1]);
}
}
// position may be encoded in TPOS/DISC/DISK as "position/count". Expecting 'Number[/Total]'
res->position = findFirstValueOfAs(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