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.
This commit is contained in:
Urs Fleisch
2025-08-27 16:42:19 +02:00
parent d47d28f0f8
commit 7a5a10102e
4 changed files with 149 additions and 53 deletions

View File

@ -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<VariantMap> 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 List<VariantM
return true;
}
attachments(true)->clear();
List<AttachedFile> &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<String>();
auto data = property.value("data").value<ByteVector>();
auto fileName = property.value("fileName").value<String>();
@ -220,16 +229,26 @@ bool Matroska::File::setComplexProperties(const String &key, const List<VariantM
else if(fileName.isEmpty() && key.find(".") != -1) {
fileName = key;
}
else if(!uid && ((uidKey = stringToULongLong(key, &ok))) && ok) {
else if(!uid && ((uidKey = key.toULongLong(&ok))) && ok) {
uid = uidKey;
}
AttachedFile attachedFile;
attachedFile.setData(data);
attachedFile.setMediaType(mimeType);
attachedFile.setDescription(property.value("description").value<String>());
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<String>());
attachedFile.setFileName(fileName);
attachedFile.setUID(uid);
d->attachments->addAttachedFile(attachedFile);
}
}
return true;
}

View File

@ -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<VariantMap> Matroska::Tag::complexProperties(const String& key) const
List<VariantMap> 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 List<VariantMa
}
d->removeSimpleTags(
[&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<String>() == key && property.contains("data")) {
d->tags.append(SimpleTag(
key,
property.value("data").value<ByteVector>(),
static_cast<SimpleTag::TargetTypeValue>(
property.value("targetTypeValue", 0).value<int>()),
property.value("language").value<String>(),
property.value("defaultLanguage", true).value<bool>()));
if(property.value("name").value<String>() == 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<SimpleTag::TargetTypeValue>(targetTypeValueVar.value<unsigned int>());
break;
case Variant::LongLong:
targetTypeValue = static_cast<SimpleTag::TargetTypeValue>(targetTypeValueVar.value<long long>());
break;
case Variant::ULongLong:
targetTypeValue = static_cast<SimpleTag::TargetTypeValue>(targetTypeValueVar.value<unsigned long long>());
break;
default:
targetTypeValue = static_cast<SimpleTag::TargetTypeValue>(targetTypeValueVar.value<int>());
}
auto language = property.value("language").value<String>();
bool defaultLanguage = property.value("defaultLanguage", true).value<bool>();
d->tags.append(property.contains("data")
? SimpleTag(key, property.value("data").value<ByteVector>(),
targetTypeValue, language, defaultLanguage)
: SimpleTag(key, property.value("value").value<String>(),
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;
}

View File

@ -495,6 +495,30 @@ int String::toInt(bool *ok) const
return static_cast<int>(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();

View File

@ -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.
*/