diff --git a/README.md b/README.md index 87df6b6..1eca803 100644 --- a/README.md +++ b/README.md @@ -360,8 +360,8 @@ Here are some Bash examples which illustrate getting and setting tag information - Common tag fields are exposed as object properties as shown in the mentioned example. - Only properties for fields that are supported by the tag are added to the "fields" object. - Adding properties of unsupported fields manually does not work; those will just be ignored. - - The content of fields that are absent in the tag is set to `undefined`. You may also set - the content of fields to `undefined` to delete them (`null` works as well). + - The property for fields that are absent in the tag have an empty array assigned. You may + also assign an empty array to fields to delete them. - The content of binary fields is exposed as `ArrayBuffer`. Use must also use an `ArrayBuffer` to set the value of binary fields such as the cover. - The content of other fields is mostly exposed as `String` or `Number`. Use must also use diff --git a/cli/mediafileinfoobject.cpp b/cli/mediafileinfoobject.cpp index 1c6ea7c..8df91a2 100644 --- a/cli/mediafileinfoobject.cpp +++ b/cli/mediafileinfoobject.cpp @@ -218,24 +218,6 @@ QString TagObject::type() const return Utility::qstr(m_tag.typeName()); } -QJSValue &TagObject::fields() -{ - if (!m_fields.isUndefined()) { - return m_fields; - } - static const auto fieldRegex = QRegularExpression(QStringLiteral("\\s(\\w)")); - m_fields = m_engine->newObject(); - for (auto field = TagParser::firstKnownField; field != TagParser::KnownField::Invalid; field = TagParser::nextKnownField(field)) { - if (!m_tag.supportsField(field)) { - continue; - } - if (const auto propertyName = propertyNameForField(field); !propertyName.isEmpty()) { - m_fields.setProperty(propertyName, m_engine->newQObject(new TagValueObject(m_tag.value(field), m_engine, this))); - } - } - return m_fields; -} - QString TagObject::propertyNameForField(TagParser::KnownField field) { static const auto reverseMapping = [] { @@ -254,6 +236,30 @@ QString TagObject::propertyNameForField(TagParser::KnownField field) return reverseMapping.value(field, QString()); } +QJSValue &TagObject::fields() +{ + if (!m_fields.isUndefined()) { + return m_fields; + } + static const auto fieldRegex = QRegularExpression(QStringLiteral("\\s(\\w)")); + m_fields = m_engine->newObject(); + for (auto field = TagParser::firstKnownField; field != TagParser::KnownField::Invalid; field = TagParser::nextKnownField(field)) { + if (!m_tag.supportsField(field)) { + continue; + } + if (const auto propertyName = propertyNameForField(field); !propertyName.isEmpty()) { + const auto values = m_tag.values(field); + const auto size = Utility::sizeToInt(values.size()); + auto array = m_engine->newArray(size); + for (auto i = quint32(); i != size; ++i) { + array.setProperty(i, m_engine->newQObject(new TagValueObject(m_tag.value(field), m_engine, this))); + } + m_fields.setProperty(propertyName, array); + } + } + return m_fields; +} + void TagObject::applyChanges() { auto context = !m_tag.target().isEmpty() || m_tag.type() == TagParser::TagType::MatroskaTag @@ -273,22 +279,35 @@ void TagObject::applyChanges() continue; } auto propertyValue = m_fields.property(propertyName); - auto fieldDisplayName = Settings::KnownFieldModel::fieldName(field); - if (const auto *const tagValueObj = qobject_cast(propertyValue.toQObject())) { - if (!tagValueObj->isInitial()) { - auto value = tagValueObj->toTagValue(encoding); - m_diag.emplace_back(TagParser::DiagLevel::Debug, - value.isNull() - ? CppUtilities::argsToString(" - delete '", fieldDisplayName, '\'') - : CppUtilities::argsToString(" - change '", fieldDisplayName, "' from '", - tagValueObj->initialContent().toString().toStdString(), "' to '", tagValueObj->content().toString().toStdString(), '\''), - std::string()); - m_tag.setValue(field, std::move(value)); - } - } else { - m_engine->throwError(QJSValue::TypeError, QStringLiteral("invalid value assigned to field ") + propertyName); + if (!propertyValue.isArray()) { + m_engine->throwError(QJSValue::TypeError, QStringLiteral("non-array assigned to field ") + propertyName); + goto end; } + const auto size = propertyValue.property(QStringLiteral("length")).toUInt(); + auto values = std::vector(); + values.reserve(size); + for (auto i = quint32(); i != size; ++i) { + const auto *const tagValueObj = qobject_cast(propertyValue.property(i).toQObject()); + if (!tagValueObj) { + m_engine->throwError(QJSValue::TypeError, QStringLiteral("invalid value present in value-array of field ") + propertyName); + goto end; + } + if (tagValueObj->isInitial()) { + continue; + } + const auto &value = values.emplace_back(tagValueObj->toTagValue(encoding)); + m_diag.emplace_back(TagParser::DiagLevel::Debug, + value.isNull() ? CppUtilities::argsToString(" - delete ", propertyName.toStdString(), '[', i, ']') + : (tagValueObj->initialContent().isNull() ? CppUtilities::argsToString( + " - set ", propertyName.toStdString(), '[', i, "] to '", tagValueObj->content().toString().toStdString(), '\'') + : CppUtilities::argsToString(" - change ", propertyName.toStdString(), '[', + i, "] from '", tagValueObj->initialContent().toString().toStdString(), + "' to '", tagValueObj->content().toString().toStdString(), '\'')), + std::string()); + } + m_tag.setValues(field, values); } +end:; } MediaFileInfoObject::MediaFileInfoObject(TagParser::MediaFileInfo &mediaFileInfo, TagParser::Diagnostics &diag, QJSEngine *engine, QObject *parent) diff --git a/misc/utility.h b/misc/utility.h index a3c0e4e..8e729c2 100644 --- a/misc/utility.h +++ b/misc/utility.h @@ -34,9 +34,9 @@ void parseFileName(const QString &fileName, QString &title, int &trackNumber); QString printModel(QAbstractItemModel *model); void printModelIndex(const QModelIndex &index, QString &res, int level); -constexpr int sizeToInt(std::size_t size) +template constexpr IntType sizeToInt(std::size_t size) { - return size > std::numeric_limits::max() ? std::numeric_limits::max() : static_cast(size); + return size > std::numeric_limits::max() ? std::numeric_limits::max() : static_cast(size); } constexpr int containerSizeToInt(typename QStringList::size_type size) diff --git a/testfiles/set-tags.js b/testfiles/set-tags.js index 9389b8f..ac8560b 100644 --- a/testfiles/set-tags.js +++ b/testfiles/set-tags.js @@ -14,13 +14,20 @@ export function main(file) { return false; } +const mainTextFields = ["title", "artist", "album"]; +const personalFields = ["comment", "rating"]; + +function isString(value) { + return typeof(value) === "string" || value instanceof String; +} + function changeTagFields(tag) { - // log supported fields + // log tag type and supported fields const fields = tag.fields; utility.diag("debug", tag.type, "tag"); utility.diag("debug", Object.keys(fields).join(", "), "supported fields"); - // log tag type and fields for debugging purposes + // log fields for debugging purposes for (const [key, value] of Object.entries(fields)) { const content = value.content; if (content !== undefined && content != null && !(content instanceof ArrayBuffer)) { @@ -28,9 +35,22 @@ function changeTagFields(tag) { } } - // change some fields - fields.title.content = "foo"; - fields.artist.content = "bar"; + // apply fixes to main text fields + for (const key of mainTextFields) { + for (const value of fields[key]) { + if (isString(value.content)) { + value.content = value.content.trim(); + value.content = utility.fixUmlauts(value.content); + value.content = utility.formatName(value.content); + } + } + } + + // ensure personal fields are cleared + for (const key of personalFields) { + fields[key] = []; + } + + // set some other fields fields.track.content = "4/17"; - fields.comment.clear(); }