Moved total_disc from Track to Release

This commit is contained in:
emeric
2023-03-06 23:10:31 +01:00
parent 422458821f
commit 3a9bde0092
18 changed files with 377 additions and 320 deletions
+26 -16
View File
@@ -66,23 +66,25 @@ findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initiali
static
std::optional<Album>
getAlbum(const Av::IAudioFile::MetadataMap& metadataMap)
std::optional<Release>
getRelease(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional<Album> res;
std::optional<Release> res;
auto album {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM"})};
if (!album)
std::optional<std::string> releaseName {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM"})};
if (!releaseName)
return res;
auto albumMBID {findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"})};
res.emplace();
res->name = *releaseName;
res->releaseMBID = findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"});
return Album{*album, albumMBID};
return res;
}
static
std::vector<Artist>
getAlbumArtists(const Av::IAudioFile::MetadataMap& metadataMap)
getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::vector<Artist> res;
@@ -151,6 +153,18 @@ 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->releaseArtists = getReleaseArtists(metadataMap);
auto getOrCreateDisc = [&]() -> Disc&
{
if (!track.disc)
track.disc.emplace();
return *track.disc;
};
for (const auto& [tag, value] : metadataMap)
{
if (debug)
@@ -167,7 +181,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
track.trackNumber = StringUtils::readAs<std::size_t>(strings[0]);
if (strings.size() > 1)
track.totalTrack = StringUtils::readAs<std::size_t>(strings[1]);
getOrCreateDisc().totalTrack = StringUtils::readAs<std::size_t>(strings[1]);
}
}
else if (tag == "DISC")
@@ -178,8 +192,8 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
{
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
if (strings.size() > 1)
track.totalDisc = StringUtils::readAs<std::size_t>(strings[1]);
if (strings.size() > 1 && track.release)
track.release->totalDisc = StringUtils::readAs<std::size_t>(strings[1]);
}
}
else if (tag == "DATE"
@@ -211,7 +225,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|| tag == "DISCSUBTITLE"
|| tag == "SETSUBTITLE")
{
track.discSubtitle = value;
getOrCreateDisc().subtitle = value;
}
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{
@@ -227,10 +241,6 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
}
}
}
track.artists = getArtists(metadataMap);
track.album = getAlbum(metadataMap);
track.albumArtists = getAlbumArtists(metadataMap);
}
catch(Av::Exception& e)
{
+123 -89
View File
@@ -19,6 +19,8 @@
#include "TagLibParser.hpp"
#include <map>
#include <taglib/apetag.h>
#include <taglib/asffile.h>
#include <taglib/id3v2tag.h>
@@ -43,23 +45,30 @@
namespace MetaData
{
// TODO use string_views here for values
using TagMap = std::map<std::string, std::vector<std::string>>;
template<typename T>
std::vector<T>
getPropertyValuesFirstMatchAs(const TagLib::PropertyMap& properties, const std::vector<std::string_view>& keys)
getPropertyValuesFirstMatchAs(const TagMap& tags, const std::vector<std::string_view>& keys)
{
std::vector<T> res;
for (std::string_view key : keys)
{
const TagLib::StringList& values {properties[std::string {key}]};
if (values.isEmpty())
const auto itValues {tags.find(std::string {key})};
if (itValues == std::cend(tags))
continue;
const std::vector<std::string>& values {itValues->second};
if (values.empty())
continue;
res.reserve(values.size());
for (const auto& value : values)
{
auto val {StringUtils::readAs<T>(StringUtils::stringTrim(value.to8Bit(true)))};
std::optional<T> val {StringUtils::readAs<T>(value)};
if (!val)
continue;
@@ -74,33 +83,31 @@ getPropertyValuesFirstMatchAs(const TagLib::PropertyMap& properties, const std::
template <typename T>
std::vector<T>
getPropertyValuesAs(const TagLib::PropertyMap& properties, const std::string& key)
getPropertyValuesAs(const TagMap& tags, const std::string& key)
{
return getPropertyValuesFirstMatchAs<T>(properties, {std::move(key)});
return getPropertyValuesFirstMatchAs<T>(tags, {key});
}
static
std::vector<std::string>
splitAndTrimString(const std::string& str, std::string_view delimiters)
std::vector<std::string_view>
splitAndTrimString(std::string_view str, std::string_view delimiters)
{
std::vector<std::string> res;
std::vector<std::string_view> strings {StringUtils::splitString(str, delimiters)};
for (std::string_view s : strings)
res.emplace_back(StringUtils::stringTrim(s));
for (std::string_view& s : strings)
s = StringUtils::stringTrim(s);
return res;
return strings;
}
static
std::vector<Artist>
getArtists(const TagLib::PropertyMap& properties,
getArtists(const TagMap& tags,
const std::vector<std::string_view>& artistTagNames,
const std::vector<std::string_view>& artistSortTagNames,
const std::vector<std::string_view>& artistMBIDTagNames
)
{
const std::vector<std::string> artistNames {getPropertyValuesFirstMatchAs<std::string>(properties, artistTagNames)};
const std::vector<std::string> artistNames {getPropertyValuesFirstMatchAs<std::string>(tags, artistTagNames)};
if (artistNames.empty())
return {};
@@ -110,7 +117,7 @@ getArtists(const TagLib::PropertyMap& properties,
[&](const std::string& name) { return Artist {name}; });
{
const std::vector<std::string> artistSortNames {getPropertyValuesFirstMatchAs<std::string>(properties, artistSortTagNames)};
const std::vector<std::string> artistSortNames {getPropertyValuesFirstMatchAs<std::string>(tags, artistSortTagNames)};
if (artistSortNames.size() == artists.size())
{
for (std::size_t i {}; i < artistSortNames.size(); ++i)
@@ -119,12 +126,12 @@ getArtists(const TagLib::PropertyMap& properties,
}
{
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(properties, artistMBIDTagNames)};
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(tags, artistMBIDTagNames)};
if (artistNames.size() == artistsMBID.size())
{
for (std::size_t i {}; i < artistsMBID.size(); ++i)
artists[i].musicBrainzArtistID = artistsMBID[i];
artists[i].artistMBID = artistsMBID[i];
}
}
@@ -134,7 +141,7 @@ getArtists(const TagLib::PropertyMap& properties,
static
PerformerContainer
getPerformerArtists(const TagLib::PropertyMap& properties,
getPerformerArtists(const TagMap& tags,
const std::vector<std::string_view>& artistTagNames)
{
PerformerContainer performers;
@@ -142,7 +149,7 @@ getPerformerArtists(const TagLib::PropertyMap& properties,
// picard stores like this: (see https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#performer)
// We may hit both styles for the same track
// PERFORMER: artist (role)
if (const std::vector<std::string> artistNames {getPropertyValuesFirstMatchAs<std::string>(properties, artistTagNames)}; !artistNames.empty())
if (const std::vector<std::string> artistNames {getPropertyValuesFirstMatchAs<std::string>(tags, artistTagNames)}; !artistNames.empty())
{
for (std::string_view entry : artistNames)
{
@@ -152,11 +159,11 @@ getPerformerArtists(const TagLib::PropertyMap& properties,
}
}
// PERFORMER:role (MP3)
for (const auto& [key, values] : properties)
for (const auto& [key, values] : tags)
{
if (key.startsWith("PERFORMER:"))
if (key.find("PERFORMER:") == 0)
{
std::string performerStr {key.to8Bit(true)};
std::string performerStr {key};
std::string role;
if (const std::size_t rolePos {performerStr.find(':')}; rolePos != std::string::npos)
{
@@ -165,7 +172,7 @@ getPerformerArtists(const TagLib::PropertyMap& properties,
}
for (const auto& value : values)
performers[role].push_back(Artist {value.to8Bit(true)});
performers[role].push_back(Artist {value});
}
}
@@ -173,19 +180,23 @@ getPerformerArtists(const TagLib::PropertyMap& properties,
}
static
std::optional<Album>
getAlbum(const TagLib::PropertyMap& properties)
std::optional<Release>
getRelease(const TagMap& tags)
{
std::vector<std::string> albumName {getPropertyValuesAs<std::string>(properties, "ALBUM")};
if (albumName.empty())
return std::nullopt;
std::optional<Release> release;
const std::vector<UUID> albumMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"})};
std::vector<std::string> releaseName {getPropertyValuesAs<std::string>(tags, "ALBUM")};
if (releaseName.empty())
return release;
if (albumMBID.empty())
return Album {std::move(albumName.front()), {}};
else
return Album {std::move(albumName.front()), albumMBID.front()};
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->releaseMBID = releaseMBID.front();
return release;
}
static
@@ -202,27 +213,28 @@ readStyleToTagLibReadStyle(ParserReadStyle readStyle)
throw LmsException {"Cannot convert read style"};
}
TagLibParser::TagLibParser(ParserReadStyle readStyle)
: _readStyle {readStyleToTagLibReadStyle(readStyle)}
{
}
void
TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug)
TagLibParser::processTag(Track& track, const std::string& tag, const std::vector<std::string>& values, bool debug)
{
if (debug)
{
std::vector<std::string> strs;
std::transform(values.begin(), values.end(), std::back_inserter(strs), [](const auto& value) { return value.to8Bit(true); });
std::cout << "[" << tag << "] = " << StringUtils::joinStrings(values, "*SEP*") << std::endl;
std::cout << "[" << tag << "] = " << StringUtils::joinStrings(strs, "*SEP*") << std::endl;
}
if (tag.empty() || values.isEmpty() || values.front().isEmpty())
if (tag.empty() || values.empty())
return;
std::string value {StringUtils::stringTrim(values.front().to8Bit(true))};
auto getOrCreateDisc = [&]() -> Disc&
{
if (!track.disc)
track.disc.emplace();
return *track.disc;
};
std::string_view value {values.front()};
if (tag == "TITLE")
track.title = value;
@@ -240,29 +252,26 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
track.acoustID = UUID::fromString(value);
else if (tag == "TRACKTOTAL")
{
auto totalTrack = StringUtils::readAs<std::size_t>(value);
if (totalTrack)
track.totalTrack = totalTrack;
getOrCreateDisc().totalTrack = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "TRACKNUMBER")
{
// Expecting 'Number/Total'
std::vector<std::string> strings {splitAndTrimString(value, "/")};
std::vector<std::string_view> strings {splitAndTrimString(value, "/")};
if (!strings.empty())
{
track.trackNumber = StringUtils::readAs<std::size_t>(strings[0]);
// Lower priority than TRACKTOTAL
if (strings.size() > 1 && !track.totalTrack)
track.totalTrack = StringUtils::readAs<std::size_t>(strings[1]);
if (strings.size() > 1 && !getOrCreateDisc().totalTrack)
getOrCreateDisc().totalTrack = StringUtils::readAs<std::size_t>(strings[1]);
}
}
else if (tag == "DISCTOTAL")
{
auto totalDisc = StringUtils::readAs<std::size_t>(value);
if (totalDisc)
track.totalDisc = totalDisc;
if (track.release)
track.release->totalDisc = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "DISCNUMBER")
{
@@ -273,8 +282,8 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
// Lower priority than DISCTOTAL
if (strings.size() > 1 && !track.totalDisc)
track.totalDisc = StringUtils::readAs<std::size_t>(strings[1]);
if (strings.size() > 1 && track.release && !track.release->totalDisc)
track.release->totalDisc = StringUtils::readAs<std::size_t>(strings[1]);
}
}
else if (tag == "DATE")
@@ -306,20 +315,20 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
else if (tag == "COPYRIGHTURL")
track.copyrightURL = value;
else if (tag == "REPLAYGAIN_ALBUM_GAIN")
track.albumReplayGain = StringUtils::readAs<float>(value);
getOrCreateDisc().replayGain = StringUtils::readAs<float>(value);
else if (tag == "REPLAYGAIN_TRACK_GAIN")
track.trackReplayGain = StringUtils::readAs<float>(value);
track.replayGain = StringUtils::readAs<float>(value);
else if (tag == "DISCSUBTITLE" || tag == "SETSUBTITLE")
track.discSubtitle = value;
getOrCreateDisc().subtitle = value;
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{
std::set<std::string> clusterNames;
for (const auto& valueList : values)
{
const auto splittedValues {splitAndTrimString(valueList.to8Bit(true), "/,;")};
const std::vector<std::string_view> splittedValues {splitAndTrimString(valueList, "/,;")};
for (const auto& value : splittedValues)
clusterNames.insert(value);
for (std::string_view value : splittedValues)
clusterNames.insert(std::string {value});
}
if (!clusterNames.empty())
@@ -327,6 +336,37 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
}
}
static
TagMap
constructTagMap(const TagLib::PropertyMap& properties)
{
TagMap tagMap;
for (const auto& [propertyName, propertyValues] : properties)
{
std::vector<std::string>& values {tagMap[propertyName.upper().to8Bit(true)]};
for (const TagLib::String& propertyValue : propertyValues)
{
std::string trimedValue {StringUtils::stringTrim(propertyValue.to8Bit(true))};
if (!trimedValue.empty())
values.emplace_back(std::move(trimedValue));
}
}
return tagMap;
}
static
void
mergeTagMaps(TagMap& dst, TagMap&& src)
{
for (auto&& [tag, values] : src)
{
if (dst.find(tag) == std::cend(dst))
dst[tag] = std::move(values);
}
}
std::optional<Track>
TagLibParser::parse(const std::filesystem::path& p, bool debug)
{
@@ -357,21 +397,14 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
track.audioStreams = {std::move(audioStream)};
}
TagLib::PropertyMap properties {f.file()->properties()};
TagMap tags {constructTagMap(f.file()->properties())};
auto getAPETags = [&](const TagLib::APE::Tag* apeTag)
{
if (!apeTag)
return;
for (const auto& [name, values] : apeTag->properties())
{
if (debug)
std::cout << "APE property: '" << name << "'" << std::endl;
if (!properties.contains(name))
properties.insert(name, values);
}
mergeTagMaps(tags, constructTagMap(apeTag->properties()));
};
// Not that good embedded pictures handling
@@ -387,23 +420,23 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
for (const auto& [name, attributeList] : tag->attributeListMap())
{
if (name.to8Bit().find("WM/") == 0 || properties.contains(name))
std::string strName {name.to8Bit(true)};
if (strName.find("WM/") == 0 || tags.find(strName) != std::cend(tags))
continue;
TagLib::StringList stringAttributeList;
std::vector<std::string> attributes;
for (const auto& attribute : attributeList)
{
if (attribute.type() == TagLib::ASF::Attribute::AttributeTypes::UnicodeType)
stringAttributeList.append(attribute.toString());
attributes.emplace_back(attribute.toString().to8Bit(true));
}
if (!stringAttributeList.isEmpty())
if (!attributes.empty())
{
if (debug)
std::cout << "ASF property: '" << name << "'" << std::endl;
if (!properties.contains(name))
properties.insert(name, stringAttributeList);
tags[strName] = std::move(attributes);
}
}
}
@@ -418,7 +451,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
if (!frameListMap["APIC"].isEmpty())
track.hasCover = true;
if (!frameListMap["TSST"].isEmpty())
properties.insert("DISCSUBTITLE", frameListMap["TSST"].front()->toString());
tags["DISCSUBTITLE"] = {frameListMap["TSST"].front()->toString().to8Bit(true)};
}
getAPETags(mp3File->APETag());
@@ -458,19 +491,20 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
track.hasCover = true;
}
for (const auto& [tag, values] : properties)
processTag(track, tag.upper().to8Bit(true), values, debug);
track.release = getRelease(tags);
if (track.release)
track.release->releaseArtists = 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.conductorArtists = getArtists(tags, {"CONDUCTORS", "CONDUCTOR"}, {"CONDUCTORSSORT", "CONDUCTORSORT"}, {});
track.composerArtists = getArtists(tags, {"COMPOSERS", "COMPOSER"}, {"COMPOSERSSORT", "COMPOSERSORT"}, {});
track.lyricistArtists = getArtists(tags, {"LYRICISTS", "LYRICIST"}, {"LYRICISTSSORT", "LYRICISTSORT"}, {});
track.mixerArtists = getArtists(tags, {"MIXERS", "MIXER"}, {"MIXERSSORT", "MIXERSORT"}, {});
track.producerArtists = getArtists(tags, {"PRODUCERS", "PRODUCER"}, {"PRODUCERSSORT", "PRODUCERSORT"}, {});
track.remixerArtists = getArtists(tags, {"REMIXERS", "REMIXER", "ModifiedBy"}, {"REMIXERSSORT", "REMIXERSORT"}, {});
track.performerArtists = getPerformerArtists(tags, {"PERFORMERS", "PERFORMER"});
track.album = getAlbum(properties);
track.artists = getArtists(properties, {"ARTISTS", "ARTIST"}, {"ARTISTSORT"}, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID"});
track.albumArtists = getArtists(properties, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"});
track.conductorArtists = getArtists(properties, {"CONDUCTORS", "CONDUCTOR"}, {"CONDUCTORSSORT", "CONDUCTORSORT"}, {});
track.composerArtists = getArtists(properties, {"COMPOSERS", "COMPOSER"}, {"COMPOSERSSORT", "COMPOSERSORT"}, {});
track.lyricistArtists = getArtists(properties, {"LYRICISTS", "LYRICIST"}, {"LYRICISTSSORT", "LYRICISTSORT"}, {});
track.mixerArtists = getArtists(properties, {"MIXERS", "MIXER"}, {"MIXERSSORT", "MIXERSORT"}, {});
track.producerArtists = getArtists(properties, {"PRODUCERS", "PRODUCER"}, {"PRODUCERSSORT", "PRODUCERSORT"}, {});
track.remixerArtists = getArtists(properties, {"REMIXERS", "REMIXER", "ModifiedBy"}, {"REMIXERSSORT", "REMIXERSORT"}, {});
track.performerArtists = getPerformerArtists(properties, {"PERFORMERS", "PERFORMER"});
for (const auto& [tag, values] : tags)
processTag(track, tag, values, debug);
return track;
}
+1 -1
View File
@@ -38,7 +38,7 @@ class TagLibParser : public IParser
private:
std::optional<Track> parse(const std::filesystem::path& p, bool debug = false) override;
void processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug);
void processTag(Track& track, const std::string& tag, const std::vector<std::string>& values, bool debug);
const TagLib::AudioProperties::ReadStyle _readStyle;
};
+2 -2
View File
@@ -28,7 +28,7 @@
namespace MetaData::Utils
{
Wt::WDate
parseDate(const std::string& dateStr)
parseDate(std::string_view dateStr)
{
static constexpr const char* formats[]
{
@@ -39,7 +39,7 @@ namespace MetaData::Utils
for (const char* format : formats)
{
std::tm tm = {};
std::stringstream ss {dateStr};
std::istringstream ss {std::string {dateStr}}; // TODO, remove extra copy here
ss >> std::get_time(&tm, format);
if (ss.fail())
continue;
+1 -1
View File
@@ -27,7 +27,7 @@
namespace MetaData::Utils
{
Wt::WDate parseDate(const std::string& dateStr);
Wt::WDate parseDate(std::string_view dateStr);
std::string_view readStyleToString(ParserReadStyle readStyle);
struct PerformerArtist
+27 -22
View File
@@ -39,18 +39,27 @@ namespace MetaData
{
std::string name;
std::optional<std::string> sortName;
std::optional<UUID> musicBrainzArtistID;
std::optional<UUID> artistMBID;
Artist(std::string_view _name) : name {_name} {}
Artist(std::string_view _name, std::optional<std::string> _sortName, std::optional<UUID> _musicBrainzArtistID) : name {_name}, sortName {_sortName}, musicBrainzArtistID {_musicBrainzArtistID} {}
Artist(std::string_view _name, std::optional<std::string> _sortName, std::optional<UUID> _artistMBID) : name {_name}, sortName {std::move(_sortName)}, artistMBID {std::move(_artistMBID)} {}
};
using PerformerContainer = std::map<std::string /*role*/, std::vector<Artist>>;
struct Album
struct Release
{
std::string name;
std::optional<UUID> musicBrainzAlbumID;
std::string name;
std::vector<Artist> releaseArtists;
std::optional<UUID> releaseMBID;
std::optional<std::size_t> totalDisc;
};
struct Disc
{
std::string subtitle;
std::optional<float> replayGain;
std::optional<std::size_t> totalTrack;
};
struct AudioStream
@@ -61,34 +70,30 @@ namespace MetaData
struct Track
{
std::vector<Artist> artists;
std::vector<Artist> albumArtists;
std::string title;
std::optional<UUID> trackMBID;
std::optional<UUID> recordingMBID;
std::optional<Album> album;
std::optional<Release> release;
std::optional<Disc> disc;
Clusters clusters;
std::chrono::milliseconds duration;
std::optional<std::size_t> trackNumber;
std::optional<std::size_t> totalTrack;
std::optional<std::size_t> discNumber;
std::optional<std::size_t> totalDisc;
Wt::WDate date;
Wt::WDate originalDate;
bool hasCover {};
std::vector<AudioStream> audioStreams;
std::optional<UUID> acoustID;
std::string copyright;
std::string copyrightURL;
std::optional<float> trackReplayGain;
std::optional<float> albumReplayGain;
std::string discSubtitle;
std::vector<Artist> conductorArtists;
std::vector<Artist> composerArtists;
std::vector<Artist> lyricistArtists;
std::vector<Artist> mixerArtists;
PerformerContainer performerArtists;
std::vector<Artist> producerArtists;
std::vector<Artist> remixerArtists;
std::optional<UUID> acoustID;
std::string copyright;
std::string copyrightURL;
std::optional<float> replayGain;
std::vector<Artist> conductorArtists;
std::vector<Artist> composerArtists;
std::vector<Artist> lyricistArtists;
std::vector<Artist> mixerArtists;
PerformerContainer performerArtists;
std::vector<Artist> producerArtists;
std::vector<Artist> remixerArtists;
};
class IParser
@@ -611,6 +611,48 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
ScanSettings::get(session).modify()->incScanVersion();
}
static
void
migrateFromV38(Session& session)
{
// migrate release-specific tags from Track to Release
session.getDboSession().execute("ALTER TABLE release ADD total_disc INTEGER");
session.getDboSession().execute(R"(
CREATE TABLE IF NOT EXISTS "track_backup" (
"id" integer primary key autoincrement,
"version" integer not null,
"scan_version" integer not null,
"track_number" integer,
"disc_number" integer,
"total_track" integer,
"disc_subtitle" text not null,
"name" text not null,
"duration" integer,
"date" text,
"original_date" text,
"file_path" text not null,
"file_last_write" text,
"file_added" text,
"has_cover" boolean not null,
"mbid" text not null,
"recording_mbid" text not null,
"copyright" text not null,
"copyright_url" text not null,
"track_replay_gain" real,
"release_replay_gain" real,
"release_id" bigint,
constraint "fk_track_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred
);
))");
session.getDboSession().execute("INSERT INTO track_backup SELECT id, version, scan_version, track_number, disc_number, total_track, disc_subtitle, name, duration, date, original_date, file_path, file_last_write, file_added, has_cover, mbid, recording_mbid, copyright, copyright_url, track_replay_gain, release_replay_gain, release_id FROM track");
session.getDboSession().execute("DROP TABLE track");
session.getDboSession().execute("ALTER TABLE track_backup RENAME TO track");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion();
}
void
doDbMigration(Session& session)
{
@@ -655,6 +697,7 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
{35, migrateFromV35},
{36, migrateFromV36},
{37, migrateFromV37},
{38, migrateFromV38},
};
while (1)
@@ -26,7 +26,7 @@ namespace Database
class Session;
using Version = std::size_t;
static constexpr Version LMS_DATABASE_VERSION {38};
static constexpr Version LMS_DATABASE_VERSION {39};
class VersionInfo
{
public:
@@ -263,30 +263,6 @@ Release::find(Session& session, const FindParameters& params)
return Utils::execQuery(query, params.range);
}
std::optional<std::size_t>
Release::getTotalTrack() const
{
assert(session());
int res = session()->query<int>("SELECT COALESCE(MAX(total_track),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.bind(getId());
return (res > 0) ? std::make_optional<std::size_t>(res) : std::nullopt;
}
std::optional<std::size_t>
Release::getTotalDisc() const
{
assert(session());
int res = session()->query<int>("SELECT COALESCE(MAX(total_disc),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.bind(getId());
return (res > 0) ? std::make_optional<std::size_t>(res) : std::nullopt;
}
std::size_t
Release::getDiscCount() const
{
-24
View File
@@ -361,30 +361,6 @@ Track::setClusters(const std::vector<ObjectPtr<Cluster>>& clusters)
_clusters.insert(getDboPtr(cluster));
}
std::optional<std::size_t>
Track::getTrackNumber() const
{
return (_trackNumber > 0) ? std::make_optional<std::size_t>(_trackNumber) : std::nullopt;
}
std::optional<std::size_t>
Track::getTotalTrack() const
{
return (_totalTrack > 0) ? std::make_optional<std::size_t>(_totalTrack) : std::nullopt;
}
std::optional<std::size_t>
Track::getDiscNumber() const
{
return (_discNumber > 0) ? std::make_optional<std::size_t>(_discNumber) : std::nullopt;
}
std::optional<std::size_t>
Track::getTotalDisc() const
{
return (_totalDisc > 0) ? std::make_optional<std::size_t>(_totalDisc) : std::nullopt;
}
std::optional<int>
Track::getYear() const
{
@@ -97,34 +97,37 @@ class Release : public Object<Release, ReleaseId>
// size is the max number of cluster per cluster type
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
// Utility functions
// Utility functions (if all tracks have the same values, which is legit to not be the case)
std::optional<int> getReleaseYear(bool originalDate = false) const;
std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const;
// Accessors
const std::string& getName() const { return _name; }
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
std::optional<std::size_t> getTotalTrack() const;
std::optional<std::size_t> getTotalDisc() const;
const std::string& getName() const { return _name; }
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
std::optional<std::size_t> getTotalDisc() const { return _totalDisc; }
std::size_t getDiscCount() const; // may not be total disc (if incomplete for example)
std::chrono::milliseconds getDuration() const;
Wt::WDateTime getLastWritten() const;
// Get the artists of this release
std::vector<ObjectPtr<Artist> > getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
std::vector<ObjectPtr<Artist> > getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
bool hasVariousArtists() const;
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
// Setters
void setName(std::string_view name) { _name = name; }
void setMBID(const std::optional<UUID>& mbid) { _MBID = mbid ? mbid->getAsString() : ""; }
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
// Get the artists of this release
std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
std::vector<ObjectPtr<Artist>> getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
bool hasVariousArtists() const;
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
void setName(std::string_view name) { _name = name; }
void setMBID(const std::optional<UUID>& mbid) { _MBID = mbid ? mbid->getAsString() : ""; }
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _MBID, "mbid");
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _MBID, "mbid");
Wt::Dbo::field(a, _totalDisc, "total_disc");
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
}
@@ -136,8 +139,9 @@ class Release : public Object<Release, ReleaseId>
static constexpr std::size_t _maxNameLength {128};
std::string _name;
std::string _MBID;
std::string _name;
std::string _MBID;
std::optional<int> _totalDisc {};
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
};
@@ -119,60 +119,57 @@ class Track : public Object<Track, TrackId>
static RangeResults<TrackId> findWithRecordingMBIDAndMissingFeatures(Session& session, Range range);
// Accessors
void setScanVersion(std::size_t version) { _scanVersion = version; }
void setTrackNumber(int num) { _trackNumber = num; }
void setDiscNumber(int num) { _discNumber = num; }
void setTotalTrack(std::optional<int> totalTrack) { _totalTrack = totalTrack ? *totalTrack : 0; }
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc ? *totalDisc : 0; }
void setScanVersion(std::size_t version) { _scanVersion = version; }
void setTrackNumber(std::optional<int> num) { _trackNumber = num; }
void setDiscNumber(std::optional<int> num) { _discNumber = num; }
void setTotalTrack(std::optional<int> totalTrack) { _totalTrack = totalTrack; }
void setDiscSubtitle(const std::string& name) { _discSubtitle = name; }
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
void setPath(const std::filesystem::path& filePath) { _filePath = filePath; }
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
void setAddedTime(Wt::WDateTime time) { _fileAdded = time; }
void setDate(const Wt::WDate& date) { _date = date; }
void setOriginalDate(const Wt::WDate& date) { _originalDate = date; }
void setHasCover(bool hasCover) { _hasCover = hasCover; }
void setTrackMBID(const std::optional<UUID>& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; }
void setRecordingMBID(const std::optional<UUID>& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; }
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
void setPath(const std::filesystem::path& filePath) { _filePath = filePath; }
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
void setAddedTime(Wt::WDateTime time) { _fileAdded = time; }
void setDate(const Wt::WDate& date) { _date = date; }
void setOriginalDate(const Wt::WDate& date) { _originalDate = date; }
void setHasCover(bool hasCover) { _hasCover = hasCover; }
void setTrackMBID(const std::optional<UUID>& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; }
void setRecordingMBID(const std::optional<UUID>& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; }
void setCopyright(const std::string& copyright) { _copyright = std::string(copyright, 0, _maxCopyrightLength); }
void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; }
void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; }
void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; }
void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc!
void clearArtistLinks();
void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink);
void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); }
void setClusters(const std::vector<ObjectPtr<Cluster>>& clusters );
std::size_t getScanVersion() const { return _scanVersion; }
std::optional<std::size_t> getTrackNumber() const;
std::optional<std::size_t> getTotalTrack() const;
std::optional<std::size_t> getDiscNumber() const;
std::optional<std::size_t> getTrackNumber() const { return _trackNumber; }
std::optional<std::size_t> getTotalTrack() const { return _totalTrack; }
std::optional<std::size_t> getDiscNumber() const { return _discNumber; }
const std::string& getDiscSubtitle() const { return _discSubtitle; }
std::optional<std::size_t> getTotalDisc() const;
std::string getName() const { return _name; }
std::filesystem::path getPath() const { return _filePath; }
std::chrono::milliseconds getDuration() const { return _duration; }
const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; }
std::optional<int> getYear() const;
std::optional<int> getOriginalYear() const;
Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; }
Wt::WDateTime getAddedTime() const { return _fileAdded; }
bool hasCover() const { return _hasCover; }
Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; }
Wt::WDateTime getAddedTime() const { return _fileAdded; }
bool hasCover() const { return _hasCover; }
std::optional<UUID> getTrackMBID() const { return UUID::fromString(_trackMBID); }
std::optional<UUID> getRecordingMBID() const { return UUID::fromString(_recordingMBID); }
std::optional<UUID> getRecordingMBID() const { return UUID::fromString(_recordingMBID); }
std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const;
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
// no artistLinkTypes means get all
std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const;
ObjectPtr<Release> getRelease() const { return _release; }
std::vector<ObjectPtr<Cluster>> getClusters() const;
std::vector<ClusterId> getClusterIds() const;
ObjectPtr<Release> getRelease() const { return _release; }
std::vector<ObjectPtr<Cluster>> getClusters() const;
std::vector<ClusterId> getClusterIds() const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
@@ -182,9 +179,8 @@ class Track : public Object<Track, TrackId>
Wt::Dbo::field(a, _scanVersion, "scan_version");
Wt::Dbo::field(a, _trackNumber, "track_number");
Wt::Dbo::field(a, _discNumber, "disc_number");
Wt::Dbo::field(a, _discSubtitle, "disc_subtitle");
Wt::Dbo::field(a, _totalTrack, "total_track");
Wt::Dbo::field(a, _totalDisc, "total_disc");
Wt::Dbo::field(a, _totalTrack, "total_track"); // here in Track since Release does not have concept of "disc" (yet?)
Wt::Dbo::field(a, _discSubtitle, "disc_subtitle"); // here in Track since Release does not have concept of "disc" (yet?)
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _duration, "duration");
Wt::Dbo::field(a, _date, "date");
@@ -198,7 +194,7 @@ class Track : public Object<Track, TrackId>
Wt::Dbo::field(a, _copyright, "copyright");
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain");
Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain");
Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?)
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
@@ -214,14 +210,11 @@ class Track : public Object<Track, TrackId>
static constexpr std::size_t _maxCopyrightURLLength {128};
int _scanVersion {};
int _trackNumber {};
int _discNumber {};
std::optional<int> _trackNumber {};
std::optional<int> _discNumber {};
std::optional<int> _totalTrack {};
std::string _discSubtitle;
int _totalTrack {};
int _totalDisc {};
std::string _name;
std::string _artistName;
std::string _releaseName;
std::chrono::duration<int, std::milli> _duration {};
Wt::WDate _date;
Wt::WDate _originalDate;
@@ -236,9 +229,9 @@ class Track : public Object<Track, TrackId>
std::optional<float> _trackReplayGain;
std::optional<float> _releaseReplayGain;
Wt::Dbo::ptr<Release> _release;
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> _clusters;
Wt::Dbo::ptr<Release> _release;
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> _clusters;
};
namespace Debug
+13 -16
View File
@@ -187,7 +187,6 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
{
auto transaction {session.createSharedTransaction()};
EXPECT_FALSE(release1->getTotalTrack());
EXPECT_FALSE(release1->getTotalDisc());
}
@@ -201,7 +200,6 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
{
auto transaction {session.createSharedTransaction()};
EXPECT_FALSE(release1->getTotalTrack());
EXPECT_FALSE(release1->getTotalDisc());
}
@@ -209,14 +207,14 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
auto transaction {session.createUniqueTransaction()};
track1.get().modify()->setTotalTrack(36);
track1.get().modify()->setTotalDisc(6);
release1.get().modify()->setTotalDisc(6);
}
{
auto transaction {session.createSharedTransaction()};
ASSERT_TRUE(release1->getTotalTrack());
EXPECT_EQ(*release1->getTotalTrack(), 36);
ASSERT_TRUE(track1->getTotalTrack());
EXPECT_EQ(*track1->getTotalTrack(), 36);
ASSERT_TRUE(release1->getTotalDisc());
EXPECT_EQ(*release1->getTotalDisc(), 6);
}
@@ -227,14 +225,14 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
track2.get().modify()->setRelease(release1.get());
track2.get().modify()->setTotalTrack(37);
track2.get().modify()->setTotalDisc(67);
release1.get().modify()->setTotalDisc(67);
}
{
auto transaction {session.createSharedTransaction()};
ASSERT_TRUE(release1->getTotalTrack());
EXPECT_EQ(*release1->getTotalTrack(), 37);
ASSERT_TRUE(track1->getTotalTrack());
EXPECT_EQ(*track1->getTotalTrack(), 36);
ASSERT_TRUE(release1->getTotalDisc());
EXPECT_EQ(*release1->getTotalDisc(), 67);
}
@@ -243,7 +241,6 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
{
auto transaction {session.createSharedTransaction()};
EXPECT_FALSE(release2->getTotalTrack());
EXPECT_FALSE(release2->getTotalDisc());
}
@@ -253,17 +250,17 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
track3.get().modify()->setRelease(release2.get());
track3.get().modify()->setTotalTrack(7);
track3.get().modify()->setTotalDisc(5);
release2.get().modify()->setTotalDisc(5);
}
{
auto transaction {session.createSharedTransaction()};
ASSERT_TRUE(release1->getTotalTrack());
EXPECT_EQ(*release1->getTotalTrack(), 37);
ASSERT_TRUE(track1->getTotalTrack());
EXPECT_EQ(*track1->getTotalTrack(), 36);
ASSERT_TRUE(release1->getTotalDisc());
EXPECT_EQ(*release1->getTotalDisc(), 67);
ASSERT_TRUE(release2->getTotalTrack());
EXPECT_EQ(*release2->getTotalTrack(), 7);
EXPECT_EQ(*release2->getTotalDisc(), 5);
ASSERT_TRUE(track3->getTotalTrack());
EXPECT_EQ(*track3->getTotalTrack(), 7);
ASSERT_TRUE(release2->getTotalDisc());
EXPECT_EQ(*release2->getTotalDisc(), 5);
}
@@ -482,7 +479,7 @@ TEST_F(DatabaseFixture, Release_getDiscCount)
}
{
auto transaction {session.createSharedTransaction()};
EXPECT_EQ(release.get()->getDiscCount(), 1);
EXPECT_EQ(release.get()->getDiscCount(), 0);
}
{
auto transaction {session.createUniqueTransaction()};
@@ -42,8 +42,8 @@ namespace
{
Artist::pointer artist {session.create<Artist>(artistInfo.name)};
if (artistInfo.musicBrainzArtistID)
artist.modify()->setMBID(*artistInfo.musicBrainzArtistID);
if (artistInfo.artistMBID)
artist.modify()->setMBID(*artistInfo.artistMBID);
if (artistInfo.sortName)
artist.modify()->setSortName(*artistInfo.sortName);
@@ -76,9 +76,9 @@ namespace
Artist::pointer artist;
// First try to get by MBID
if (artistInfo.musicBrainzArtistID)
if (artistInfo.artistMBID)
{
artist = Artist::find(session, *artistInfo.musicBrainzArtistID);
artist = Artist::find(session, *artistInfo.artistMBID);
if (!artist)
artist = createArtist(session, artistInfo);
else
@@ -115,45 +115,49 @@ namespace
return artists;
}
void
updateReleaseIfNeeded(Release::pointer release, const MetaData::Release& releaseInfo)
{
if (release->getName() != releaseInfo.name)
release.modify()->setName(releaseInfo.name);
if (release->getTotalDisc() != releaseInfo.totalDisc)
release.modify()->setTotalDisc(releaseInfo.totalDisc);
}
Release::pointer
getOrCreateRelease(Session& session, const MetaData::Album& album)
getOrCreateRelease(Session& session, const MetaData::Release& releaseInfo)
{
Release::pointer release;
// First try to get by MBID
if (album.musicBrainzAlbumID)
if (releaseInfo.releaseMBID)
{
release = Release::find(session, *album.musicBrainzAlbumID);
release = Release::find(session, *releaseInfo.releaseMBID);
if (!release)
{
release = session.create<Release>(album.name, album.musicBrainzAlbumID);
}
else if (release->getName() != album.name)
{
// Name may have been updated
release.modify()->setName(album.name);
}
release = session.create<Release>(releaseInfo.name, releaseInfo.releaseMBID);
updateReleaseIfNeeded(release, releaseInfo);
return release;
}
// Fall back on release name (collisions may occur)
if (!album.name.empty())
if (!releaseInfo.name.empty())
{
for (const Release::pointer& sameNamedRelease : Release::find(session, album.name))
for (const Release::pointer& sameNamedRelease : Release::find(session, releaseInfo.name))
{
// do not fallback on properly tagged releases
if (!sameNamedRelease->getMBID())
{
release = sameNamedRelease;
break;
}
if (sameNamedRelease->getMBID())
continue;
release = sameNamedRelease;
break;
}
// No release found with the same name and without MBID -> creating
if (!release)
release = session.create<Release>(album.name);
release = session.create<Release>(releaseInfo.name);
updateReleaseIfNeeded(release, releaseInfo);
return release;
}
@@ -389,8 +393,11 @@ namespace Scanner
for (const Artist::pointer& artist : getOrCreateArtists(dbSession, trackInfo->artists, false))
track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, artist, TrackArtistLinkType::Artist));
for (const Artist::pointer& releaseArtist : getOrCreateArtists(dbSession, trackInfo->albumArtists, false))
track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, releaseArtist, TrackArtistLinkType::ReleaseArtist));
if (trackInfo->release)
{
for (const Artist::pointer& releaseArtist : getOrCreateArtists(dbSession, trackInfo->release->releaseArtists, false))
track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, releaseArtist, TrackArtistLinkType::ReleaseArtist));
}
// Allow fallbacks on artists with the same name even if they have MBID, since there is no tag to indicate the MBID of these artists
// We could ask MusicBrainz to get all the information, but that would heavily slow down the import process
@@ -419,10 +426,13 @@ namespace Scanner
track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, remixer, TrackArtistLinkType::Remixer));
track.modify()->setScanVersion(_settings.scanVersion);
if (trackInfo->album)
track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->album));
if (trackInfo->release)
track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->release));
else
track.modify()->setRelease({});
track.modify()->setTotalTrack(trackInfo->disc ? trackInfo->disc->totalTrack : std::nullopt);
track.modify()->setReleaseReplayGain(trackInfo->disc ? trackInfo->disc->replayGain : std::nullopt);
track.modify()->setDiscSubtitle(trackInfo->disc ? trackInfo->disc->subtitle : "");
track.modify()->setClusters(getOrCreateClusters(dbSession, trackInfo->clusters));
track.modify()->setLastWriteTime(lastWriteTime);
track.modify()->setName(title);
@@ -430,9 +440,6 @@ namespace Scanner
track.modify()->setAddedTime(Wt::WDateTime::currentDateTime());
track.modify()->setTrackNumber(trackInfo->trackNumber ? *trackInfo->trackNumber : 0);
track.modify()->setDiscNumber(trackInfo->discNumber ? *trackInfo->discNumber : 0);
track.modify()->setTotalTrack(trackInfo->totalTrack);
track.modify()->setTotalDisc(trackInfo->totalDisc);
track.modify()->setDiscSubtitle(trackInfo->discSubtitle);
track.modify()->setDate(trackInfo->date);
track.modify()->setOriginalDate(trackInfo->originalDate);
@@ -447,7 +454,6 @@ namespace Scanner
track.modify()->setHasCover(trackInfo->hasCover);
track.modify()->setCopyright(trackInfo->copyright);
track.modify()->setCopyrightURL(trackInfo->copyrightURL);
track.modify()->setTrackReplayGain(trackInfo->trackReplayGain);
track.modify()->setReleaseReplayGain(trackInfo->albumReplayGain);
track.modify()->setTrackReplayGain(trackInfo->replayGain);
}
}
+2 -2
View File
@@ -132,10 +132,10 @@ stringTrim(std::string_view str, std::string_view whitespaces)
return res;
}
std::string
std::string_view
stringTrimEnd(std::string_view str, std::string_view whitespaces)
{
return std::string {str.substr(0, str.find_last_not_of(whitespaces) + 1)};
return str.substr(0, str.find_last_not_of(whitespaces) + 1);
}
std::string
+1 -1
View File
@@ -48,7 +48,7 @@ std::string_view
stringTrim(std::string_view str, std::string_view whitespaces = " \t");
[[nodiscard]]
std::string
std::string_view
stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t");
[[nodiscard]]
-1
View File
@@ -28,7 +28,6 @@
#include <Wt/WStackedWidget.h>
#include <Wt/WTemplateFormView.h>
#include "services/database/Cluster.hpp"
#include "services/database/Release.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
+40 -26
View File
@@ -29,12 +29,14 @@
#include "metadata/IParser.hpp"
#include "utils/StreamLogger.hpp"
std::ostream& operator<<(std::ostream& os, const MetaData::Artist& artist)
static
std::ostream&
operator<<(std::ostream& os, const MetaData::Artist& artist)
{
os << artist.name;
if (artist.musicBrainzArtistID)
os << " (" << artist.musicBrainzArtistID->getAsString() << ")";
if (artist.artistMBID)
os << " (" << artist.artistMBID->getAsString() << ")";
if (artist.sortName)
os << " '" << *artist.sortName << "'";
@@ -42,12 +44,36 @@ std::ostream& operator<<(std::ostream& os, const MetaData::Artist& artist)
return os;
}
std::ostream& operator<<(std::ostream& os, const MetaData::Album& album)
static
std::ostream&
operator<<(std::ostream& os, const MetaData::Release& release)
{
os << album.name;
os << release.name;
if (album.musicBrainzAlbumID)
os << " (" << album.musicBrainzAlbumID->getAsString() << ")";
if (release.releaseMBID)
os << " (" << release.releaseMBID->getAsString() << ")" << std::endl;
if (release.totalDisc)
std::cout << "\tTotalDisc: " << *release.totalDisc << std::endl;
for (const MetaData::Artist& artist : release.releaseArtists)
std::cout << "\tAlbum artist: " << artist << std::endl;
return os;
}
static
std::ostream&
operator<<(std::ostream& os, const MetaData::Disc& disc)
{
if (!disc.subtitle.empty())
os << disc.subtitle << std::endl;
if (disc.totalTrack)
std::cout << "\tTotalTrack: " << *disc.totalTrack << std::endl;
if (disc.replayGain)
std::cout << "\tDisc replay gain: " << *disc.replayGain << std::endl;
return os;
}
@@ -74,9 +100,6 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
for (const Artist& artist : track->artists)
std::cout << "Artist: " << artist << std::endl;
for (const Artist& artist : track->albumArtists)
std::cout << "Album artist: " << artist << std::endl;
for (const Artist& artist : track->conductorArtists)
std::cout << "Conductor: " << artist << std::endl;
@@ -105,8 +128,11 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
for (const Artist& artist : track->remixerArtists)
std::cout << "Remixer: " << artist << std::endl;
if (track->album)
std::cout << "Album: " << *track->album << std::endl;
if (track->release)
std::cout << "Release: " << *track->release;
if (track->disc)
std::cout << "Disc: " << *track->disc;
std::cout << "Title: " << track->title << std::endl;
@@ -130,18 +156,9 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
if (track->trackNumber)
std::cout << "Track: " << *track->trackNumber << std::endl;
if (track->totalTrack)
std::cout << "TotalTrack: " << *track->totalTrack << std::endl;
if (track->discNumber)
std::cout << "Disc: " << *track->discNumber << std::endl;
if (!track->discSubtitle.empty())
std::cout << "Disc Subtitle: " << track->discSubtitle << std::endl;
if (track->totalDisc)
std::cout << "TotalDisc: " << *track->totalDisc << std::endl;
if (track->date.isValid())
std::cout << "Date: " << track->date.toString("yyyy-MM-dd") << std::endl;
@@ -153,11 +170,8 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
for (const auto& audioStream : track->audioStreams)
std::cout << "Audio stream: " << audioStream.bitRate << " bps" << std::endl;
if (track->trackReplayGain)
std::cout << "Track replay gain: " << *track->trackReplayGain << std::endl;
if (track->albumReplayGain)
std::cout << "Album replay gain: " << *track->albumReplayGain << std::endl;
if (track->replayGain)
std::cout << "Track replay gain: " << *track->replayGain << std::endl;
if (track->acoustID)
std::cout << "AcoustID: " << track->acoustID->getAsString() << std::endl;