WMA: better metadata parsing. fixes #40
This commit is contained in:
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
|
|
||||||
#define LMS_DATABASE_VERSION 13
|
#define LMS_DATABASE_VERSION 14
|
||||||
|
|
||||||
using Version = std::size_t;
|
using Version = std::size_t;
|
||||||
|
|
||||||
@@ -153,7 +153,13 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
|||||||
}
|
}
|
||||||
else if (version == 12)
|
else if (version == 12)
|
||||||
{
|
{
|
||||||
// Artist and release that have a baddly parsed name but a MBID had no chance to updat the name
|
// Artist and release that have a badly parsed name but a MBID had no chance to updat the name
|
||||||
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
|
ScanSettings::get(*this).modify()->incScanVersion();
|
||||||
|
}
|
||||||
|
else if (version == 13)
|
||||||
|
{
|
||||||
|
// Always store UUID in lower case + better WMA parsing
|
||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(*this).modify()->incScanVersion();
|
ScanSettings::get(*this).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,12 +154,118 @@ getAlbum(const TagLib::PropertyMap& properties)
|
|||||||
return Album {std::move(albumName.front()), albumMBID.front()};
|
return Album {std::move(albumName.front()), albumMBID.front()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug)
|
||||||
|
{
|
||||||
|
|
||||||
|
// TODO validate MBID format
|
||||||
|
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(strs, "*SEP*") << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag.empty() || values.isEmpty() || values.front().isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string value {StringUtils::stringTrim(values.front().to8Bit(true))};
|
||||||
|
|
||||||
|
if (tag == "TITLE")
|
||||||
|
track.title = value;
|
||||||
|
else if (tag == "MUSICBRAINZ_RELEASETRACKID"
|
||||||
|
|| tag == "MUSICBRAINZ RELEASE TRACK ID")
|
||||||
|
{
|
||||||
|
track.musicBrainzTrackID = UUID::fromString(value);
|
||||||
|
}
|
||||||
|
else if (tag == "MUSICBRAINZ_TRACKID"
|
||||||
|
|| tag == "MUSICBRAINZ TRACK ID")
|
||||||
|
track.musicBrainzRecordID = UUID::fromString(value);
|
||||||
|
else if (tag == "ACOUSTID_ID")
|
||||||
|
track.acoustID = UUID::fromString(value);
|
||||||
|
else if (tag == "TRACKTOTAL")
|
||||||
|
{
|
||||||
|
auto totalTrack = StringUtils::readAs<std::size_t>(value);
|
||||||
|
if (totalTrack)
|
||||||
|
track.totalTrack = totalTrack;
|
||||||
|
}
|
||||||
|
else if (tag == "TRACKNUMBER")
|
||||||
|
{
|
||||||
|
// Expecting 'Number/Total'
|
||||||
|
std::vector<std::string> 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tag == "DISCTOTAL")
|
||||||
|
{
|
||||||
|
auto totalDisc = StringUtils::readAs<std::size_t>(value);
|
||||||
|
if (totalDisc)
|
||||||
|
track.totalDisc = totalDisc;
|
||||||
|
}
|
||||||
|
else if (tag == "DISCNUMBER")
|
||||||
|
{
|
||||||
|
// Expecting 'Number/Total'
|
||||||
|
std::vector<std::string> strings {StringUtils::splitString(value, "/")};
|
||||||
|
|
||||||
|
if (!strings.empty())
|
||||||
|
{
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tag == "DATE")
|
||||||
|
track.year = StringUtils::readAs<int>(value);
|
||||||
|
else if (tag == "ORIGINALDATE" && !track.originalYear)
|
||||||
|
{
|
||||||
|
// Lower priority than ORIGINALYEAR
|
||||||
|
track.originalYear = StringUtils::readAs<int>(value);
|
||||||
|
}
|
||||||
|
else if (tag == "ORIGINALYEAR")
|
||||||
|
{
|
||||||
|
// Higher priority than ORIGINALDATE
|
||||||
|
auto originalYear = StringUtils::readAs<int>(value);
|
||||||
|
if (originalYear)
|
||||||
|
track.originalYear = originalYear;
|
||||||
|
}
|
||||||
|
else if (tag == "METADATA_BLOCK_PICTURE")
|
||||||
|
track.hasCover = true;
|
||||||
|
else if (tag == "COPYRIGHT")
|
||||||
|
track.copyright = value;
|
||||||
|
else if (tag == "COPYRIGHTURL")
|
||||||
|
track.copyrightURL = value;
|
||||||
|
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||||
|
{
|
||||||
|
std::set<std::string> clusterNames;
|
||||||
|
for (const auto& valueList : values)
|
||||||
|
{
|
||||||
|
auto values = splitAndTrimString(valueList.to8Bit(true), "/,;");
|
||||||
|
|
||||||
|
for (const auto& value : values)
|
||||||
|
clusterNames.insert(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clusterNames.empty())
|
||||||
|
track.clusters[tag] = clusterNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<Track>
|
std::optional<Track>
|
||||||
TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
||||||
{
|
{
|
||||||
TagLib::FileRef f {p.string().c_str(),
|
TagLib::FileRef f {p.string().c_str(),
|
||||||
true, // read audio properties
|
true, // read audio properties
|
||||||
TagLib::AudioProperties::Fast};
|
TagLib::AudioProperties::Fast};
|
||||||
|
|
||||||
if (f.isNull())
|
if (f.isNull())
|
||||||
{
|
{
|
||||||
@@ -184,14 +290,40 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
|||||||
track.audioStreams = {std::move(audioStream)};
|
track.audioStreams = {std::move(audioStream)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TagLib::PropertyMap properties {f.file()->properties()};
|
||||||
|
|
||||||
// Not that good embedded pictures handling
|
// Not that good embedded pictures handling
|
||||||
|
|
||||||
// WMA
|
// WMA
|
||||||
if (TagLib::ASF::File* asfFile {dynamic_cast<TagLib::ASF::File*>(f.file())})
|
if (TagLib::ASF::File* asfFile {dynamic_cast<TagLib::ASF::File*>(f.file())})
|
||||||
{
|
{
|
||||||
const TagLib::ASF::Tag* tag {asfFile->tag()};
|
const TagLib::ASF::Tag* tag {asfFile->tag()};
|
||||||
if (tag && tag->attributeListMap().contains("WM/Picture"))
|
if (tag)
|
||||||
track.hasCover = true;
|
{
|
||||||
|
if (tag->attributeListMap().contains("WM/Picture"))
|
||||||
|
track.hasCover = true;
|
||||||
|
|
||||||
|
for (const auto& [name, attributeList] : tag->attributeListMap())
|
||||||
|
{
|
||||||
|
if (name.to8Bit().find("WM/") == 0 || properties.contains(name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
TagLib::StringList stringAttributeList;
|
||||||
|
for (const auto& attribute : attributeList)
|
||||||
|
{
|
||||||
|
if (attribute.type() == TagLib::ASF::Attribute::AttributeTypes::UnicodeType)
|
||||||
|
stringAttributeList.append(attribute.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stringAttributeList.isEmpty())
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
std::cout << "Property: '" << name << "'" << std::endl;
|
||||||
|
|
||||||
|
properties.insert(name, stringAttributeList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// MP3
|
// MP3
|
||||||
else if (TagLib::MPEG::File* mp3File {dynamic_cast<TagLib::MPEG::File*>(f.file())})
|
else if (TagLib::MPEG::File* mp3File {dynamic_cast<TagLib::MPEG::File*>(f.file())})
|
||||||
@@ -209,124 +341,18 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
|||||||
track.hasCover = true;
|
track.hasCover = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f.tag())
|
for(const auto& property : properties)
|
||||||
{
|
{
|
||||||
MetaData::Clusters clusters;
|
const std::string tag {property.first.upper().to8Bit(true)};
|
||||||
const TagLib::PropertyMap& properties {f.file()->properties()};
|
const TagLib::StringList& values {property.second};
|
||||||
|
|
||||||
for(const auto& property : properties)
|
|
||||||
{
|
|
||||||
const std::string tag {property.first.upper().to8Bit(true)};
|
|
||||||
const TagLib::StringList& values {property.second};
|
|
||||||
|
|
||||||
// TODO validate MBID format
|
|
||||||
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(strs, "*SEP*") << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag.empty() || values.isEmpty() || values.front().isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::string value {StringUtils::stringTrim(values.front().to8Bit(true))};
|
|
||||||
|
|
||||||
if (tag == "TITLE")
|
|
||||||
track.title = value;
|
|
||||||
else if (tag == "MUSICBRAINZ_RELEASETRACKID"
|
|
||||||
|| tag == "MUSICBRAINZ RELEASE TRACK ID")
|
|
||||||
{
|
|
||||||
track.musicBrainzTrackID = UUID::fromString(value);
|
|
||||||
}
|
|
||||||
else if (tag == "MUSICBRAINZ_TRACKID"
|
|
||||||
|| tag == "MUSICBRAINZ TRACK ID")
|
|
||||||
track.musicBrainzRecordID = UUID::fromString(value);
|
|
||||||
else if (tag == "ACOUSTID_ID")
|
|
||||||
track.acoustID = UUID::fromString(value);
|
|
||||||
else if (tag == "TRACKTOTAL")
|
|
||||||
{
|
|
||||||
auto totalTrack = StringUtils::readAs<std::size_t>(value);
|
|
||||||
if (totalTrack)
|
|
||||||
track.totalTrack = totalTrack;
|
|
||||||
}
|
|
||||||
else if (tag == "TRACKNUMBER")
|
|
||||||
{
|
|
||||||
// Expecting 'Number/Total'
|
|
||||||
std::vector<std::string> 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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (tag == "DISCTOTAL")
|
|
||||||
{
|
|
||||||
auto totalDisc = StringUtils::readAs<std::size_t>(value);
|
|
||||||
if (totalDisc)
|
|
||||||
track.totalDisc = totalDisc;
|
|
||||||
}
|
|
||||||
else if (tag == "DISCNUMBER")
|
|
||||||
{
|
|
||||||
// Expecting 'Number/Total'
|
|
||||||
std::vector<std::string> strings {StringUtils::splitString(value, "/")};
|
|
||||||
|
|
||||||
if (!strings.empty())
|
|
||||||
{
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (tag == "DATE")
|
|
||||||
track.year = StringUtils::readAs<int>(value);
|
|
||||||
else if (tag == "ORIGINALDATE" && !track.originalYear)
|
|
||||||
{
|
|
||||||
// Lower priority than ORIGINALYEAR
|
|
||||||
track.originalYear = StringUtils::readAs<int>(value);
|
|
||||||
}
|
|
||||||
else if (tag == "ORIGINALYEAR")
|
|
||||||
{
|
|
||||||
// Higher priority than ORIGINALDATE
|
|
||||||
auto originalYear = StringUtils::readAs<int>(value);
|
|
||||||
if (originalYear)
|
|
||||||
track.originalYear = originalYear;
|
|
||||||
}
|
|
||||||
else if (tag == "METADATA_BLOCK_PICTURE")
|
|
||||||
track.hasCover = true;
|
|
||||||
else if (tag == "COPYRIGHT")
|
|
||||||
track.copyright = value;
|
|
||||||
else if (tag == "COPYRIGHTURL")
|
|
||||||
track.copyrightURL = value;
|
|
||||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
|
||||||
{
|
|
||||||
std::set<std::string> clusterNames;
|
|
||||||
for (const auto& valueList : values)
|
|
||||||
{
|
|
||||||
auto values = splitAndTrimString(valueList.to8Bit(true), "/,;");
|
|
||||||
|
|
||||||
for (const auto& value : values)
|
|
||||||
clusterNames.insert(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!clusterNames.empty())
|
|
||||||
track.clusters[tag] = clusterNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
track.artists = getArtists(properties);
|
|
||||||
track.albumArtists = getAlbumArtists(properties);
|
|
||||||
track.album = getAlbum(properties);
|
|
||||||
|
|
||||||
|
processTag(track, tag, values, debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
track.artists = getArtists(properties);
|
||||||
|
track.albumArtists = getAlbumArtists(properties);
|
||||||
|
track.album = getAlbum(properties);
|
||||||
|
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,21 @@
|
|||||||
|
|
||||||
#include "metadata/IParser.hpp"
|
#include "metadata/IParser.hpp"
|
||||||
|
|
||||||
|
namespace TagLib
|
||||||
|
{
|
||||||
|
class StringList;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MetaData
|
namespace MetaData
|
||||||
{
|
{
|
||||||
|
|
||||||
// Parse that makes use of AvFormat
|
// Parse that makes use of AvFormat
|
||||||
class TagLibParser : public IParser
|
class TagLibParser : public IParser
|
||||||
{
|
{
|
||||||
public:
|
private:
|
||||||
std::optional<Track> parse(const std::filesystem::path& p, bool debug = false) override;
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace MetaData
|
} // namespace MetaData
|
||||||
|
|||||||
Reference in New Issue
Block a user