From 80837485cfe28c4b51fc178c3676d31adf0332f7 Mon Sep 17 00:00:00 2001 From: complexlogic Date: Sat, 30 Sep 2023 22:02:22 -0700 Subject: [PATCH] Added write support --- examples/CMakeLists.txt | 5 + examples/matroskawriter.cpp | 37 +++ taglib/fileref.cpp | 3 + taglib/matroska/ebml/ebmlelement.cpp | 60 +++- taglib/matroska/ebml/ebmlelement.h | 21 +- taglib/matroska/ebml/ebmlmasterelement.cpp | 12 + taglib/matroska/ebml/ebmlmasterelement.h | 8 +- taglib/matroska/ebml/ebmlmksegment.cpp | 19 +- taglib/matroska/ebml/ebmlmksegment.h | 7 +- taglib/matroska/ebml/ebmlmktags.cpp | 24 +- taglib/matroska/ebml/ebmlmktags.h | 5 +- taglib/matroska/ebml/ebmlstringelement.cpp | 14 + taglib/matroska/ebml/ebmlstringelement.h | 6 +- taglib/matroska/ebml/ebmluintelement.cpp | 21 +- taglib/matroska/ebml/ebmluintelement.h | 4 + taglib/matroska/ebml/ebmlutils.cpp | 62 +--- taglib/matroska/ebml/ebmlutils.h | 80 +++++- taglib/matroska/matroskafile.cpp | 65 ++++- taglib/matroska/matroskafile.h | 5 +- taglib/matroska/matroskasimpletag.cpp | 18 +- taglib/matroska/matroskasimpletag.h | 20 +- taglib/matroska/matroskatag.cpp | 315 ++++++++++++++++++++- taglib/matroska/matroskatag.h | 97 +++++-- 23 files changed, 756 insertions(+), 152 deletions(-) create mode 100644 examples/matroskawriter.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 90feb9e5..777797ca 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -44,6 +44,11 @@ target_link_libraries(strip-id3v1 tag) add_executable(matroskareader matroskareader.cpp) target_link_libraries(matroskareader tag) +########### next target ############### + +add_executable(matroskawriter matroskawriter.cpp) +target_link_libraries(matroskawriter tag) + install(TARGETS tagreader tagreader_c tagwriter framelist strip-id3v1 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/examples/matroskawriter.cpp b/examples/matroskawriter.cpp new file mode 100644 index 00000000..0ad1f929 --- /dev/null +++ b/examples/matroskawriter.cpp @@ -0,0 +1,37 @@ +#include +#include "matroskafile.h" +#include "matroskatag.h" +#include "matroskasimpletag.h" +#include "tstring.h" +#include "tutils.h" + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + printf("Usage: matroskawriter FILE\n"); + return 1; + } + TagLib::Matroska::File file(argv[1]); + if (!file.isValid()) { + printf("File is not valid\n"); + return 1; + } + auto tag = file.tag(true); + tag->clearSimpleTags(); + + auto simpleTag = new TagLib::Matroska::SimpleTagString(); + simpleTag->setName("Test Name 1"); + simpleTag->setTargetTypeValue(TagLib::Matroska::SimpleTag::TargetTypeValue::Track); + simpleTag->setValue("Test Value 1"); + tag->addSimpleTag(simpleTag); + + simpleTag = new TagLib::Matroska::SimpleTagString(); + simpleTag->setName("Test Name 2"); + simpleTag->setTargetTypeValue(TagLib::Matroska::SimpleTag::TargetTypeValue::Album); + simpleTag->setValue("Test Value 2"); + tag->addSimpleTag(simpleTag); + + file.save(); + + return 0; +} diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 2b5e5873..17ecc8b4 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -38,6 +38,7 @@ #include "tstringlist.h" #include "tvariant.h" #include "tdebug.h" +#include "matroskafile.h" #include "mpegfile.h" #ifdef TAGLIB_WITH_RIFF #include "aifffile.h" @@ -220,6 +221,8 @@ namespace else if(ext == "SHN") file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle); #endif + else if(ext == "MKA" || ext == "MKV" || ext == "WEBM") + file = new Matroska::File(stream, readAudioProperties); // if file is not valid, leave it to content-based detection. diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 13129812..69467b4d 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -29,6 +29,8 @@ #include "tdebug.h" #include "tutils.h" +#include + using namespace TagLib; EBML::Element* EBML::Element::factory(File &file) @@ -47,28 +49,28 @@ EBML::Element* EBML::Element::factory(File &file) // Return the subclass switch(id) { - case EBML_ID_HEAD: + case ElementIDs::EBMLHeader: return new Element(id, sizeLength, dataSize); - case EBML_ID_MK_SEGMENT: + case ElementIDs::MkSegment: return new MkSegment(sizeLength, dataSize); - case EBML_ID_MK_TAGS: + case ElementIDs::MkTags: return new MkTags(sizeLength, dataSize); - case EBML_ID_MK_TAG: - case EBML_ID_MK_TAG_TARGETS: - case EBML_ID_MK_SIMPLE_TAG: + case ElementIDs::MkTag: + case ElementIDs::MkTagTargets: + case ElementIDs::MkSimpleTag: return new MasterElement(id, sizeLength, dataSize); - case EBML_ID_MK_TAG_NAME: - case EBML_ID_MK_TAG_STRING: + case ElementIDs::MkTagName: + case ElementIDs::MkTagString: return new UTF8StringElement(id, sizeLength, dataSize); - case EBML_ID_MK_TAG_LANGUAGE: + case ElementIDs::MkTagLanguage: return new Latin1StringElement(id, sizeLength, dataSize); - case EBML_ID_MK_TARGET_TYPE_VALUE: + case ElementIDs::MkTagTargetTypeValue: return new UIntElement(id, sizeLength, dataSize); default: @@ -78,7 +80,45 @@ EBML::Element* EBML::Element::factory(File &file) return nullptr; } +EBML::Element::Id EBML::Element::readId(File &file) +{ + auto buffer = file.readBlock(1); + if (buffer.size() != 1) { + debug("Failed to read VINT size"); + return 0; + } + unsigned int nb_bytes = VINTSizeLength<4>(*buffer.begin()); + if (!nb_bytes) + return 0; + if (nb_bytes > 1) + buffer.append(file.readBlock(nb_bytes - 1)); + if (buffer.size() != nb_bytes) { + debug("Failed to read VINT data"); + return 0; + } + return buffer.toUInt(true); +} + void EBML::Element::skipData(File &file) { file.seek(dataSize, File::Position::Current); } + +offset_t EBML::Element::headSize() const +{ + return EBML::idSize(id) + sizeLength; +} + +ByteVector EBML::Element::render() +{ + ByteVector buffer = renderId(); + buffer.append(renderVINT(0, 0)); + return buffer; +} + +ByteVector EBML::Element::renderId() +{ + int numBytes = idSize(id); + id = Utils::byteSwap(id); + return ByteVector((char*) &id + (4 - numBytes), numBytes); +} diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index db662293..0338659e 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -23,7 +23,8 @@ #ifndef DO_NOT_DOCUMENT #include "tfile.h" -#include "ebmlutils.h" +#include "tutils.h" +//#include "ebmlutils.h" #include "taglib.h" namespace TagLib { @@ -31,6 +32,7 @@ namespace TagLib { class Element { public: + using Id = unsigned int; Element(Id id, int sizeLength, offset_t dataSize) : id(id), sizeLength(sizeLength), dataSize(dataSize) {} @@ -42,15 +44,32 @@ namespace TagLib { } void skipData(File &file); Id getId() const { return id; } + offset_t headSize() const; int getSizeLength() const { return sizeLength; } int64_t getDataSize() const { return dataSize; } + ByteVector renderId(); + virtual ByteVector render(); static Element* factory(File &file); + static Id readId(File &file); protected: Id id; int sizeLength; offset_t dataSize; }; + + namespace ElementIDs { + inline constexpr Element::Id EBMLHeader = 0x1A45DFA3; + inline constexpr Element::Id MkSegment = 0x18538067; + inline constexpr Element::Id MkTags = 0x1254C367; + inline constexpr Element::Id MkTag = 0x7373; + inline constexpr Element::Id MkTagTargets = 0x63C0; + inline constexpr Element::Id MkTagTargetTypeValue = 0x68CA; + inline constexpr Element::Id MkSimpleTag = 0x67C8; + inline constexpr Element::Id MkTagName = 0x45A3; + inline constexpr Element::Id MkTagLanguage = 0x447A; + inline constexpr Element::Id MkTagString = 0x4487; + } } } diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp index a3595c8b..b63da33a 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.cpp +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -45,3 +45,15 @@ bool EBML::MasterElement::read(File &file) } return file.tell() == maxOffset; } + +ByteVector EBML::MasterElement::render() +{ + ByteVector buffer = renderId(); + ByteVector data; + for (auto element : elements) + data.append(element->render()); + dataSize = data.size(); + buffer.append(renderVINT(dataSize, 0)); + buffer.append(data); + return buffer; +} diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h index 8f769db3..e08c54b2 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.h +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -22,8 +22,9 @@ #define TAGLIB_EBMLMASTERELEMENT_H #ifndef DO_NOT_DOCUMENT -#include "ebmlelement.h" #include "ebmlutils.h" +#include "ebmlelement.h" +#include "tbytevector.h" #include "tlist.h" #include "taglib.h" @@ -35,9 +36,14 @@ namespace TagLib { MasterElement(Id id, int sizeLength, offset_t dataSize) : Element(id, sizeLength, dataSize) {} + MasterElement(Id id) + : Element(id, 0, 0) + {} ~MasterElement() override; virtual bool isMaster() const override { return true; } virtual bool read(File &file) override; + ByteVector render() override; + void appendElement(Element *element) { elements.append(element); } List::Iterator begin () { return elements.begin(); } List::Iterator end () { return elements.end(); } List::ConstIterator cbegin () const { return elements.cbegin(); } diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index e22537fa..8cbbd0a6 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -24,6 +24,7 @@ #include "matroskafile.h" #include "matroskatag.h" #include "tutils.h" +#include "tbytevector.h" #include "tdebug.h" using namespace TagLib; @@ -36,13 +37,21 @@ EBML::MkSegment::~MkSegment() bool EBML::MkSegment::read(File &file) { offset_t maxOffset = file.tell() + dataSize; - tags = static_cast(findElement(file, EBML_ID_MK_TAGS, maxOffset)); - if (tags && !tags->read(file)) - return false; + tags = static_cast(findElement(file, ElementIDs::MkTags, maxOffset)); + if (tags) { + offset_t tagsHeadSize = tags->headSize(); + tagsOffset = file.tell() - tagsHeadSize; + tagsOriginalSize = tagsHeadSize + tags->getDataSize(); + if (!tags->read(file)) + return false; + } return true; } -Matroska::Tag* EBML::MkSegment::parseTag() +std::tuple EBML::MkSegment::parseTag() { - return tags ? tags->parse() : nullptr; + if (tags) + return {tags->parse(), tagsOffset, tagsOriginalSize}; + else + return {nullptr, 0, 0}; } diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index a3a17375..b3a0a256 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -20,6 +20,7 @@ #include "ebmlmasterelement.h" #include "taglib.h" +#include #ifndef TAGLIB_EBMLMKSEGMENT_H #define TAGLIB_EBMLMKSEGMENT_H @@ -36,14 +37,16 @@ namespace TagLib { { public: MkSegment(int sizeLength, offset_t dataSize) - : MasterElement(EBML_ID_MK_SEGMENT, sizeLength, dataSize) + : MasterElement(ElementIDs::MkSegment, sizeLength, dataSize) {} ~MkSegment() override; bool read(File &file) override; - Matroska::Tag* parseTag(); + std::tuple parseTag(); private: MkTags *tags = nullptr; + offset_t tagsOffset = 0; + offset_t tagsOriginalSize = 0; }; } diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index c088cae9..ae2db490 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -37,7 +37,7 @@ Matroska::Tag* EBML::MkTags::parse() // Loop through each element for (auto tagsChild : elements) { - if (tagsChild->getId() != EBML_ID_MK_TAG) + if (tagsChild->getId() != ElementIDs::MkTag) continue; auto tag = static_cast(tagsChild); List simpleTags; @@ -46,20 +46,20 @@ Matroska::Tag* EBML::MkTags::parse() // Identify the element and the elements for (auto tagChild : *tag) { Id tagChildId = tagChild->getId(); - if (!targets && tagChildId == EBML_ID_MK_TAG_TARGETS) + if (!targets && tagChildId == ElementIDs::MkTagTargets) targets = static_cast(tagChild); - else if (tagChildId == EBML_ID_MK_SIMPLE_TAG) + else if (tagChildId == ElementIDs::MkSimpleTag) simpleTags.append(static_cast(tagChild)); } // Parse the element - Matroska::Tag::TargetTypeValue targetTypeValue = Matroska::Tag::TargetTypeValue::None; + Matroska::SimpleTag::TargetTypeValue targetTypeValue = Matroska::SimpleTag::TargetTypeValue::None; if (targets) { for (auto targetsChild : *targets) { Id id = targetsChild->getId(); - if (id == EBML_ID_MK_TARGET_TYPE_VALUE - && targetTypeValue == Matroska::Tag::TargetTypeValue::None) { - targetTypeValue = static_cast( + if (id == ElementIDs::MkTagTargetTypeValue + && targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) { + targetTypeValue = static_cast( static_cast(targetsChild)->getValue() ); } @@ -74,9 +74,9 @@ Matroska::Tag* EBML::MkTags::parse() for (auto simpleTagChild : *simpleTag) { Id id = simpleTagChild->getId(); - if (id == EBML_ID_MK_TAG_NAME && !tagName) + if (id == ElementIDs::MkTagName && !tagName) tagName = &(static_cast(simpleTagChild)->getValue()); - else if (id == EBML_ID_MK_TAG_STRING && !tagValueString) + else if (id == ElementIDs::MkTagString && !tagValueString) tagValueString = &(static_cast(simpleTagChild)->getValue()); } if (!tagName || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) @@ -85,12 +85,14 @@ Matroska::Tag* EBML::MkTags::parse() // Create a Simple Tag object and add it to the Tag object Matroska::SimpleTag *sTag = nullptr; if (tagValueString) { - auto sTagString = new Matroska::SimpleTagString(targetTypeValue); + auto sTagString = new Matroska::SimpleTagString(); + sTagString->setTargetTypeValue(targetTypeValue); sTagString->setValue(*tagValueString); sTag = sTagString; } else if (tagValueBinary) { - auto sTagBinary = new Matroska::SimpleTagBinary(targetTypeValue); + auto sTagBinary = new Matroska::SimpleTagBinary(); + sTagBinary->setTargetTypeValue(targetTypeValue); sTagBinary->setValue(*tagValueBinary); sTag = sTagBinary; } diff --git a/taglib/matroska/ebml/ebmlmktags.h b/taglib/matroska/ebml/ebmlmktags.h index a54748de..d8447273 100644 --- a/taglib/matroska/ebml/ebmlmktags.h +++ b/taglib/matroska/ebml/ebmlmktags.h @@ -36,7 +36,10 @@ namespace TagLib { { public: MkTags(int sizeLength, offset_t dataSize) - : MasterElement(EBML_ID_MK_TAGS, sizeLength, dataSize) + : MasterElement(ElementIDs::MkTags, sizeLength, dataSize) + {} + MkTags() + : MasterElement(ElementIDs::MkTags, 0, 0) {} //virtual void read(File &file) override; Matroska::Tag* parse(); diff --git a/taglib/matroska/ebml/ebmlstringelement.cpp b/taglib/matroska/ebml/ebmlstringelement.cpp index e2cc51e7..475d0abf 100644 --- a/taglib/matroska/ebml/ebmlstringelement.cpp +++ b/taglib/matroska/ebml/ebmlstringelement.cpp @@ -23,6 +23,7 @@ #include "tstring.h" #include "tbytevector.h" #include "tdebug.h" +#include using namespace TagLib; @@ -45,3 +46,16 @@ bool EBML::StringElement::read(TagLib::File &file) } template bool EBML::StringElement::read(TagLib::File &file); template bool EBML::StringElement::read(TagLib::File &file); + +template +ByteVector EBML::StringElement::render() +{ + ByteVector buffer = renderId(); + std::string string = value.to8Bit(t == String::UTF8); + dataSize = string.size(); + buffer.append(renderVINT(dataSize, 0)); + buffer.append(ByteVector(string.data(), dataSize)); + return buffer; +} +template ByteVector EBML::StringElement::render(); +template ByteVector EBML::StringElement::render(); diff --git a/taglib/matroska/ebml/ebmlstringelement.h b/taglib/matroska/ebml/ebmlstringelement.h index c9c59d94..6421068f 100644 --- a/taglib/matroska/ebml/ebmlstringelement.h +++ b/taglib/matroska/ebml/ebmlstringelement.h @@ -25,6 +25,7 @@ #include #include "ebmlelement.h" #include "ebmlutils.h" +#include "tbytevector.h" #include "tstring.h" namespace TagLib { @@ -37,10 +38,13 @@ namespace TagLib { StringElement(Id id, int sizeLength, offset_t dataSize) : Element(id, sizeLength, dataSize) {} + StringElement(Id id) + : Element(id, 0, 0) + {} const String& getValue() const { return value; } void setValue(const String &value) { this->value = value; } - //template bool read(File &file) override; + ByteVector render() override; private: String value; diff --git a/taglib/matroska/ebml/ebmluintelement.cpp b/taglib/matroska/ebml/ebmluintelement.cpp index f0d2c6b8..2197a3d2 100644 --- a/taglib/matroska/ebml/ebmluintelement.cpp +++ b/taglib/matroska/ebml/ebmluintelement.cpp @@ -33,11 +33,20 @@ bool EBML::UIntElement::read(TagLib::File &file) debug("Failed to read EBML Uint element"); return false; } - const auto& [sizeLength, value] = parseVINT(buffer); - if (!sizeLength) { - debug("Failed to parse VINT"); - return false; - } - this->value = value; + value = buffer.toLongLong(true); return true; } + +ByteVector EBML::UIntElement::render() +{ + ByteVector buffer = renderId(); + dataSize = minSize(value); + buffer.append(renderVINT(dataSize, 0)); + uint64_t value = this->value; + static const auto byteOrder = Utils::systemByteOrder(); + if (byteOrder == Utils::LittleEndian) + value = Utils::byteSwap((unsigned long long) value); + + buffer.append(ByteVector((char*) &value + (sizeof(value) - dataSize), dataSize)); + return buffer; +} diff --git a/taglib/matroska/ebml/ebmluintelement.h b/taglib/matroska/ebml/ebmluintelement.h index 1464a3c3..0af32b7c 100644 --- a/taglib/matroska/ebml/ebmluintelement.h +++ b/taglib/matroska/ebml/ebmluintelement.h @@ -35,9 +35,13 @@ namespace TagLib { UIntElement(Id id, int sizeLength, offset_t dataSize) : Element(id, sizeLength, dataSize) {} + UIntElement(Id id) + : UIntElement(id, 0, 0) + {} unsigned int getValue() const { return value; } void setValue(unsigned int value) { this->value = value; } bool read(File &file) override; + ByteVector render() override; private: uint64_t value = 0; diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index 0b3418b1..05290726 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -29,13 +29,7 @@ using namespace TagLib; -namespace TagLib::EBML { - template - unsigned int getNumBytes(uint8_t firstByte); -} - - -EBML::Element* EBML::findElement(File &file, EBML::Id id, offset_t maxOffset) +EBML::Element* EBML::findElement(File &file, EBML::Element::Id id, offset_t maxOffset) { Element *element = nullptr; while (file.tell() < maxOffset) { @@ -54,46 +48,6 @@ EBML::Element* EBML::findNextElement(File &file, offset_t maxOffset) return file.tell() < maxOffset ? Element::factory(file) : nullptr; } -template -unsigned int EBML::getNumBytes(uint8_t firstByte) -{ - static_assert(maxSizeLength >= 1 && maxSizeLength <= 8); - if (!firstByte) { - debug("VINT with greater than 8 bytes not allowed"); - return 0; - } - uint8_t mask = 0b10000000; - unsigned int numBytes = 1; - while (!(mask & firstByte)) { - numBytes++; - mask >>= 1; - } - if (numBytes > maxSizeLength) { - debug(Utils::formatString("VINT size length exceeds %i bytes", maxSizeLength)); - return 0; - } - return numBytes; -} - -EBML::Id EBML::readId(File &file) -{ - auto buffer = file.readBlock(1); - if (buffer.size() != 1) { - debug("Failed to read VINT size"); - return 0; - } - unsigned int nb_bytes = getNumBytes<4>(*buffer.begin()); - if (!nb_bytes) - return 0; - if (nb_bytes > 1) - buffer.append(file.readBlock(nb_bytes - 1)); - if (buffer.size() != nb_bytes) { - debug("Failed to read VINT data"); - return 0; - } - return buffer.toUInt(true); -} - template std::pair EBML::readVINT(File &file) { @@ -103,7 +57,7 @@ std::pair EBML::readVINT(File &file) debug("Failed to read VINT size"); return {0, 0}; } - unsigned int nb_bytes = getNumBytes<8>(*buffer.begin()); + unsigned int nb_bytes = VINTSizeLength<8>(*buffer.begin()); if (!nb_bytes) return {0, 0}; @@ -124,7 +78,7 @@ std::pair EBML::parseVINT(const ByteVector &buffer) if (buffer.isEmpty()) return {0, 0}; - unsigned int numBytes = getNumBytes<8>(*buffer.begin()); + unsigned int numBytes = VINTSizeLength<8>(*buffer.begin()); if (!numBytes) return {0, 0}; @@ -136,3 +90,13 @@ namespace TagLib::EBML { template std::pair parseVINT(const ByteVector &buffer); template std::pair parseVINT(const ByteVector &buffer); } + +ByteVector EBML::renderVINT(uint64_t number, int minSizeLength) +{ + int numBytes = std::max(minSizeLength, minSize(number)); + number |= (1ULL << (numBytes * 7)); + static const auto byteOrder = Utils::systemByteOrder(); + if (byteOrder == Utils::LittleEndian) + number = Utils::byteSwap(static_cast(number)); + return ByteVector((char*) &number + (sizeof(number) - numBytes), numBytes); +} diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h index eae0d520..8f6f5a87 100644 --- a/taglib/matroska/ebml/ebmlutils.h +++ b/taglib/matroska/ebml/ebmlutils.h @@ -25,34 +25,86 @@ #include #include #include "taglib.h" +#include "tutils.h" +#include "tdebug.h" +#include "ebmlelement.h" +#include "tbytevector.h" -#define EBML_ID_HEAD 0x1A45DFA3 -#define EBML_ID_MK_SEGMENT 0x18538067 -#define EBML_ID_MK_TAGS 0x1254C367 -#define EBML_ID_MK_TAG 0x7373 -#define EBML_ID_MK_TAG_TARGETS 0x63C0 -#define EBML_ID_MK_TARGET_TYPE_VALUE 0x68CA -#define EBML_ID_MK_SIMPLE_TAG 0x67C8 -#define EBML_ID_MK_TAG_NAME 0x45A3 -#define EBML_ID_MK_TAG_LANGUAGE 0x447A -#define EBML_ID_MK_TAG_STRING 0x4487 +/* +#define EBMLHeader 0x1A45DFA3 +#define MkSegment 0x18538067 +#define MkTags 0x1254C367 +#define MkTag 0x7373 +#define MkTagTargets 0x63C0 +#define MkTagTargetTypeValue 0x68CA +#define MkSimpleTag 0x67C8 +#define MkTagName 0x45A3 +#define MkTagLanguage 0x447A +#define MkTagString 0x4487 +*/ namespace TagLib { class File; class ByteVector; namespace EBML { - class Element; - using Id = unsigned int; + template + constexpr unsigned int VINTSizeLength(uint8_t firstByte) + { + static_assert(maxSizeLength >= 1 && maxSizeLength <= 8); + if (!firstByte) { + debug("VINT with greater than 8 bytes not allowed"); + return 0; + } + uint8_t mask = 0b10000000; + unsigned int numBytes = 1; + while (!(mask & firstByte)) { + numBytes++; + mask >>= 1; + } + if (numBytes > maxSizeLength) { + debug(Utils::formatString("VINT size length exceeds %i bytes", maxSizeLength)); + return 0; + } + return numBytes; + } - Id readId(File &file); + //Id readId(File &file); template std::pair readVINT(File &file); template std::pair parseVINT(const ByteVector &buffer); - Element* findElement(File &file, EBML::Id id, offset_t maxLength); + Element* findElement(File &file, Element::Id id, offset_t maxLength); Element* findNextElement(File &file, offset_t maxOffset); + ByteVector renderVINT(uint64_t number, int minSizeLength); + constexpr int minSize(uint64_t data) + { + if (data <= 0x7Fu) + return 1; + else if (data <= 0x3FFFu) + return 2; + else if (data <= 0x1FFFFFu) + return 3; + else if (data <= 0xFFFFFFFu) + return 4; + else if (data <= 0x7FFFFFFFFu) + return 5; + else + return 0; + } + + constexpr int idSize(Element::Id id) + { + if (id <= 0xFF) + return 1; + else if (id <= 0xFFFF) + return 2; + else if (id <= 0xFFFFFF) + return 3; + else + return 4; + } } } diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index cbab7f25..ebbf7fbd 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -44,6 +44,11 @@ public: FilePrivate(const FilePrivate &) = delete; FilePrivate &operator=(const FilePrivate &) = delete; Matroska::Tag *tag = nullptr; + offset_t tagsOffset = 0; + offset_t tagsOriginalSize = 0; + offset_t segmentSizeOffset = 0; + offset_t segmentSizeLength = 0; + offset_t segmentDataSize = 0; //Properties *properties = nullptr; }; @@ -59,11 +64,33 @@ Matroska::File::File(FileName file, bool readProperties) } read(readProperties); } +Matroska::File::File(IOStream *stream, bool readProperties) +: TagLib::File(stream), + d(std::make_unique()) +{ + if (!isOpen()) { + debug("Failed to open matroska file"); + setValid(false); + return; + } + read(readProperties); +} Matroska::File::~File() = default; TagLib::Tag* Matroska::File::tag() const { - return d->tag; + return tag(true); +} + +Matroska::Tag* Matroska::File::tag(bool create) const +{ + if (d->tag) + return d->tag; + else { + if (create) + d->tag = new Matroska::Tag(); + return d->tag; + } } void Matroska::File::read(bool readProperties) @@ -72,27 +99,55 @@ void Matroska::File::read(bool readProperties) // Find the EBML Header std::unique_ptr head(EBML::Element::factory(*this)); - if (!head || head->getId() != EBML_ID_HEAD) { + if (!head || head->getId() != EBML::ElementIDs::EBMLHeader) { debug("Failed to find EBML head"); setValid(false); return; } head->skipData(*this); - // Find the Matroska segment + // Find the Matroska segment in the file std::unique_ptr segment( - static_cast(EBML::findElement(*this, EBML_ID_MK_SEGMENT, fileLength - tell())) + static_cast( + EBML::findElement(*this, EBML::ElementIDs::MkSegment, fileLength - tell()) + ) ); if (!segment) { debug("Failed to find Matroska segment"); setValid(false); return; } + d->segmentSizeLength = segment->getSizeLength(); + d->segmentSizeOffset = tell() - d->segmentSizeLength; + d->segmentDataSize = segment->getDataSize(); + + // Read the segment into memory from file if (!segment->read(*this)) { debug("Failed to read segment"); setValid(false); return; } - d->tag = segment->parseTag(); + + // Parse the tag + const auto& [tag, tagsOffset, tagsOriginalSize] = segment->parseTag(); + d->tag = tag; + d->tagsOffset = tagsOffset; + d->tagsOriginalSize = tagsOriginalSize; } + +bool Matroska::File::save() +{ + if (d->tag) { + ByteVector tag = d->tag->render(); + if (!d->tagsOriginalSize) { + d->tagsOffset = d->segmentSizeOffset + d->segmentSizeLength + d->segmentDataSize; + } + insert(tag, d->tagsOffset, d->tagsOriginalSize); + d->segmentDataSize += (tag.size() - d->tagsOriginalSize); + auto segmentDataSizeBuffer = EBML::renderVINT(d->segmentDataSize, d->segmentSizeLength); + insert(segmentDataSizeBuffer, d->segmentSizeOffset, d->segmentSizeLength); + + } + return true; +} diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index 4d3a46b8..69550c16 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -34,16 +34,19 @@ namespace TagLib { namespace Matroska { class Properties; + class Tag; class TAGLIB_EXPORT File : public TagLib::File { public: File(FileName file, bool readProperties = true); + File(IOStream *stream, bool readProperties = true); ~File() override; File(const File &) = delete; File &operator=(const File &) = delete; AudioProperties *audioProperties() const override { return nullptr; } TagLib::Tag *tag() const override; - bool save() override { return false; } + Matroska::Tag *tag(bool create) const; + bool save() override; //PropertyMap properties() const override { return PropertyMap(); } //void removeUnsupportedProperties(const StringList &properties) override { } private: diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp index 923fc11f..47cf0307 100644 --- a/taglib/matroska/matroskasimpletag.cpp +++ b/taglib/matroska/matroskasimpletag.cpp @@ -29,7 +29,7 @@ class Matroska::SimpleTag::SimpleTagPrivate { public: SimpleTagPrivate() = default; - Tag::TargetTypeValue targetTypeValue; + SimpleTag::TargetTypeValue targetTypeValue = TargetTypeValue::None; String name; }; @@ -50,20 +50,20 @@ class Matroska::SimpleTagBinary::SimpleTagBinaryPrivate }; -Matroska::SimpleTag::SimpleTag(Tag::TargetTypeValue targetTypeValue) +Matroska::SimpleTag::SimpleTag() : d(std::make_unique()) { - d->targetTypeValue = targetTypeValue; + } Matroska::SimpleTag::~SimpleTag() = default; -Matroska::Tag::TargetTypeValue Matroska::SimpleTag::targetTypeValue() const +Matroska::SimpleTag::TargetTypeValue Matroska::SimpleTag::targetTypeValue() const { return d->targetTypeValue; } -void Matroska::SimpleTag::setTargetTypeValue(Matroska::Tag::TargetTypeValue targetTypeValue) +void Matroska::SimpleTag::setTargetTypeValue(TargetTypeValue targetTypeValue) { d->targetTypeValue = targetTypeValue; } @@ -78,8 +78,8 @@ void Matroska::SimpleTag::setName(const String &name) d->name = name; } -Matroska::SimpleTagString::SimpleTagString(Tag::TargetTypeValue targetTypeValue) -: Matroska::SimpleTag(targetTypeValue), +Matroska::SimpleTagString::SimpleTagString() +: Matroska::SimpleTag(), dd(std::make_unique()) { @@ -96,8 +96,8 @@ void Matroska::SimpleTagString::setValue(const String &value) dd->value = value; } -Matroska::SimpleTagBinary::SimpleTagBinary(Tag::TargetTypeValue targetTypeValue) -: Matroska::SimpleTag(targetTypeValue), +Matroska::SimpleTagBinary::SimpleTagBinary() +: Matroska::SimpleTag(), dd(std::make_unique()) { diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index 52df0db0..91da7f6b 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -32,9 +32,19 @@ namespace TagLib { class TAGLIB_EXPORT SimpleTag { public: + enum TargetTypeValue { + None = 0, + Shot = 10, + Subtrack = 20, + Track = 30, + Part = 40, + Album = 50, + Edition = 60, + Collection = 70 + }; const String& name() const; - Tag::TargetTypeValue targetTypeValue() const; - void setTargetTypeValue(Tag::TargetTypeValue targetTypeValue); + TargetTypeValue targetTypeValue() const; + void setTargetTypeValue(TargetTypeValue targetTypeValue); void setName(const String &name); virtual ~SimpleTag(); @@ -43,13 +53,13 @@ namespace TagLib { std::unique_ptr d; protected: - SimpleTag(Tag::TargetTypeValue targetTypeValue); + SimpleTag(); }; class TAGLIB_EXPORT SimpleTagString : public SimpleTag { public: - SimpleTagString(Tag::TargetTypeValue targetTypeValue); + SimpleTagString(); ~SimpleTagString() override; const String& value() const; void setValue(const String &value); @@ -62,7 +72,7 @@ namespace TagLib { class TAGLIB_EXPORT SimpleTagBinary : public SimpleTag { public: - SimpleTagBinary(Tag::TargetTypeValue targetTypeValue); + SimpleTagBinary(); ~SimpleTagBinary() override; const ByteVector& value() const; void setValue(const ByteVector &value); diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index d7fec1b0..999e4521 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -20,19 +20,35 @@ #include "matroskatag.h" #include "matroskasimpletag.h" +#include "ebmlmasterelement.h" +#include "ebmlstringelement.h" +#include "ebmlmktags.h" +#include "ebmluintelement.h" +#include "ebmlutils.h" +#include "tpropertymap.h" #include "tlist.h" #include "tdebug.h" +#include +#include +#include + using namespace TagLib; +namespace TagLib { + namespace Matroska { + namespace Utils { + std::pair translateKey(const String &key); + String translateTag(const String &name, Matroska::SimpleTag::TargetTypeValue targetTypeValue); + } + } +} + class Matroska::Tag::TagPrivate { public: TagPrivate() = default; - ~TagPrivate() { - for (auto tag : tags) - delete tag; - } + ~TagPrivate() = default; List tags; }; @@ -41,7 +57,7 @@ Matroska::Tag::Tag() : TagLib::Tag(), d(std::make_unique()) { - + d->tags.setAutoDelete(true); } Matroska::Tag::~Tag() = default; @@ -57,7 +73,296 @@ void Matroska::Tag::removeSimpleTag(SimpleTag *tag) d->tags.erase(it); } +void Matroska::Tag::clearSimpleTags() +{ + d->tags.clear(); +} + const Matroska::SimpleTagsList& Matroska::Tag::simpleTagsList() const { return d->tags; } + +Matroska::SimpleTagsList& Matroska::Tag::simpleTagsListPrivate() +{ + return d->tags; +} + +const Matroska::SimpleTagsList& Matroska::Tag::simpleTagsListPrivate() const +{ + return d->tags; +} + +void Matroska::Tag::setTitle(const String &s) +{ + setTag("TITLE", s); +} + +void Matroska::Tag::setArtist(const String &s) +{ + setTag("ARTIST", s); +} + +void Matroska::Tag::setAlbum(const String &s) +{ + setTag("ALBUM", s); +} + +void Matroska::Tag::setComment(const String &s) +{ + setTag("COMMENT", s); +} + +void Matroska::Tag::setGenre(const String &s) +{ + setTag("GENRE", s); +} + +void Matroska::Tag::setYear(unsigned int i) +{ + setTag("DATE", String::number(i)); +} + +void Matroska::Tag::setTrack(unsigned int i) +{ + setTag("TRACKNUMBER", String::number(i)); +} + +String Matroska::Tag::title() const +{ + const auto value = getTag("TITLE"); + return value ? *value : String(); +} + +String Matroska::Tag::artist() const +{ + const auto value = getTag("ARTIST"); + return value ? *value : String(); +} + +String Matroska::Tag::album() const +{ + const auto value = getTag("ALBUM"); + return value ? *value : String(); +} + +String Matroska::Tag::comment() const +{ + const auto value = getTag("COMMENT"); + return value ? *value : String(); +} + +String Matroska::Tag::genre() const +{ + const auto value = getTag("GENRE"); + return value ? *value : String(); +} + +unsigned int Matroska::Tag::year() const +{ + auto value = getTag("DATE"); + if (!value) + return 0; + auto list = value->split("-"); + return static_cast(list.front().toInt()); +} + +unsigned int Matroska::Tag::track() const +{ + auto value = getTag("TRACKNUMBER"); + if (!value) + return 0; + auto list = value->split("-"); + return static_cast(list.front().toInt()); +} + +bool Matroska::Tag::isEmpty() const +{ + return d->tags.isEmpty(); +} + +ByteVector Matroska::Tag::render() +{ + EBML::MkTags tags; + List*> targetList; + targetList.setAutoDelete(true); + + // Build target-based list + for (auto tag : d->tags) { + auto targetTypeValue = tag->targetTypeValue(); + auto it = std::find_if(targetList.begin(), + targetList.end(), + [&](auto list) { + const auto *simpleTag = list->front(); + return simpleTag->targetTypeValue() == targetTypeValue; + } + ); + if (it == targetList.end()) { + auto list = new List(); + list->append(tag); + targetList.append(list); + } + else + (*it)->append(tag); + } + for (auto list : targetList) { + auto frontTag = list->front(); + auto targetTypeValue = frontTag->targetTypeValue(); + auto tag = new EBML::MasterElement(EBML::ElementIDs::MkTag); + + // Build + auto targets = new EBML::MasterElement(EBML::ElementIDs::MkTagTargets); + if (targetTypeValue != Matroska::SimpleTag::TargetTypeValue::None) { + auto element = new EBML::UIntElement(EBML::ElementIDs::MkTagTargetTypeValue); + element->setValue(static_cast(targetTypeValue)); + targets->appendElement(element); + } + tag->appendElement(targets); + + // Build element + for (auto simpleTag : *list) { + auto t = new EBML::MasterElement(EBML::ElementIDs::MkSimpleTag); + auto tagName = new EBML::UTF8StringElement(EBML::ElementIDs::MkTagName); + tagName->setValue(simpleTag->name()); + t->appendElement(tagName); + + Matroska::SimpleTagString *tStr = nullptr; + Matroska::SimpleTagBinary *tBin = nullptr; + if((tStr = dynamic_cast(simpleTag))) { + auto tagValue = new EBML::UTF8StringElement(EBML::ElementIDs::MkTagString); + tagValue->setValue(tStr->value()); + t->appendElement(tagValue); + } + else if((tBin = dynamic_cast(simpleTag))) { + // Todo + } + + // Todo: language + tag->appendElement(t); + } + tags.appendElement(tag); + } + + return tags.render(); +} + +namespace +{ + // PropertyMap key, Tag name, Target type value + constexpr std::array simpleTagsTranslation { + std::tuple("TITLE", "TITLE", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ALBUM", "TITLE", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("ARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ALBUMARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("SUBTITLE", "SUBTITLE", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("TRACKNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("DISCNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Part), + std::tuple("DATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album), + // Todo - original date + std::tuple("GENRE", "GENRE", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("COMMENT", "COMMENT", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("TITLESORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("COMPOSERSORT", "COMPOSERSORT", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("COMPOSER", "COMPOSER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("LYRICIST", "LYRICIST", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("CONDUCTOR", "CONDUCTOR", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("REMIXER", "REMIXER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("BPM", "BPM", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("COPYRIGHT", "COPYRIGHT", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ENCODEDBY", "ENCODED_BY", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("MOOD", "MOOD", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("MEDIA", "ORIGINAL_MEDIA_TYPE", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("LABEL", "LABEL_CODE", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("CATALOGNUMBER", "CATALOG_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("BARCODE", "BARCODE", Matroska::SimpleTag::TargetTypeValue::Track), + // Todo MusicBrainz tags + }; +} + +bool Matroska::Tag::setTag(const String &key, const String &value) +{ + const auto& [name, targetTypeValue] = Matroska::Utils::translateKey(key); + if (name.isEmpty()) + return false; + removeSimpleTags( + [&name, targetTypeValue] (auto t) { + return t->name() == name + && t->targetTypeValue() == targetTypeValue; + } + ); + if (!value.isEmpty()) { + auto t = new Matroska::SimpleTagString(); + t->setTargetTypeValue(targetTypeValue); + t->setName(name); + t->setValue(value); + addSimpleTag(t); + } + return true; +} + +const String* Matroska::Tag::getTag(const String &key) const +{ + const auto& [name, targetTypeValue] = Matroska::Utils::translateKey(key); + if (name.isEmpty()) + return nullptr; + auto tag = dynamic_cast( + findSimpleTag( + [&name, targetTypeValue] (auto t) { + return t->name() == name + && t->targetTypeValue() == targetTypeValue; + } + ) + ); + return tag ? &tag->value() : nullptr; +} + +std::pair Matroska::Utils::translateKey(const String &key) +{ + auto it = std::find_if(simpleTagsTranslation.cbegin(), + simpleTagsTranslation.cend(), + [&key](const auto &t) { return key == std::get<0>(t); } + ); + if (it != simpleTagsTranslation.end()) + return { std::get<1>(*it), std::get<2>(*it) }; + else + return { String(), Matroska::SimpleTag::TargetTypeValue::None }; +} + +String Matroska::Utils::translateTag(const String &name, Matroska::SimpleTag::TargetTypeValue targetTypeValue) +{ + auto it = std::find_if(simpleTagsTranslation.cbegin(), + simpleTagsTranslation.cend(), + [&name, targetTypeValue](const auto &t) { + return name == std::get<1>(t) + && targetTypeValue == std::get<2>(t); + } + ); + return it != simpleTagsTranslation.end() ? std::get<0>(*it) : String(); +} + +PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) +{ + PropertyMap unsupportedProperties; + for (const auto& [key, value] : propertyMap) { + if (!setTag(key, value.toString())) + unsupportedProperties[key] = value; + } + return unsupportedProperties; +} + +PropertyMap Matroska::Tag::properties() const +{ + PropertyMap properties; + Matroska::SimpleTagString *tStr = nullptr; + for (auto simpleTag : d->tags) { + if ((tStr = dynamic_cast(simpleTag))) { + String key = Matroska::Utils::translateTag(tStr->name(), tStr->targetTypeValue()); + if (!key.isEmpty() && !properties.contains(key)) + properties[key] = tStr->value(); + } + } + return properties; +} diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index af5f9e3b..793f0737 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -22,11 +22,13 @@ #define HAS_MATROSKATAG_H #include +#include +#include #include "tag.h" #include "tstring.h" #include "tlist.h" -//#include "matroskasimpletag.h +#include "matroskafile.h" namespace TagLib { namespace Matroska { @@ -35,38 +37,81 @@ namespace TagLib { class TAGLIB_EXPORT Tag : public TagLib::Tag { public: - enum TargetTypeValue { - None = 0, - Shot = 10, - Subtrack = 20, - Track = 30, - Part = 40, - Album = 50, - Edition = 60, - Collection = 70 - }; Tag(); ~Tag() override; void addSimpleTag(SimpleTag *tag); void removeSimpleTag(SimpleTag *tag); + void clearSimpleTags(); const SimpleTagsList& simpleTagsList() const; - String title() const override { return ""; } - String artist() const override { return ""; } - String album() const override { return ""; } - String comment() const override { return ""; } - String genre() const override { return ""; } - unsigned int year() const override { return 0; } - unsigned int track() const override { return 0; } - void setTitle(const String &s) override {} - void setArtist(const String &s) override {} - void setAlbum(const String &s) override {} - void setComment(const String &s) override {} - void setGenre(const String &s) override {} - void setYear(unsigned int i) override {} - void setTrack(unsigned int i) override {} - bool isEmpty() const override { return false; } + String title() const override; + String artist() const override; + String album() const override; + String comment() const override; + String genre() const override; + unsigned int year() const override; + unsigned int track() const override; + void setTitle(const String &s); + void setArtist(const String &s); + void setAlbum(const String &s); + void setComment(const String &s); + void setGenre(const String &s); + void setYear(unsigned int i); + void setTrack(unsigned int i); + bool isEmpty() const; + PropertyMap properties() const override; + PropertyMap setProperties(const PropertyMap &propertyMap) override; + template + int removeSimpleTags(T&& p) + { + auto &list = simpleTagsListPrivate(); + int numRemoved = 0; + for (auto it = list.begin(); it != list.end();) { + it = std::find_if(it, list.end(), std::forward(p)); + if (it != list.end()) { + delete *it; + *it = nullptr; + it = list.erase(it); + numRemoved++; + } + } + return numRemoved; + } + + template + SimpleTagsList findSimpleTags(T&& p) + { + auto &list = simpleTagsListPrivate(); + for (auto it = list.begin(); it != list.end();) { + it = std::find_if(it, list.end(), std::forward(p)); + if (it != list.end()) { + list.append(*it); + ++it; + } + } + return list; + } + template + const Matroska::SimpleTag* findSimpleTag(T&& p) const + { + auto &list = simpleTagsListPrivate(); + auto it = std::find_if(list.begin(), list.end(), std::forward(p)); + return it != list.end() ? *it : nullptr; + } + template + Matroska::SimpleTag* findSimpleTag(T&&p) + { + return const_cast( + const_cast(this)->findSimpleTag(std::forward(p)) + ); + } private: + friend class Matroska::File; + ByteVector render(); + SimpleTagsList& simpleTagsListPrivate(); + const SimpleTagsList& simpleTagsListPrivate() const; + bool setTag(const String &key, const String &value); + const String* getTag(const String &key) const; class TagPrivate; std::unique_ptr d; };