Preserve track UID in simple tags

Some encoders write track specific DURATION tags, which should not be
removed.
This commit is contained in:
Urs Fleisch
2025-09-02 20:45:54 +02:00
parent 1d3a375765
commit b9122afaca
8 changed files with 78 additions and 22 deletions

View File

@ -44,6 +44,11 @@ int main(int argc, char *argv[])
PRINT_PRETTY("Target Type Value",
targetTypeValue == 0 ? "None" : TagLib::Utils::formatString("%i", targetTypeValue).toCString(false)
);
if(auto trackUid = t.trackUid()) {
PRINT_PRETTY("Track UID",
TagLib::Utils::formatString("%llu",trackUid).toCString(false)
);
}
const TagLib::String &language = t.language();
PRINT_PRETTY("Language", !language.isEmpty() ? language.toCString(false) : "Not set");

View File

@ -82,6 +82,7 @@ std::unique_ptr<EBML::Element> EBML::Element::factory(File &file)
RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileMediaType);
RETURN_ELEMENT_FOR_CASE(Id::MkCodecID);
RETURN_ELEMENT_FOR_CASE(Id::MkTagTargetTypeValue);
RETURN_ELEMENT_FOR_CASE(Id::MkTagTrackUID);
RETURN_ELEMENT_FOR_CASE(Id::MkTagsLanguageDefault);
RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileUID);
RETURN_ELEMENT_FOR_CASE(Id::MkSeekPosition);

View File

@ -40,6 +40,7 @@ namespace TagLib::EBML {
MkTag = 0x7373,
MkTagTargets = 0x63C0,
MkTagTargetTypeValue = 0x68CA,
MkTagTrackUID = 0x63C5,
MkSimpleTag = 0x67C8,
MkTagName = 0x45A3,
MkTagLanguage = 0x447A,
@ -168,6 +169,7 @@ namespace TagLib::EBML {
template <> struct GetElementTypeById<Element::Id::MkAttachedFileMediaType> { using type = Latin1StringElement; };
template <> struct GetElementTypeById<Element::Id::MkCodecID> { using type = Latin1StringElement; };
template <> struct GetElementTypeById<Element::Id::MkTagTargetTypeValue> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkTagTrackUID> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkAttachedFileUID> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkSeekPosition> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkTimestampScale> { using type = UIntElement; };

View File

@ -54,6 +54,7 @@ std::unique_ptr<Matroska::Tag> EBML::MkTags::parse()
// Parse the <Targets> element
Matroska::SimpleTag::TargetTypeValue targetTypeValue = Matroska::SimpleTag::TargetTypeValue::None;
unsigned long long trackUid = 0;
if(targets) {
for(const auto &targetsChild : *targets) {
Id id = targetsChild->getId();
@ -63,6 +64,9 @@ std::unique_ptr<Matroska::Tag> EBML::MkTags::parse()
element_cast<Id::MkTagTargetTypeValue>(targetsChild)->getValue()
);
}
else if(id == Id::MkTagTrackUID) {
trackUid = element_cast<Id::MkTagTrackUID>(targetsChild)->getValue();
}
}
}
@ -92,9 +96,11 @@ std::unique_ptr<Matroska::Tag> EBML::MkTags::parse()
mTag->addSimpleTag(tagValueString
? Matroska::SimpleTag(tagName, *tagValueString,
targetTypeValue, language, defaultLanguageFlag)
targetTypeValue, language, defaultLanguageFlag,
trackUid)
: Matroska::SimpleTag(tagName, *tagValueBinary,
targetTypeValue, language, defaultLanguageFlag));
targetTypeValue, language, defaultLanguageFlag,
trackUid));
}
}
return mTag;

View File

@ -30,16 +30,19 @@ class Matroska::SimpleTag::SimpleTagPrivate
{
public:
explicit SimpleTagPrivate(const String &name, const String& value,
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) :
value(value), name(name), language(language),
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage,
unsigned long long trackUid) :
value(value), name(name), language(language), trackUid(trackUid),
targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {}
explicit SimpleTagPrivate(const String &name, const ByteVector& value,
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) :
value(value), name(name), language(language),
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage,
unsigned long long trackUid) :
value(value), name(name), language(language), trackUid(trackUid),
targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {}
const std::variant<String, ByteVector> value;
const String name;
const String language;
const unsigned long long trackUid;
const TargetTypeValue targetTypeValue;
const bool defaultLanguageFlag;
};
@ -49,16 +52,20 @@ public:
////////////////////////////////////////////////////////////////////////////////
Matroska::SimpleTag::SimpleTag(const String &name, const String &value,
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) :
TargetTypeValue targetTypeValue,
const String &language, bool defaultLanguage,
unsigned long long trackUid) :
d(std::make_unique<SimpleTagPrivate>(name, value, targetTypeValue,
language, defaultLanguage))
language, defaultLanguage, trackUid))
{
}
Matroska::SimpleTag::SimpleTag(const String &name, const ByteVector &value,
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) :
TargetTypeValue targetTypeValue,
const String &language, bool defaultLanguage,
unsigned long long trackUid) :
d(std::make_unique<SimpleTagPrivate>(name, value, targetTypeValue,
language, defaultLanguage))
language, defaultLanguage, trackUid))
{
}
@ -106,6 +113,11 @@ bool Matroska::SimpleTag::defaultLanguageFlag() const
return d->defaultLanguageFlag;
}
unsigned long long Matroska::SimpleTag::trackUid() const
{
return d->trackUid;
}
Matroska::SimpleTag::ValueType Matroska::SimpleTag::type() const
{
return std::holds_alternative<ByteVector>(d->value) ? BinaryType : StringType;

View File

@ -55,14 +55,16 @@ namespace TagLib {
*/
SimpleTag(const String &name, const String &value,
TargetTypeValue targetTypeValue = None,
const String &language = String(), bool defaultLanguage = true);
const String &language = String(), bool defaultLanguage = true,
unsigned long long trackUid = 0);
/*!
* Construct a binary simple tag.
*/
SimpleTag(const String &name, const ByteVector &value,
TargetTypeValue targetTypeValue = None,
const String &language = String(), bool defaultLanguage = true);
const String &language = String(), bool defaultLanguage = true,
unsigned long long trackUid = 0);
/*!
* Construct a simple tag as a copy of \a other.
@ -114,6 +116,12 @@ namespace TagLib {
*/
bool defaultLanguageFlag() const;
/*!
* Returns the UID that identifies the track that the tags belong to,
* zero if not defined, the tag applies to all tracks
*/
unsigned long long trackUid() const;
/*!
* Returns the type of the value.
*/

View File

@ -92,11 +92,13 @@ void Matroska::Tag::addSimpleTag(const SimpleTag &tag)
}
void Matroska::Tag::removeSimpleTag(const String &name,
SimpleTag::TargetTypeValue targetTypeValue)
SimpleTag::TargetTypeValue targetTypeValue,
unsigned long long trackUid)
{
auto it = std::find_if(d->tags.begin(), d->tags.end(),
[&name, targetTypeValue](const SimpleTag &t) {
return t.name() == name && t.targetTypeValue() == targetTypeValue;
[&name, targetTypeValue, trackUid](const SimpleTag &t) {
return t.name() == name && t.targetTypeValue() == targetTypeValue &&
t.trackUid() == trackUid;
}
);
if(it != d->tags.end()) {
@ -221,11 +223,13 @@ ByteVector Matroska::Tag::renderInternal()
// Build target-based list
for(const auto &tag : std::as_const(d->tags)) {
auto targetTypeValue = tag.targetTypeValue();
auto trackUid = tag.trackUid();
auto it = std::find_if(targetList.begin(),
targetList.end(),
[&](const auto &list) {
const auto &simpleTag = list.front();
return simpleTag.targetTypeValue() == targetTypeValue;
return simpleTag.targetTypeValue() == targetTypeValue &&
simpleTag.trackUid() == trackUid;
}
);
if(it == targetList.end()) {
@ -239,6 +243,7 @@ ByteVector Matroska::Tag::renderInternal()
for(const auto &list : targetList) {
const auto &frontTag = list.front();
auto targetTypeValue = frontTag.targetTypeValue();
auto trackUid = frontTag.trackUid();
auto tag = EBML::make_unique_element<EBML::Element::Id::MkTag>();
// Build <Tag Targets> element
@ -248,6 +253,11 @@ ByteVector Matroska::Tag::renderInternal()
element->setValue(static_cast<unsigned int>(targetTypeValue));
targets->appendElement(std::move(element));
}
if(trackUid != 0) {
auto element = EBML::make_unique_element<EBML::Element::Id::MkTagTrackUID>();
element->setValue(trackUid);
targets->appendElement(std::move(element));
}
tag->appendElement(std::move(targets));
// Build <Simple Tag> element
@ -401,7 +411,8 @@ String Matroska::Tag::TagPrivate::getTag(const String &key) const
return t.name() == name
&& t.type() == SimpleTag::StringType
&& (t.targetTypeValue() == targetTypeValue ||
(t.targetTypeValue() == SimpleTag::TargetTypeValue::None && !strict));
(t.targetTypeValue() == SimpleTag::TargetTypeValue::None && !strict))
&& t.trackUid() == 0;
}
);
return it != tags.end() ? it->toString() : String();
@ -411,7 +422,7 @@ PropertyMap Matroska::Tag::properties() const
{
PropertyMap properties;
for(const auto &simpleTag : std::as_const(d->tags)) {
if(simpleTag.type() == SimpleTag::StringType) {
if(simpleTag.type() == SimpleTag::StringType && simpleTag.trackUid() == 0) {
String key = translateTag(simpleTag.name(), simpleTag.targetTypeValue());
if(!key.isEmpty())
properties[key].append(simpleTag.toString());
@ -428,6 +439,7 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap)
for(auto it = d->tags.begin(); it != d->tags.end();) {
String key;
if(it->type() == SimpleTag::StringType &&
it->trackUid() == 0 &&
!(key = translateTag(it->name(), it->targetTypeValue())).isEmpty()) {
it = d->tags.erase(it);
setNeedsRender(true);
@ -468,6 +480,7 @@ StringList Matroska::Tag::complexPropertyKeys() const
StringList keys;
for(const SimpleTag &t : std::as_const(d->tags)) {
if(t.type() != SimpleTag::StringType ||
t.trackUid() != 0 ||
translateTag(t.name(), t.targetTypeValue()).isEmpty()) {
keys.append(t.name());
}
@ -482,6 +495,7 @@ List<VariantMap> Matroska::Tag::complexProperties(const String& key) const
for(const SimpleTag &t : std::as_const(d->tags)) {
if(t.name() == key &&
(t.type() != SimpleTag::StringType ||
t.trackUid() != 0 ||
translateTag(t.name(), t.targetTypeValue()).isEmpty())) {
VariantMap property;
if(t.type() != SimpleTag::StringType) {
@ -491,7 +505,12 @@ List<VariantMap> Matroska::Tag::complexProperties(const String& key) const
property.insert("value", t.toString());
}
property.insert("name", t.name());
property.insert("targetTypeValue", t.targetTypeValue());
if(t.targetTypeValue() != SimpleTag::TargetTypeValue::None) {
property.insert("targetTypeValue", t.targetTypeValue());
}
if(t.trackUid()) {
property.insert("trackUid", t.trackUid());
}
property.insert("language", t.language());
property.insert("defaultLanguage", t.defaultLanguageFlag());
props.append(property);
@ -511,6 +530,7 @@ bool Matroska::Tag::setComplexProperties(const String& key, const List<VariantMa
[&key](const SimpleTag &t) {
return t.name() == key &&
(t.type() != SimpleTag::StringType ||
t.trackUid() != 0 ||
translateTag(t.name(), t.targetTypeValue()).isEmpty());
}
);
@ -535,11 +555,12 @@ bool Matroska::Tag::setComplexProperties(const String& key, const List<VariantMa
}
auto language = property.value("language").value<String>();
bool defaultLanguage = property.value("defaultLanguage", true).value<bool>();
auto trackUid = property.value("trackUid", 0ULL).value<unsigned long long>();
d->tags.append(property.contains("data")
? SimpleTag(key, property.value("data").value<ByteVector>(),
targetTypeValue, language, defaultLanguage)
targetTypeValue, language, defaultLanguage, trackUid)
: SimpleTag(key, property.value("value").value<String>(),
targetTypeValue, language, defaultLanguage));
targetTypeValue, language, defaultLanguage, trackUid));
setNeedsRender(true);
result = true;
}

View File

@ -89,7 +89,8 @@ namespace TagLib {
bool setComplexProperties(const String& key, const List<VariantMap>& value) override;
void addSimpleTag(const SimpleTag &tag);
void removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue);
void removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue,
unsigned long long trackUid = 0);
void clearSimpleTags();
const SimpleTagsList &simpleTagsList() const;