From 7a5a10102e447c5fff0731accafec9c5d05ee369 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Wed, 27 Aug 2025 16:42:19 +0200 Subject: [PATCH] Set Matroska tags which are not in the property map using complex properties Also all attached files can be accessed and modified using complex properties. --- taglib/matroska/matroskafile.cpp | 65 +++++++++++++++--------- taglib/matroska/matroskatag.cpp | 85 +++++++++++++++++++++----------- taglib/toolkit/tstring.cpp | 29 +++++++++++ taglib/toolkit/tstring.h | 23 +++++++++ 4 files changed, 149 insertions(+), 53 deletions(-) diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index 84ccd507..3ee1065f 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -138,25 +138,23 @@ namespace { if(attachedFile.mediaType().startsWith("image/")) { return "PICTURE"; } - if(!attachedFile.mediaType().isEmpty()) { - return attachedFile.mediaType(); - } if(!attachedFile.fileName().isEmpty()) { return attachedFile.fileName(); } - return String::fromLongLong(attachedFile.uid()); + if(!attachedFile.mediaType().isEmpty()) { + return attachedFile.mediaType(); + } + return String::fromULongLong(attachedFile.uid()); } - unsigned long long stringToULongLong(const String &str, bool *ok) + bool keyMatchesAttachedFile(const String &key, const Matroska::AttachedFile &attachedFile) { - const wchar_t *beginPtr = str.toCWString(); - wchar_t *endPtr; - errno = 0; - const unsigned long long value = ::wcstoull(beginPtr, &endPtr, 10); - if(ok) { - *ok = errno == 0 && endPtr > beginPtr && *endPtr == L'\0'; - } - return value; + return !key.isEmpty() && ( + (key == "PICTURE" && attachedFile.mediaType().startsWith("image/")) || + key == attachedFile.fileName() || + key == attachedFile.mediaType() || + key == String::fromULongLong(attachedFile.uid()) + ); } } @@ -182,7 +180,7 @@ List Matroska::File::complexProperties(const String &key) const if(d->attachments) { const auto &attachedFiles = d->attachments->attachedFileList(); for(const auto &attachedFile : attachedFiles) { - if(keyForAttachedFile(attachedFile) == key) { + if(keyMatchesAttachedFile(key, attachedFile)) { VariantMap property; property.insert("data", attachedFile.data()); property.insert("mimeType", attachedFile.mediaType()); @@ -202,8 +200,19 @@ bool Matroska::File::setComplexProperties(const String &key, const Listclear(); + List &files = attachments(true)->attachedFiles(); + for(auto it = files.begin(); it != files.end();) { + if(keyMatchesAttachedFile(key, *it)) { + it = files.erase(it); + } + else { + ++it; + } + } + for(const auto &property : value) { + if(property.isEmpty()) + continue; auto mimeType = property.value("mimeType").value(); auto data = property.value("data").value(); auto fileName = property.value("fileName").value(); @@ -220,16 +229,26 @@ bool Matroska::File::setComplexProperties(const String &key, const List()); - attachedFile.setFileName(fileName); - attachedFile.setUID(uid); - d->attachments->addAttachedFile(attachedFile); + if(fileName.isEmpty() && !mimeType.isEmpty()) { + int slashPos = mimeType.rfind('/'); + String ext = mimeType.substr(slashPos + 1); + if(ext == "jpeg") { + ext = "jpg"; + } + fileName = "attachment." + ext; + } + if(!mimeType.isEmpty() && !fileName.isEmpty()) { + AttachedFile attachedFile; + attachedFile.setData(data); + attachedFile.setMediaType(mimeType); + attachedFile.setDescription(property.value("description").value()); + attachedFile.setFileName(fileName); + attachedFile.setUID(uid); + d->attachments->addAttachedFile(attachedFile); + } } return true; } diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index bbc94bc2..082c5775 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -333,7 +333,7 @@ namespace ); if(it != simpleTagsTranslation.end()) return { std::get<1>(*it), std::get<2>(*it), std::get<3>(*it) }; - if (!key.isEmpty() && !key.startsWith("_")) + if (!key.isEmpty()) return { key, Matroska::SimpleTag::TargetTypeValue::Track, false }; return { String(), Matroska::SimpleTag::TargetTypeValue::None, false }; } @@ -352,8 +352,7 @@ namespace return it != simpleTagsTranslation.end() ? String(std::get<0>(*it), String::UTF8) : (targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track || - targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) && - !name.startsWith("_") + targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) ? name : String(); } @@ -399,6 +398,21 @@ String Matroska::Tag::TagPrivate::getTag(const String &key) const return it != tags.end() ? it->toString() : String(); } +PropertyMap Matroska::Tag::properties() const +{ + PropertyMap properties; + for(const auto &simpleTag : std::as_const(d->tags)) { + if(simpleTag.type() == SimpleTag::StringType) { + String key = translateTag(simpleTag.name(), simpleTag.targetTypeValue()); + if(!key.isEmpty()) + properties[key].append(simpleTag.toString()); + else + properties.addUnsupportedData(simpleTag.name()); + } + } + return properties; +} + PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) { // Remove all simple tags which would be returned in properties() @@ -442,7 +456,8 @@ StringList Matroska::Tag::complexPropertyKeys() const { StringList keys; for(const SimpleTag &t : std::as_const(d->tags)) { - if(t.type() == SimpleTag::BinaryType) { + if(t.type() != SimpleTag::StringType || + translateTag(t.name(), t.targetTypeValue()).isEmpty()) { keys.append(t.name()); } } @@ -454,9 +469,16 @@ List Matroska::Tag::complexProperties(const String& key) const List props; if(key.upper() != "PICTURE") { // Pictures are handled at the file level for(const SimpleTag &t : std::as_const(d->tags)) { - if(t.type() == SimpleTag::BinaryType) { + if(t.name() == key && + (t.type() != SimpleTag::StringType || + translateTag(t.name(), t.targetTypeValue()).isEmpty())) { VariantMap property; - property.insert("data", t.toByteVector()); + if(t.type() != SimpleTag::StringType) { + property.insert("data", t.toByteVector()); + } + else { + property.insert("value", t.toString()); + } property.insert("name", t.name()); property.insert("targetTypeValue", t.targetTypeValue()); property.insert("language", t.language()); @@ -476,36 +498,39 @@ bool Matroska::Tag::setComplexProperties(const String& key, const ListremoveSimpleTags( [&key](const SimpleTag &t) { - return t.name() == key && t.type() == SimpleTag::BinaryType; + return t.name() == key && + (t.type() != SimpleTag::StringType || + translateTag(t.name(), t.targetTypeValue()).isEmpty()); } ); bool result = false; for(const auto &property : value) { - if(property.value("name").value() == key && property.contains("data")) { - d->tags.append(SimpleTag( - key, - property.value("data").value(), - static_cast( - property.value("targetTypeValue", 0).value()), - property.value("language").value(), - property.value("defaultLanguage", true).value())); + if(property.value("name").value() == key && + (property.contains("data") || property.contains("value") )) { + SimpleTag::TargetTypeValue targetTypeValue; + Variant targetTypeValueVar = property.value("targetTypeValue", 0); + switch(targetTypeValueVar.type()) { + case Variant::UInt: + targetTypeValue = static_cast(targetTypeValueVar.value()); + break; + case Variant::LongLong: + targetTypeValue = static_cast(targetTypeValueVar.value()); + break; + case Variant::ULongLong: + targetTypeValue = static_cast(targetTypeValueVar.value()); + break; + default: + targetTypeValue = static_cast(targetTypeValueVar.value()); + } + auto language = property.value("language").value(); + bool defaultLanguage = property.value("defaultLanguage", true).value(); + d->tags.append(property.contains("data") + ? SimpleTag(key, property.value("data").value(), + targetTypeValue, language, defaultLanguage) + : SimpleTag(key, property.value("value").value(), + targetTypeValue, language, defaultLanguage)); result = true; } } return result; } - -PropertyMap Matroska::Tag::properties() const -{ - PropertyMap properties; - for(const auto &simpleTag : std::as_const(d->tags)) { - if(simpleTag.type() == SimpleTag::StringType) { - String key = translateTag(simpleTag.name(), simpleTag.targetTypeValue()); - if(!key.isEmpty()) - properties[key].append(simpleTag.toString()); - else - properties.addUnsupportedData(simpleTag.name()); - } - } - return properties; -} diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 046de208..fab5452b 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -495,6 +495,30 @@ int String::toInt(bool *ok) const return static_cast(value); } +long long String::toLongLong(bool *ok, int base) const +{ + const wchar_t *beginPtr = d->data.c_str(); + wchar_t *endPtr; + errno = 0; + const long long value = ::wcstoll(beginPtr, &endPtr, base); + if(ok) { + *ok = errno == 0 && endPtr > beginPtr && *endPtr == L'\0'; + } + return value; +} + +unsigned long long String::toULongLong(bool *ok, int base) const +{ + const wchar_t *beginPtr = d->data.c_str(); + wchar_t *endPtr; + errno = 0; + const unsigned long long value = ::wcstoull(beginPtr, &endPtr, base); + if(ok) { + *ok = errno == 0 && endPtr > beginPtr && *endPtr == L'\0'; + } + return value; +} + String String::stripWhiteSpace() const { static const wchar_t *WhiteSpaceChars = L"\t\n\f\r "; @@ -527,6 +551,11 @@ String String::fromLongLong(long long n) // static return std::to_string(n); } +String String::fromULongLong(unsigned long long n) // static +{ + return std::to_string(n); +} + wchar_t &String::operator[](int i) { detach(); diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index f7e40815..1636a159 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -359,6 +359,24 @@ namespace TagLib { */ int toInt(bool *ok = nullptr) const; + /*! + * Convert the string to an integer. + * + * If the conversion was successful, it sets the value of \a *ok to + * \c true and returns the integer. Otherwise it sets \a *ok to \c false + * and the result is undefined. + */ + long long toLongLong(bool *ok = nullptr, int base = 10) const; + + /*! + * Convert the string to an integer. + * + * If the conversion was successful, it sets the value of \a *ok to + * \c true and returns the integer. Otherwise it sets \a *ok to \c false + * and the result is undefined. + */ + unsigned long long toULongLong(bool *ok = nullptr, int base = 10) const; + /*! * Returns a string with the leading and trailing whitespace stripped. */ @@ -384,6 +402,11 @@ namespace TagLib { */ static String fromLongLong(long long n); + /*! + * Converts the base-10 integer \a n to a string. + */ + static String fromULongLong(unsigned long long n); + /*! * Returns a reference to the character at position \a i. */