Make tags and properties more flexible

This commit is contained in:
Urs Fleisch
2025-08-13 17:22:58 +02:00
parent 5a6f1f96f8
commit f7ab162514
8 changed files with 165 additions and 150 deletions

View File

@ -69,6 +69,7 @@ namespace TagLib::EBML {
inline constexpr Element::Id MkSimpleTag = 0x67C8;
inline constexpr Element::Id MkTagName = 0x45A3;
inline constexpr Element::Id MkTagLanguage = 0x447A;
inline constexpr Element::Id MkTagBinary = 0x4485;
inline constexpr Element::Id MkTagString = 0x4487;
inline constexpr Element::Id MkTagsTagLanguage = 0x447A;
inline constexpr Element::Id MkTagsLanguageDefault = 0x4484;

View File

@ -19,6 +19,7 @@
***************************************************************************/
#include "ebmlmktags.h"
#include "ebmlbinaryelement.h"
#include "ebmluintelement.h"
#include "ebmlstringelement.h"
#include "matroskatag.h"
@ -79,7 +80,8 @@ Matroska::Tag *EBML::MkTags::parse()
tagName = &(static_cast<UTF8StringElement *>(simpleTagChild)->getValue());
else if(id == ElementIDs::MkTagString && !tagValueString)
tagValueString = &(static_cast<UTF8StringElement *>(simpleTagChild)->getValue());
// TODO implement binary
else if(id == ElementIDs::MkTagBinary && !tagValueBinary)
tagValueBinary = &(static_cast<BinaryElement *>(simpleTagChild)->getValue());
else if(id == ElementIDs::MkTagsTagLanguage && !language)
language = &(static_cast<Latin1StringElement *>(simpleTagChild)->getValue());
else if(id == ElementIDs::MkTagsLanguageDefault)
@ -96,7 +98,7 @@ Matroska::Tag *EBML::MkTags::parse()
sTagString->setValue(*tagValueString);
sTag = sTagString;
}
else if(tagValueBinary) {
else { // tagValueBinary must be non null
auto sTagBinary = new Matroska::SimpleTagBinary();
sTagBinary->setTargetTypeValue(targetTypeValue);
sTagBinary->setValue(*tagValueBinary);

View File

@ -48,12 +48,15 @@ namespace TagLib {
void removeAttachedFile(AttachedFile *file);
void clear();
const AttachedFileList &attachedFileList() const;
bool render() override;
private:
friend class EBML::MkAttachments;
friend class File;
class AttachmentsPrivate;
// private Element implementation
bool render() override;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<AttachmentsPrivate> d;
};

View File

@ -154,5 +154,6 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset)
bool Matroska::CueTrack::adjustOffset(offset_t, offset_t)
{
// TODO implement
return false;
}

View File

@ -28,6 +28,7 @@
#include "ebmlmksegment.h"
#include "tlist.h"
#include "tdebug.h"
#include "tagutils.h"
#include <memory>
#include <vector>
@ -57,10 +58,10 @@ public:
// static members
////////////////////////////////////////////////////////////////////////////////
bool Matroska::File::isSupported(IOStream *)
bool Matroska::File::isSupported(IOStream *stream)
{
// TODO implement
return false;
const ByteVector id = Utils::readHeader(stream, 4, false);
return id.startsWith("\x1A\x45\xDF\xA3");
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -46,6 +46,5 @@ namespace TagLib::Matroska {
};
}
#endif
#endif

View File

@ -25,6 +25,7 @@
#include "matroskasimpletag.h"
#include "ebmlmasterelement.h"
#include "ebmlstringelement.h"
#include "ebmlbinaryelement.h"
#include "ebmlmktags.h"
#include "ebmluintelement.h"
#include "ebmlutils.h"
@ -33,16 +34,62 @@
using namespace TagLib;
namespace TagLib::Matroska::Utils {
std::pair<String, SimpleTag::TargetTypeValue> translateKey(const String &key);
String translateTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue);
}
class Matroska::Tag::TagPrivate
{
public:
TagPrivate() = default;
~TagPrivate() = default;
bool setTag(const String &key, const String &value);
const String *getTag(const String &key) const;
template <typename T>
int removeSimpleTags(T &&p)
{
auto &list = tags;
int numRemoved = 0;
for(auto it = list.begin(); it != list.end();) {
it = std::find_if(it, list.end(), std::forward<T>(p));
if(it != list.end()) {
delete *it;
*it = nullptr;
it = list.erase(it);
numRemoved++;
}
}
return numRemoved;
}
template <typename T>
SimpleTagsList findSimpleTags(T &&p)
{
auto &list = tags;
for(auto it = list.begin(); it != list.end();) {
it = std::find_if(it, list.end(), std::forward<T>(p));
if(it != list.end()) {
list.append(*it);
++it;
}
}
return list;
}
template <typename T>
const SimpleTag *findSimpleTag(T &&p) const
{
auto &list = tags;
auto it = std::find_if(list.begin(), list.end(), std::forward<T>(p));
return it != list.end() ? *it : nullptr;
}
template <typename T>
SimpleTag *findSimpleTag(T &&p)
{
return const_cast<SimpleTag *>(
const_cast<const TagPrivate *>(this)->findSimpleTag(std::forward<T>(p))
);
}
List<SimpleTag *> tags;
ByteVector data;
};
@ -79,84 +126,74 @@ const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsList() const
return d->tags;
}
Matroska::SimpleTagsList &Matroska::Tag::simpleTagsListPrivate()
{
return d->tags;
}
const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsListPrivate() const
{
return d->tags;
}
void Matroska::Tag::setTitle(const String &s)
{
setTag("TITLE", s);
d->setTag("TITLE", s);
}
void Matroska::Tag::setArtist(const String &s)
{
setTag("ARTIST", s);
d->setTag("ARTIST", s);
}
void Matroska::Tag::setAlbum(const String &s)
{
setTag("ALBUM", s);
d->setTag("ALBUM", s);
}
void Matroska::Tag::setComment(const String &s)
{
setTag("COMMENT", s);
d->setTag("COMMENT", s);
}
void Matroska::Tag::setGenre(const String &s)
{
setTag("GENRE", s);
d->setTag("GENRE", s);
}
void Matroska::Tag::setYear(unsigned int i)
{
setTag("DATE", String::number(i));
d->setTag("DATE", String::number(i));
}
void Matroska::Tag::setTrack(unsigned int i)
{
setTag("TRACKNUMBER", String::number(i));
d->setTag("TRACKNUMBER", String::number(i));
}
String Matroska::Tag::title() const
{
const auto value = getTag("TITLE");
const auto value = d->getTag("TITLE");
return value ? *value : String();
}
String Matroska::Tag::artist() const
{
const auto value = getTag("ARTIST");
const auto value = d->getTag("ARTIST");
return value ? *value : String();
}
String Matroska::Tag::album() const
{
const auto value = getTag("ALBUM");
const auto value = d->getTag("ALBUM");
return value ? *value : String();
}
String Matroska::Tag::comment() const
{
const auto value = getTag("COMMENT");
const auto value = d->getTag("COMMENT");
return value ? *value : String();
}
String Matroska::Tag::genre() const
{
const auto value = getTag("GENRE");
const auto value = d->getTag("GENRE");
return value ? *value : String();
}
unsigned int Matroska::Tag::year() const
{
auto value = getTag("DATE");
auto value = d->getTag("DATE");
if(!value)
return 0;
auto list = value->split("-");
@ -165,7 +202,7 @@ unsigned int Matroska::Tag::year() const
unsigned int Matroska::Tag::track() const
{
auto value = getTag("TRACKNUMBER");
auto value = d->getTag("TRACKNUMBER");
if(!value)
return 0;
auto list = value->split("-");
@ -230,8 +267,10 @@ bool Matroska::Tag::render()
tagValue->setValue(tStr->value());
t->appendElement(tagValue);
}
else if((tBin = dynamic_cast<Matroska::SimpleTagBinary *>(simpleTag))) {
// Todo
else if((tBin = dynamic_cast<SimpleTagBinary *>(simpleTag))) {
auto tagValue = new EBML::BinaryElement(EBML::ElementIDs::MkTagBinary);
tagValue->setValue(tBin->value());
t->appendElement(tagValue);
}
// Language
@ -264,42 +303,72 @@ bool Matroska::Tag::render()
namespace
{
// PropertyMap key, Tag name, Target type value
// If the key is the same as the name and the target type value is Track,
// no translation is needed because this is the default mapping.
// Therefore, keys like TITLE, ARTIST, GENRE, COMMENT, etc. are omitted.
// For offical tags, see https://www.matroska.org/technical/tagging.html
constexpr std::array simpleTagsTranslation {
std::tuple("TITLE", "TITLE", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("ALBUM", "TITLE", Matroska::SimpleTag::TargetTypeValue::Album),
std::tuple("ARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("ALBUMARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Album),
std::tuple("SUBTITLE", "SUBTITLE", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("TRACKNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("TRACKTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("DISCNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Part),
std::tuple("DISCTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Part),
std::tuple("DATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album),
// Todo - original date
std::tuple("GENRE", "GENRE", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("COMMENT", "COMMENT", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("TITLESORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album),
std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album),
std::tuple("COMPOSERSORT", "COMPOSERSORT", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("COMPOSER", "COMPOSER", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("LYRICIST", "LYRICIST", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("CONDUCTOR", "CONDUCTOR", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("REMIXER", "REMIXER", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("BPM", "BPM", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("COPYRIGHT", "COPYRIGHT", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("ALBUMARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album),
std::tuple("ENCODEDBY", "ENCODED_BY", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("MOOD", "MOOD", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("MEDIA", "ORIGINAL_MEDIA_TYPE", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("LABEL", "LABEL_CODE", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("CATALOGNUMBER", "CATALOG_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("BARCODE", "BARCODE", Matroska::SimpleTag::TargetTypeValue::Track),
// Todo MusicBrainz tags
std::tuple("DJMIXER", "MIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("REMIXER", "REMIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("INITIALKEY", "INITIAL_KEY", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("RELEASEDATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("ENCODINGTIME", "DATE_ENCODED", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("TAGGINGDATE", "DATE_TAGGED", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("ENCODEDBY", "ENCODER", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("ENCODING", "ENCODER_SETTINGS", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("OWNER", "PURCHASE_OWNER", Matroska::SimpleTag::TargetTypeValue::Track),
std::tuple("MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMARTISTID", Matroska::SimpleTag::TargetTypeValue::Album),
std::tuple("MUSICBRAINZ_ALBUMID", "MUSICBRAINZ_ALBUMID", Matroska::SimpleTag::TargetTypeValue::Album),
std::tuple("MUSICBRAINZ_RELEASEGROUPID", "MUSICBRAINZ_RELEASEGROUPID", Matroska::SimpleTag::TargetTypeValue::Album),
};
std::pair<String, Matroska::SimpleTag::TargetTypeValue> translateKey(const String &key)
{
auto it = std::find_if(simpleTagsTranslation.cbegin(),
simpleTagsTranslation.cend(),
[&key](const auto &t) { return key == std::get<0>(t); }
);
if(it != simpleTagsTranslation.end())
return { std::get<1>(*it), std::get<2>(*it) };
if (!key.isEmpty() && !key.startsWith("_"))
return { key, Matroska::SimpleTag::TargetTypeValue::Track };
return { String(), Matroska::SimpleTag::TargetTypeValue::None };
}
String translateTag(const String &name, Matroska::SimpleTag::TargetTypeValue targetTypeValue)
{
auto it = std::find_if(simpleTagsTranslation.cbegin(),
simpleTagsTranslation.cend(),
[&name, targetTypeValue](const auto &t) {
return name == std::get<1>(t)
&& targetTypeValue == std::get<2>(t);
}
);
return it != simpleTagsTranslation.end()
? String(std::get<0>(*it), String::UTF8)
: targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track && !name.startsWith("_")
? name
: String();
}
}
bool Matroska::Tag::setTag(const String &key, const String &value)
bool Matroska::Tag::TagPrivate::setTag(const String &key, const String &value)
{
const auto pair = Utils::translateKey(key);
const auto pair = translateKey(key);
// Workaround Clang issue - no lambda capture of structured bindings
const String &name = pair.first;
auto targetTypeValue = pair.second;
@ -316,14 +385,14 @@ bool Matroska::Tag::setTag(const String &key, const String &value)
t->setTargetTypeValue(targetTypeValue);
t->setName(name);
t->setValue(value);
addSimpleTag(t);
tags.append(t);
}
return true;
}
const String *Matroska::Tag::getTag(const String &key) const
const String *Matroska::Tag::TagPrivate::getTag(const String &key) const
{
const auto pair = Utils::translateKey(key);
const auto pair = translateKey(key);
// Workaround Clang issue - no lambda capture of structured bindings
const String &name = pair.first;
auto targetTypeValue = pair.second;
@ -340,36 +409,23 @@ const String *Matroska::Tag::getTag(const String &key) const
return tag ? &tag->value() : nullptr;
}
std::pair<String, Matroska::SimpleTag::TargetTypeValue> Matroska::Utils::translateKey(const String &key)
{
auto it = std::find_if(simpleTagsTranslation.cbegin(),
simpleTagsTranslation.cend(),
[&key](const auto &t) { return key == std::get<0>(t); }
);
if(it != simpleTagsTranslation.end())
return { std::get<1>(*it), std::get<2>(*it) };
else
return { String(), SimpleTag::TargetTypeValue::None };
}
String Matroska::Utils::translateTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue)
{
auto it = std::find_if(simpleTagsTranslation.cbegin(),
simpleTagsTranslation.cend(),
[&name, targetTypeValue](const auto &t) {
return name == std::get<1>(t)
&& targetTypeValue == std::get<2>(t);
}
);
return it != simpleTagsTranslation.end() ? std::get<0>(*it) : String();
}
PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap)
{
PropertyMap unsupportedProperties;
for(const auto &[key, value] : propertyMap) {
if(!setTag(key, value.toString()))
unsupportedProperties[key] = value;
for(const auto &[key, values] : propertyMap) {
for(const auto &value : values) {
if(auto [name, targetTypeValue] = translateKey(key);
!name.isEmpty()) {
auto t = new SimpleTagString();
t->setTargetTypeValue(targetTypeValue);
t->setName(name);
t->setValue(value);
d->tags.append(t);
}
else {
unsupportedProperties[key] = values;
}
}
}
return unsupportedProperties;
}
@ -378,11 +434,11 @@ PropertyMap Matroska::Tag::properties() const
{
PropertyMap properties;
SimpleTagString *tStr = nullptr;
for(auto simpleTag : d->tags) {
for(auto simpleTag : std::as_const(d->tags)) {
if((tStr = dynamic_cast<SimpleTagString *>(simpleTag))) {
String key = Utils::translateTag(tStr->name(), tStr->targetTypeValue());
if(!key.isEmpty() && !properties.contains(key))
properties[key] = tStr->value();
String key = translateTag(tStr->name(), tStr->targetTypeValue());
if(!key.isEmpty())
properties[key].append(tStr->value());
}
}
return properties;

View File

@ -18,12 +18,10 @@
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef HAS_MATROSKATAG_H
#define HAS_MATROSKATAG_H
#ifndef TAGLIB_MATROSKATAG_H
#define TAGLIB_MATROSKATAG_H
#include <memory>
#include <algorithm>
#include <utility>
#include "tag.h"
#include "tstring.h"
@ -48,10 +46,6 @@ namespace TagLib {
public:
Tag();
~Tag() override;
void addSimpleTag(SimpleTag *tag);
void removeSimpleTag(SimpleTag *tag);
void clearSimpleTags();
const SimpleTagsList &simpleTagsList() const;
String title() const override;
String artist() const override;
String album() const override;
@ -67,64 +61,22 @@ namespace TagLib {
void setYear(unsigned int i) override;
void setTrack(unsigned int i) override;
bool isEmpty() const override;
bool render() override;
PropertyMap properties() const override;
PropertyMap setProperties(const PropertyMap &propertyMap) override;
template <typename T>
int removeSimpleTags(T &&p)
{
auto &list = simpleTagsListPrivate();
int numRemoved = 0;
for(auto it = list.begin(); it != list.end();) {
it = std::find_if(it, list.end(), std::forward<T>(p));
if(it != list.end()) {
delete *it;
*it = nullptr;
it = list.erase(it);
numRemoved++;
}
}
return numRemoved;
}
template <typename T>
SimpleTagsList findSimpleTags(T &&p)
{
auto &list = simpleTagsListPrivate();
for(auto it = list.begin(); it != list.end();) {
it = std::find_if(it, list.end(), std::forward<T>(p));
if(it != list.end()) {
list.append(*it);
++it;
}
}
return list;
}
template <typename T>
const SimpleTag *findSimpleTag(T &&p) const
{
auto &list = simpleTagsListPrivate();
auto it = std::find_if(list.begin(), list.end(), std::forward<T>(p));
return it != list.end() ? *it : nullptr;
}
template <typename T>
SimpleTag *findSimpleTag(T &&p)
{
return const_cast<SimpleTag *>(
const_cast<const Tag *>(this)->findSimpleTag(std::forward<T>(p))
);
}
void addSimpleTag(SimpleTag *tag);
void removeSimpleTag(SimpleTag *tag);
void clearSimpleTags();
const SimpleTagsList &simpleTagsList() const;
private:
friend class File;
friend class EBML::MkTags;
SimpleTagsList &simpleTagsListPrivate();
const SimpleTagsList &simpleTagsListPrivate() const;
bool setTag(const String &key, const String &value);
const String *getTag(const String &key) const;
class TagPrivate;
// private Element implementation
bool render() override;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<TagPrivate> d;
};