diff --git a/generictagfield.h b/generictagfield.h index af42081..35a4b71 100644 --- a/generictagfield.h +++ b/generictagfield.h @@ -38,6 +38,7 @@ public: TagField(const IdentifierType &id, const TagValue &value); ~TagField(); + IdentifierType &id(); const IdentifierType &id() const; std::string idToString() const; void setId(const IdentifierType &id); @@ -110,6 +111,14 @@ template TagField::~TagField() { } +/*! + * \brief Returns the id of the current TagField. + */ +template inline typename TagField::IdentifierType &TagField::id() +{ + return m_id; +} + /*! * \brief Returns the id of the current TagField. */ diff --git a/matroska/matroskacontainer.cpp b/matroska/matroskacontainer.cpp index e9846fa..69da918 100644 --- a/matroska/matroskacontainer.cpp +++ b/matroska/matroskacontainer.cpp @@ -663,7 +663,11 @@ void MatroskaContainer::internalParseTags(Diagnostics &diag, AbortableProgressFe CPP_UTILITIES_UNUSED(progress) static const string context("parsing tags of Matroska container"); - for (EbmlElement *element : m_tagsElements) { + auto flags = MatroskaTagFlags::None; + if (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::NormalizeKnownTagFieldIds) { + flags += MatroskaTagFlags::NormalizeKnownFieldIds; + } + for (EbmlElement *const element : m_tagsElements) { try { element->parse(diag); for (EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) { @@ -672,7 +676,7 @@ void MatroskaContainer::internalParseTags(Diagnostics &diag, AbortableProgressFe case MatroskaIds::Tag: m_tags.emplace_back(make_unique()); try { - m_tags.back()->parse(*subElement, diag); + m_tags.back()->parse2(*subElement, flags, diag); } catch (const NoDataFoundException &) { m_tags.pop_back(); } catch (const Failure &) { diff --git a/matroska/matroskatag.cpp b/matroska/matroskatag.cpp index 4af1532..272fa6e 100644 --- a/matroska/matroskatag.cpp +++ b/matroska/matroskatag.cpp @@ -108,6 +108,18 @@ KnownField MatroskaTag::internallyGetKnownField(const IdentifierType &id) const * error occurs. */ void MatroskaTag::parse(EbmlElement &tagElement, Diagnostics &diag) +{ + parse2(tagElement, MatroskaTagFlags::None, diag); +} + +/*! + * \brief Parses tag information from the specified \a tagElement. + * + * \throws Throws std::ios_base::failure when an IO error occurs. + * \throws Throws TagParser::Failure or a derived exception when a parsing + * error occurs. + */ +void MatroskaTag::parse2(EbmlElement &tagElement, MatroskaTagFlags flags, Diagnostics &diag) { static const string context("parsing Matroska tag"); m_size = tagElement.totalSize(); @@ -117,15 +129,24 @@ void MatroskaTag::parse(EbmlElement &tagElement, Diagnostics &diag) diag.emplace_back(DiagLevel::Critical, "Matroska tag is too big.", context); throw NotImplementedException(); } + const auto normalize = flags & MatroskaTagFlags::NormalizeKnownFieldIds; for (EbmlElement *child = tagElement.firstChild(); child; child = child->nextSibling()) { child->parse(diag); switch (child->id()) { case MatroskaIds::SimpleTag: try { - MatroskaTagField field; + auto field = MatroskaTagField(); field.reparse(*child, diag, true); - fields().emplace(field.id(), move(field)); + if (normalize) { + auto normalizedId = field.id(); + MatroskaTagField::normalizeId(normalizedId); + if (internallyGetKnownField(normalizedId) != KnownField::Invalid) { + field.id() = std::move(normalizedId); + } + } + fields().emplace(field.id(), std::move(field)); } catch (const Failure &) { + // message will be added to diag anyways } break; case MatroskaIds::Targets: diff --git a/matroska/matroskatag.h b/matroska/matroskatag.h index 65db999..832438d 100644 --- a/matroska/matroskatag.h +++ b/matroska/matroskatag.h @@ -11,6 +11,20 @@ namespace TagParser { class EbmlElement; class MatroskaTag; +/*! + * \brief The MatroskaTagFlags enum specifies flags which controls parsing and making of Matroska tags. + */ +enum class MatroskaTagFlags : std::uint64_t { + None = 0x0, /**< Regular parsing/making. */ + NormalizeKnownFieldIds = 0x1, /**< Normalize known field IDs when parsing. */ +}; + +} // namespace TagParser + +CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(TagParser, TagParser::MatroskaTagFlags) + +namespace TagParser { + class TAG_PARSER_EXPORT MatroskaTagMaker { friend class MatroskaTag; @@ -70,6 +84,7 @@ public: TagTargetLevel targetLevel() const override; void parse(EbmlElement &tagElement, Diagnostics &diag); + void parse2(EbmlElement &tagElement, MatroskaTagFlags flags, Diagnostics &diag); MatroskaTagMaker prepareMaking(Diagnostics &diag); void make(std::ostream &stream, Diagnostics &diag); diff --git a/matroska/matroskatagfield.cpp b/matroska/matroskatagfield.cpp index 331f5f3..01b48f7 100644 --- a/matroska/matroskatagfield.cpp +++ b/matroska/matroskatagfield.cpp @@ -176,6 +176,19 @@ void MatroskaTagField::make(ostream &stream, Diagnostics &diag) prepareMaking(diag).make(stream); } +/*! + * \brief Ensures the specified \a id is upper-case as recommended by the Matroska spec. + * \sa https://matroska.org/technical/tagging.html#tag-formatting + */ +void MatroskaTagField::normalizeId(std::string &id) +{ + for (auto &c : id) { + if (c >= 'a' && c <= 'z') { + c -= 'a' - 'A'; + } + } +} + /*! * \class TagParser::MatroskaTagFieldMaker * \brief The MatroskaTagFieldMaker class helps making tag fields. diff --git a/matroska/matroskatagfield.h b/matroska/matroskatagfield.h index 6f2a72d..fe60111 100644 --- a/matroska/matroskatagfield.h +++ b/matroska/matroskatagfield.h @@ -79,6 +79,7 @@ public: static typename std::string fieldIdFromString(std::string_view idString); static std::string fieldIdToString(const std::string &id); + static void normalizeId(std::string &id); }; /*! diff --git a/mediafileinfo.cpp b/mediafileinfo.cpp index b996a32..a33d674 100644 --- a/mediafileinfo.cpp +++ b/mediafileinfo.cpp @@ -90,8 +90,8 @@ MediaFileInfo::MediaFileInfo(std::string &&path) , m_preferredPadding(0) , m_tagPosition(ElementPosition::BeforeData) , m_indexPosition(ElementPosition::BeforeData) - , m_fileHandlingFlags( - MediaFileHandlingFlags::ForceRewrite | MediaFileHandlingFlags::ForceTagPosition | MediaFileHandlingFlags::ForceIndexPosition) + , m_fileHandlingFlags(MediaFileHandlingFlags::ForceRewrite | MediaFileHandlingFlags::ForceTagPosition | MediaFileHandlingFlags::ForceIndexPosition + | MediaFileHandlingFlags::NormalizeKnownTagFieldIds) { } diff --git a/mediafileinfo.h b/mediafileinfo.h index 8a3a456..e8f91b4 100644 --- a/mediafileinfo.h +++ b/mediafileinfo.h @@ -62,6 +62,7 @@ enum class MediaFileHandlingFlags : std::uint64_t { ForceRewrite = (1 << 1), /**< enforces a re-write of the file when applying changes */ ForceTagPosition = (1 << 2), /**< enforces the tag position when applying changes, see remarks of MediaFileInfo::setTagPosition() */ ForceIndexPosition = (1 << 3), /**< enforces the index position when applying changes, see remarks of MediaFileInfo::setIndexPosition() */ + NormalizeKnownTagFieldIds = (1 << 4), /**< normalizes known tag field IDs when parsing to match the tag specification's recommendations */ }; } // namespace TagParser @@ -162,6 +163,8 @@ public: void setSaveFilePath(std::string &&saveFilePath); const std::string &writingApplication() const; void setWritingApplication(std::string_view writingApplication); + MediaFileHandlingFlags fileHandlingFlags(); + void setFileHandlingFlags(MediaFileHandlingFlags flags); bool isForcingFullParse() const; void setForceFullParse(bool forceFullParse); bool isForcingRewrite() const; @@ -472,6 +475,22 @@ inline AbstractContainer *MediaFileInfo::container() const return m_container.get(); } +/*! + * \brief Returns the currently configured file handling flags. + */ +inline MediaFileHandlingFlags MediaFileInfo::fileHandlingFlags() +{ + return m_fileHandlingFlags; +} + +/*! + * \brief Replaces all currently configured file handling flags with the specified \a flags. + */ +inline void MediaFileInfo::setFileHandlingFlags(MediaFileHandlingFlags flags) +{ + m_fileHandlingFlags = flags; +} + /*! * \brief Returns an indication whether forcing a full parse is enabled. *