WMA: better metadata parsing. fixes #40

This commit is contained in:
emeric
2020-03-23 20:16:12 +01:00
parent a40c05ec6b
commit bd6a08facb
3 changed files with 160 additions and 121 deletions
+8 -2
View File
@@ -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();
} }
+92 -66
View File
@@ -154,70 +154,9 @@ getAlbum(const TagLib::PropertyMap& properties)
return Album {std::move(albumName.front()), albumMBID.front()}; return Album {std::move(albumName.front()), albumMBID.front()};
} }
std::optional<Track> void
TagLibParser::parse(const std::filesystem::path& p, bool debug) TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::StringList& values, bool debug)
{ {
TagLib::FileRef f {p.string().c_str(),
true, // read audio properties
TagLib::AudioProperties::Fast};
if (f.isNull())
{
LMS_LOG(METADATA, ERROR) << "File '" << p.string() << "': parsing failed";
return std::nullopt;
}
if (!f.audioProperties())
{
LMS_LOG(METADATA, INFO) << "File '" << p.string() << "': no audio properties";
return std::nullopt;
}
Track track;
{
const TagLib::AudioProperties *properties {f.audioProperties() };
track.duration = std::chrono::milliseconds {properties->length() * 1000};
MetaData::AudioStream audioStream {static_cast<unsigned>(properties->bitrate() * 1000)};
track.audioStreams = {std::move(audioStream)};
}
// Not that good embedded pictures handling
// WMA
if (TagLib::ASF::File* asfFile {dynamic_cast<TagLib::ASF::File*>(f.file())})
{
const TagLib::ASF::Tag* tag {asfFile->tag()};
if (tag && tag->attributeListMap().contains("WM/Picture"))
track.hasCover = true;
}
// MP3
else if (TagLib::MPEG::File* mp3File {dynamic_cast<TagLib::MPEG::File*>(f.file())})
{
if (mp3File->ID3v2Tag())
{
if (!mp3File->ID3v2Tag()->frameListMap()["APIC"].isEmpty())
track.hasCover = true;
}
}
// FLAC
else if (TagLib::FLAC::File* flacFile {dynamic_cast<TagLib::FLAC::File*>(f.file())})
{
if (!flacFile->pictureList().isEmpty())
track.hasCover = true;
}
if (f.tag())
{
MetaData::Clusters clusters;
const TagLib::PropertyMap& properties {f.file()->properties()};
for(const auto& property : properties)
{
const std::string tag {property.first.upper().to8Bit(true)};
const TagLib::StringList& values {property.second};
// TODO validate MBID format // TODO validate MBID format
if (debug) if (debug)
@@ -229,7 +168,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
} }
if (tag.empty() || values.isEmpty() || values.front().isEmpty()) if (tag.empty() || values.isEmpty() || values.front().isEmpty())
continue; return;
std::string value {StringUtils::stringTrim(values.front().to8Bit(true))}; std::string value {StringUtils::stringTrim(values.front().to8Bit(true))};
@@ -321,12 +260,99 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
} }
} }
std::optional<Track>
TagLibParser::parse(const std::filesystem::path& p, bool debug)
{
TagLib::FileRef f {p.string().c_str(),
true, // read audio properties
TagLib::AudioProperties::Fast};
if (f.isNull())
{
LMS_LOG(METADATA, ERROR) << "File '" << p.string() << "': parsing failed";
return std::nullopt;
}
if (!f.audioProperties())
{
LMS_LOG(METADATA, INFO) << "File '" << p.string() << "': no audio properties";
return std::nullopt;
}
Track track;
{
const TagLib::AudioProperties *properties {f.audioProperties() };
track.duration = std::chrono::milliseconds {properties->length() * 1000};
MetaData::AudioStream audioStream {static_cast<unsigned>(properties->bitrate() * 1000)};
track.audioStreams = {std::move(audioStream)};
}
TagLib::PropertyMap properties {f.file()->properties()};
// Not that good embedded pictures handling
// WMA
if (TagLib::ASF::File* asfFile {dynamic_cast<TagLib::ASF::File*>(f.file())})
{
const TagLib::ASF::Tag* tag {asfFile->tag()};
if (tag)
{
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
else if (TagLib::MPEG::File* mp3File {dynamic_cast<TagLib::MPEG::File*>(f.file())})
{
if (mp3File->ID3v2Tag())
{
if (!mp3File->ID3v2Tag()->frameListMap()["APIC"].isEmpty())
track.hasCover = true;
}
}
// FLAC
else if (TagLib::FLAC::File* flacFile {dynamic_cast<TagLib::FLAC::File*>(f.file())})
{
if (!flacFile->pictureList().isEmpty())
track.hasCover = true;
}
for(const auto& property : properties)
{
const std::string tag {property.first.upper().to8Bit(true)};
const TagLib::StringList& values {property.second};
processTag(track, tag, values, debug);
}
track.artists = getArtists(properties); track.artists = getArtists(properties);
track.albumArtists = getAlbumArtists(properties); track.albumArtists = getAlbumArtists(properties);
track.album = getAlbum(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