From 47e9b9a17c3eddcf6290d971729d0e7a4be0d63e Mon Sep 17 00:00:00 2001 From: complexlogic Date: Tue, 19 Sep 2023 13:55:31 -0700 Subject: [PATCH 01/31] Initial matroska support --- examples/CMakeLists.txt | 6 + examples/matroskareader.cpp | 52 ++++++++ taglib/CMakeLists.txt | 42 ++++++- taglib/matroska/ebml/ebmlelement.cpp | 84 +++++++++++++ taglib/matroska/ebml/ebmlelement.h | 59 +++++++++ taglib/matroska/ebml/ebmlmasterelement.cpp | 47 +++++++ taglib/matroska/ebml/ebmlmasterelement.h | 55 ++++++++ taglib/matroska/ebml/ebmlmksegment.cpp | 48 +++++++ taglib/matroska/ebml/ebmlmksegment.h | 53 ++++++++ taglib/matroska/ebml/ebmlmktags.cpp | 102 +++++++++++++++ taglib/matroska/ebml/ebmlmktags.h | 48 +++++++ taglib/matroska/ebml/ebmlstringelement.cpp | 47 +++++++ taglib/matroska/ebml/ebmlstringelement.h | 55 ++++++++ taglib/matroska/ebml/ebmluintelement.cpp | 43 +++++++ taglib/matroska/ebml/ebmluintelement.h | 53 ++++++++ taglib/matroska/ebml/ebmlutils.cpp | 138 +++++++++++++++++++++ taglib/matroska/ebml/ebmlutils.h | 60 +++++++++ taglib/matroska/matroskafile.cpp | 98 +++++++++++++++ taglib/matroska/matroskafile.h | 64 ++++++++++ taglib/matroska/matroskaproperties.h | 83 +++++++++++++ taglib/matroska/matroskasimpletag.cpp | 115 +++++++++++++++++ taglib/matroska/matroskasimpletag.h | 78 ++++++++++++ taglib/matroska/matroskatag.cpp | 63 ++++++++++ taglib/matroska/matroskatag.h | 76 ++++++++++++ 24 files changed, 1567 insertions(+), 2 deletions(-) create mode 100644 examples/matroskareader.cpp create mode 100644 taglib/matroska/ebml/ebmlelement.cpp create mode 100644 taglib/matroska/ebml/ebmlelement.h create mode 100644 taglib/matroska/ebml/ebmlmasterelement.cpp create mode 100644 taglib/matroska/ebml/ebmlmasterelement.h create mode 100644 taglib/matroska/ebml/ebmlmksegment.cpp create mode 100644 taglib/matroska/ebml/ebmlmksegment.h create mode 100644 taglib/matroska/ebml/ebmlmktags.cpp create mode 100644 taglib/matroska/ebml/ebmlmktags.h create mode 100644 taglib/matroska/ebml/ebmlstringelement.cpp create mode 100644 taglib/matroska/ebml/ebmlstringelement.h create mode 100644 taglib/matroska/ebml/ebmluintelement.cpp create mode 100644 taglib/matroska/ebml/ebmluintelement.h create mode 100644 taglib/matroska/ebml/ebmlutils.cpp create mode 100644 taglib/matroska/ebml/ebmlutils.h create mode 100644 taglib/matroska/matroskafile.cpp create mode 100644 taglib/matroska/matroskafile.h create mode 100644 taglib/matroska/matroskaproperties.h create mode 100644 taglib/matroska/matroskasimpletag.cpp create mode 100644 taglib/matroska/matroskasimpletag.h create mode 100644 taglib/matroska/matroskatag.cpp create mode 100644 taglib/matroska/matroskatag.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2360ada0..90feb9e5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,6 +2,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ape + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/matroska ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1 ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2 @@ -38,6 +39,11 @@ target_link_libraries(framelist tag) add_executable(strip-id3v1 strip-id3v1.cpp) target_link_libraries(strip-id3v1 tag) +########### next target ############### + +add_executable(matroskareader matroskareader.cpp) +target_link_libraries(matroskareader tag) + install(TARGETS tagreader tagreader_c tagwriter framelist strip-id3v1 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp new file mode 100644 index 00000000..e1b6045b --- /dev/null +++ b/examples/matroskareader.cpp @@ -0,0 +1,52 @@ +#include +#include "matroskafile.h" +#include "matroskatag.h" +#include "matroskasimpletag.h" +#include "tstring.h" +#include "tutils.h" +#include "tbytevector.h" +#define GREEN_TEXT(s) "" s "" +#define PRINT_PRETTY(label, value) printf("" GREEN_TEXT(label) ": %s\n", value) + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + printf("Usage: matroskareader FILE\n"); + return 1; + } + TagLib::Matroska::File file(argv[1]); + if (!file.isValid()) { + printf("File is not valid\n"); + return 1; + } + auto tag = dynamic_cast(file.tag()); + if (!tag) { + printf("File has no tag\n"); + return 0; + } + + const TagLib::Matroska::SimpleTagsList &list = tag->simpleTagsList(); + printf("Found %i tags:\n\n", list.size()); + + for (TagLib::Matroska::SimpleTag *t : list) { + PRINT_PRETTY("Tag Name", t->name().toCString(true)); + + TagLib::Matroska::SimpleTagString *tString = nullptr; + TagLib::Matroska::SimpleTagBinary *tBinary = nullptr; + if ((tString = dynamic_cast(t))) + PRINT_PRETTY("Tag Value", tString->value().toCString(true)); + else if ((tBinary = dynamic_cast(t))) + PRINT_PRETTY("Tag Value", + TagLib::Utils::formatString("Binary with size %i", tBinary->value().size()).toCString(false) + ); + + auto targetTypeValue = static_cast(t->targetTypeValue()); + PRINT_PRETTY("Target Type Value", + targetTypeValue == 0 ? "None" : TagLib::Utils::formatString("%i", targetTypeValue).toCString(false) + ); + + printf("\n"); + } + + return 0; +} diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 89f988aa..f833298d 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -5,6 +5,8 @@ set(tag_HDR_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2 ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2/frames ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v1 + ${CMAKE_CURRENT_SOURCE_DIR}/matroska + ${CMAKE_CURRENT_SOURCE_DIR}/matroska/ebml ) if(WITH_ASF) set(tag_HDR_DIRS ${tag_HDR_DIRS} @@ -66,7 +68,7 @@ if(WITH_SHORTEN) endif() include_directories(${tag_HDR_DIRS}) -set(tag_HDRS +set(tag_PUBLIC_HDRS tag.h fileref.h audioproperties.h @@ -90,6 +92,9 @@ set(tag_HDRS toolkit/tpropertymap.h toolkit/tdebuglistener.h toolkit/tversionnumber.h + matroska/matroskafile.h + matroska/matroskatag.h + matroska/matroskasimpletag.h mpeg/mpegfile.h mpeg/mpegproperties.h mpeg/mpegheader.h @@ -221,6 +226,34 @@ if(WITH_SHORTEN) ) endif() +set(tag_PRIVATE_HDRS + matroska/ebml/ebmlelement.h + matroska/ebml/ebmlmasterelement.h + matroska/ebml/ebmlmksegment.h + matroska/ebml/ebmlmktags.h + matroska/ebml/ebmlstringelement.h + matroska/ebml/ebmluintelement.h + matroska/ebml/ebmlutils.h +) + +set(tag_HDRS ${tag_PUBLIC_HDRS} ${tag_PRIVATE_HDRS}) + +set(matroska_SRCS + matroska/matroskafile.cpp + matroska/matroskasimpletag.cpp + matroska/matroskatag.cpp +) + +set(ebml_SRCS + matroska/ebml/ebmlelement.cpp + matroska/ebml/ebmlmasterelement.cpp + matroska/ebml/ebmlmksegment.cpp + matroska/ebml/ebmlmktags.cpp + matroska/ebml/ebmlstringelement.cpp + matroska/ebml/ebmluintelement.cpp + matroska/ebml/ebmlutils.cpp +) + set(mpeg_SRCS mpeg/mpegfile.cpp mpeg/mpegproperties.cpp @@ -429,7 +462,7 @@ set(toolkit_SRCS ) set(tag_LIB_SRCS - ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} + ${matroska_SRCS} ${ebml_SRCS} ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} @@ -458,8 +491,13 @@ set_target_properties(tag PROPERTIES SOVERSION ${TAGLIB_SOVERSION_MAJOR} INSTALL_NAME_DIR ${CMAKE_INSTALL_FULL_LIBDIR} DEFINE_SYMBOL MAKE_TAGLIB_LIB +<<<<<<< HEAD INTERFACE_LINK_LIBRARIES "${ZLIB_INTERFACE_LINK_LIBRARIES}" PUBLIC_HEADER "${tag_HDRS}" +======= + LINK_INTERFACE_LIBRARIES "" + PUBLIC_HEADER "${tag_PUBLIC_HDRS}" +>>>>>>> 770c1012 (Initial matroska support) ) if(NOT BUILD_SHARED_LIBS) target_compile_definitions(tag PUBLIC TAGLIB_STATIC) diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp new file mode 100644 index 00000000..13129812 --- /dev/null +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -0,0 +1,84 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlelement.h" +#include "ebmlmasterelement.h" +#include "ebmlmksegment.h" +#include "ebmlmktags.h" +#include "ebmlstringelement.h" +#include "ebmluintelement.h" +#include "ebmlutils.h" +#include "tfile.h" +#include "tdebug.h" +#include "tutils.h" + +using namespace TagLib; + +EBML::Element* EBML::Element::factory(File &file) +{ + // Get the element ID + Id id = readId(file); + if (!id) { + debug("Failed to parse EMBL ElementID"); + return nullptr; + } + + // Get the size length and data length + const auto& [sizeLength, dataSize] = readVINT(file); + if (!sizeLength) + return nullptr; + + // Return the subclass + switch(id) { + case EBML_ID_HEAD: + return new Element(id, sizeLength, dataSize); + + case EBML_ID_MK_SEGMENT: + return new MkSegment(sizeLength, dataSize); + + case EBML_ID_MK_TAGS: + return new MkTags(sizeLength, dataSize); + + case EBML_ID_MK_TAG: + case EBML_ID_MK_TAG_TARGETS: + case EBML_ID_MK_SIMPLE_TAG: + return new MasterElement(id, sizeLength, dataSize); + + case EBML_ID_MK_TAG_NAME: + case EBML_ID_MK_TAG_STRING: + return new UTF8StringElement(id, sizeLength, dataSize); + + case EBML_ID_MK_TAG_LANGUAGE: + return new Latin1StringElement(id, sizeLength, dataSize); + + case EBML_ID_MK_TARGET_TYPE_VALUE: + return new UIntElement(id, sizeLength, dataSize); + + default: + return new Element(id, sizeLength, dataSize); + } + + return nullptr; +} + +void EBML::Element::skipData(File &file) +{ + file.seek(dataSize, File::Position::Current); +} diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h new file mode 100644 index 00000000..db662293 --- /dev/null +++ b/taglib/matroska/ebml/ebmlelement.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLELEMENT_H +#define TAGLIB_EBMLELEMENT_H +#ifndef DO_NOT_DOCUMENT + +#include "tfile.h" +#include "ebmlutils.h" +#include "taglib.h" + +namespace TagLib { + namespace EBML { + class Element + { + public: + Element(Id id, int sizeLength, offset_t dataSize) + : id(id), sizeLength(sizeLength), dataSize(dataSize) + {} + virtual ~Element() = default; + virtual bool isMaster() const { return false; } + virtual bool read(File &file) { + skipData(file); + return true; + } + void skipData(File &file); + Id getId() const { return id; } + int getSizeLength() const { return sizeLength; } + int64_t getDataSize() const { return dataSize; } + static Element* factory(File &file); + + protected: + Id id; + int sizeLength; + offset_t dataSize; + }; + } +} + + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp new file mode 100644 index 00000000..a3595c8b --- /dev/null +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "matroskafile.h" + +#include "tfile.h" +#include "tdebug.h" +#include "tutils.h" + +using namespace TagLib; + +EBML::MasterElement::~MasterElement() +{ + for (auto element : elements) + delete element; +} + +bool EBML::MasterElement::read(File &file) +{ + offset_t maxOffset = file.tell() + dataSize; + Element *element = nullptr; + while ((element = findNextElement(file, maxOffset))) { + if (!element->read(file)) + return false; + elements.append(element); + } + return file.tell() == maxOffset; +} diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h new file mode 100644 index 00000000..8f769db3 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLMASTERELEMENT_H +#define TAGLIB_EBMLMASTERELEMENT_H +#ifndef DO_NOT_DOCUMENT + +#include "ebmlelement.h" +#include "ebmlutils.h" +#include "tlist.h" +#include "taglib.h" + +namespace TagLib { + namespace EBML { + class MasterElement : public Element + { + public: + MasterElement(Id id, int sizeLength, offset_t dataSize) + : Element(id, sizeLength, dataSize) + {} + ~MasterElement() override; + virtual bool isMaster() const override { return true; } + virtual bool read(File &file) override; + List::Iterator begin () { return elements.begin(); } + List::Iterator end () { return elements.end(); } + List::ConstIterator cbegin () const { return elements.cbegin(); } + List::ConstIterator cend () const { return elements.cend(); } + + protected: + List elements; + }; + + } +} + + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp new file mode 100644 index 00000000..e22537fa --- /dev/null +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -0,0 +1,48 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmksegment.h" +#include "ebmlmktags.h" +#include "ebmlutils.h" +#include "matroskafile.h" +#include "matroskatag.h" +#include "tutils.h" +#include "tdebug.h" + +using namespace TagLib; + +EBML::MkSegment::~MkSegment() +{ + delete tags; +} + +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; + return true; +} + +Matroska::Tag* EBML::MkSegment::parseTag() +{ + return tags ? tags->parse() : nullptr; +} diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h new file mode 100644 index 00000000..a3a17375 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmasterelement.h" +#include "taglib.h" + +#ifndef TAGLIB_EBMLMKSEGMENT_H +#define TAGLIB_EBMLMKSEGMENT_H +#ifndef DO_NOT_DOCUMENT + +namespace TagLib { + namespace Matroska { + class Tag; + } + namespace EBML { + class Tags; + class MkTags; + class MkSegment : public MasterElement + { + public: + MkSegment(int sizeLength, offset_t dataSize) + : MasterElement(EBML_ID_MK_SEGMENT, sizeLength, dataSize) + {} + ~MkSegment() override; + bool read(File &file) override; + Matroska::Tag* parseTag(); + + private: + MkTags *tags = nullptr; + + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp new file mode 100644 index 00000000..c088cae9 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -0,0 +1,102 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmktags.h" +#include "ebmluintelement.h" +#include "ebmlstringelement.h" +#include "ebmlutils.h" +#include "matroskafile.h" +#include "matroskatag.h" +#include "matroskasimpletag.h" +#include "tlist.h" +#include "tdebug.h" +#include "tutils.h" + +using namespace TagLib; + +Matroska::Tag* EBML::MkTags::parse() +{ + auto mTag = new Matroska::Tag(); + + // Loop through each element + for (auto tagsChild : elements) { + if (tagsChild->getId() != EBML_ID_MK_TAG) + continue; + auto tag = static_cast(tagsChild); + List simpleTags; + MasterElement *targets = nullptr; + + // Identify the element and the elements + for (auto tagChild : *tag) { + Id tagChildId = tagChild->getId(); + if (!targets && tagChildId == EBML_ID_MK_TAG_TARGETS) + targets = static_cast(tagChild); + else if (tagChildId == EBML_ID_MK_SIMPLE_TAG) + simpleTags.append(static_cast(tagChild)); + } + + // Parse the element + Matroska::Tag::TargetTypeValue targetTypeValue = Matroska::Tag::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( + static_cast(targetsChild)->getValue() + ); + } + } + } + + // Parse each + for (auto simpleTag : simpleTags) { + const String *tagName = nullptr; + const String *tagValueString = nullptr; + const ByteVector *tagValueBinary = nullptr; + + for (auto simpleTagChild : *simpleTag) { + Id id = simpleTagChild->getId(); + if (id == EBML_ID_MK_TAG_NAME && !tagName) + tagName = &(static_cast(simpleTagChild)->getValue()); + else if (id == EBML_ID_MK_TAG_STRING && !tagValueString) + tagValueString = &(static_cast(simpleTagChild)->getValue()); + } + if (!tagName || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) + continue; + + // Create a Simple Tag object and add it to the Tag object + Matroska::SimpleTag *sTag = nullptr; + if (tagValueString) { + auto sTagString = new Matroska::SimpleTagString(targetTypeValue); + sTagString->setValue(*tagValueString); + sTag = sTagString; + } + else if (tagValueBinary) { + auto sTagBinary = new Matroska::SimpleTagBinary(targetTypeValue); + sTagBinary->setValue(*tagValueBinary); + sTag = sTagBinary; + } + sTag->setName(*tagName); + mTag->addSimpleTag(sTag); + } + } + return mTag; +} diff --git a/taglib/matroska/ebml/ebmlmktags.h b/taglib/matroska/ebml/ebmlmktags.h new file mode 100644 index 00000000..a54748de --- /dev/null +++ b/taglib/matroska/ebml/ebmlmktags.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + +#ifndef TAGLIB_EBMLMKTAGS_H +#define TAGLIB_EBMLMKTAGS_H +#ifndef DO_NOT_DOCUMENT + +namespace TagLib { + namespace Matroska { + class Tag; + } + //class Matroska::Tag; + namespace EBML { + class MkTags : public MasterElement + { + public: + MkTags(int sizeLength, offset_t dataSize) + : MasterElement(EBML_ID_MK_TAGS, sizeLength, dataSize) + {} + //virtual void read(File &file) override; + Matroska::Tag* parse(); + + }; + } +} +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlstringelement.cpp b/taglib/matroska/ebml/ebmlstringelement.cpp new file mode 100644 index 00000000..e2cc51e7 --- /dev/null +++ b/taglib/matroska/ebml/ebmlstringelement.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlstringelement.h" +#include "tfile.h" +#include "tstring.h" +#include "tbytevector.h" +#include "tdebug.h" + +using namespace TagLib; + +template +bool EBML::StringElement::read(TagLib::File &file) +{ + ByteVector buffer = file.readBlock(dataSize); + if (buffer.size() != dataSize) { + debug("Failed to read string"); + return false; + } + + // The EBML strings aren't supposed to be null-terminated, + // but we'll check for it and stip the null terminator if found + int nullByte = buffer.find('\0'); + if (nullByte >= 0) + buffer = ByteVector(buffer.data(), nullByte); + value = String(buffer, t); + return true; +} +template bool EBML::StringElement::read(TagLib::File &file); +template bool EBML::StringElement::read(TagLib::File &file); diff --git a/taglib/matroska/ebml/ebmlstringelement.h b/taglib/matroska/ebml/ebmlstringelement.h new file mode 100644 index 00000000..c9c59d94 --- /dev/null +++ b/taglib/matroska/ebml/ebmlstringelement.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLSTRINGELEMENT_H +#define TAGLIB_EBMLSTRINGELEMENT_H +#ifndef DO_NOT_DOCUMENT + +#include +#include "ebmlelement.h" +#include "ebmlutils.h" +#include "tstring.h" + +namespace TagLib { + class File; + namespace EBML { + template + class StringElement : public Element + { + public: + StringElement(Id id, int sizeLength, offset_t dataSize) + : Element(id, sizeLength, dataSize) + {} + const String& getValue() const { return value; } + void setValue(const String &value) { this->value = value; } + //template + bool read(File &file) override; + + private: + String value; + }; + + using UTF8StringElement = StringElement; + using Latin1StringElement = StringElement; + } +} + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmluintelement.cpp b/taglib/matroska/ebml/ebmluintelement.cpp new file mode 100644 index 00000000..f0d2c6b8 --- /dev/null +++ b/taglib/matroska/ebml/ebmluintelement.cpp @@ -0,0 +1,43 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmluintelement.h" +#include "ebmlutils.h" +#include "tbytevector.h" +#include "tfile.h" +#include "tdebug.h" + +using namespace TagLib; + +bool EBML::UIntElement::read(TagLib::File &file) +{ + ByteVector buffer = file.readBlock(dataSize); + if (buffer.size() != dataSize) { + 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; + return true; +} diff --git a/taglib/matroska/ebml/ebmluintelement.h b/taglib/matroska/ebml/ebmluintelement.h new file mode 100644 index 00000000..1464a3c3 --- /dev/null +++ b/taglib/matroska/ebml/ebmluintelement.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLUINTELEMENT_H +#define TAGLIB_EBMLUINTELEMENT_H +#ifndef DO_NOT_DOCUMENT + +#include +#include "ebmlelement.h" + +namespace TagLib { + class File; + + namespace EBML { + class UIntElement : public Element + { + public: + UIntElement(Id id, int sizeLength, offset_t dataSize) + : Element(id, sizeLength, dataSize) + {} + unsigned int getValue() const { return value; } + void setValue(unsigned int value) { this->value = value; } + bool read(File &file) override; + + private: + uint64_t value = 0; + + //protected: + + + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp new file mode 100644 index 00000000..0b3418b1 --- /dev/null +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -0,0 +1,138 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlutils.h" +#include "ebmlelement.h" +#include "tbytevector.h" +#include "matroskafile.h" + +#include "tdebug.h" +#include "tutils.h" +#include "taglib.h" + +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) +{ + Element *element = nullptr; + while (file.tell() < maxOffset) { + element = Element::factory(file); + if (!element || element->getId() == id) + return element; + element->skipData(file); + delete element; + element = nullptr; + } + return element; +} + +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) +{ + static_assert(sizeof(T) == 8); + auto buffer = file.readBlock(1); + if (buffer.size() != 1) { + debug("Failed to read VINT size"); + return {0, 0}; + } + unsigned int nb_bytes = getNumBytes<8>(*buffer.begin()); + if (!nb_bytes) + return {0, 0}; + + if (nb_bytes > 1) + buffer.append(file.readBlock(nb_bytes - 1)); + int bits_to_shift = (sizeof(T) * 8) - (7 * nb_bytes); + offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; + return { nb_bytes, static_cast(buffer.toLongLong(true)) & mask }; +} +namespace TagLib::EBML { + template std::pair readVINT(File &file); + template std::pair readVINT(File &file); +} + +template +std::pair EBML::parseVINT(const ByteVector &buffer) +{ + if (buffer.isEmpty()) + return {0, 0}; + + unsigned int numBytes = getNumBytes<8>(*buffer.begin()); + if (!numBytes) + return {0, 0}; + + int bits_to_shift = (sizeof(T) * 8) - (7 * numBytes); + offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; + return { numBytes, static_cast(buffer.toLongLong(true)) & mask }; +} +namespace TagLib::EBML { + template std::pair parseVINT(const ByteVector &buffer); + template std::pair parseVINT(const ByteVector &buffer); +} diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h new file mode 100644 index 00000000..eae0d520 --- /dev/null +++ b/taglib/matroska/ebml/ebmlutils.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLUTILS_H +#define TAGLIB_EBMLUTILS_H +#ifndef DO_NOT_DOCUMENT + +#include +#include +#include "taglib.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 + +namespace TagLib { + class File; + class ByteVector; + + namespace EBML { + class Element; + using Id = unsigned int; + + 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* findNextElement(File &file, offset_t maxOffset); + + } +} + +#endif +#endif diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp new file mode 100644 index 00000000..cbab7f25 --- /dev/null +++ b/taglib/matroska/matroskafile.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskafile.h" +#include "matroskatag.h" +#include "ebmlutils.h" +#include "ebmlelement.h" +#include "ebmlmksegment.h" +#include "tlist.h" +#include "tdebug.h" +#include "tutils.h" + +#include + +using namespace TagLib; + +class Matroska::File::FilePrivate +{ +public: + FilePrivate() {} + + ~FilePrivate() + { + delete tag; + } + + FilePrivate(const FilePrivate &) = delete; + FilePrivate &operator=(const FilePrivate &) = delete; + Matroska::Tag *tag = nullptr; + + //Properties *properties = nullptr; +}; + +Matroska::File::File(FileName file, bool readProperties) +: TagLib::File(file), + 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; +} + +void Matroska::File::read(bool readProperties) +{ + offset_t fileLength = length(); + + // Find the EBML Header + std::unique_ptr head(EBML::Element::factory(*this)); + if (!head || head->getId() != EBML_ID_HEAD) { + debug("Failed to find EBML head"); + setValid(false); + return; + } + head->skipData(*this); + + // Find the Matroska segment + std::unique_ptr segment( + static_cast(EBML::findElement(*this, EBML_ID_MK_SEGMENT, fileLength - tell())) + ); + if (!segment) { + debug("Failed to find Matroska segment"); + setValid(false); + return; + } + if (!segment->read(*this)) { + debug("Failed to read segment"); + setValid(false); + return; + } + d->tag = segment->parseTag(); + +} diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h new file mode 100644 index 00000000..4d3a46b8 --- /dev/null +++ b/taglib/matroska/matroskafile.h @@ -0,0 +1,64 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef HAS_MATROSKAFILE_H +#define HAS_MATROSKAFILE_H + +#include "taglib_export.h" +#include "tfile.h" +#include "tag.h" + + +namespace TagLib { + namespace Matroska { + class Properties; + class TAGLIB_EXPORT File : public TagLib::File + { + public: + File(FileName file, 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; } + //PropertyMap properties() const override { return PropertyMap(); } + //void removeUnsupportedProperties(const StringList &properties) override { } + private: + void read(bool readProperties); + class FilePrivate; + std::unique_ptr d; + + }; + } +} + + + + + + + + #endif \ No newline at end of file diff --git a/taglib/matroska/matroskaproperties.h b/taglib/matroska/matroskaproperties.h new file mode 100644 index 00000000..a3a735f3 --- /dev/null +++ b/taglib/matroska/matroskaproperties.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MATROSKAPROPERTIES_H +#define TAGLIB_MATROSKAPROPERTIES_H + +#include "taglib_export.h" +#include "audioproperties.h" + + +namespace TagLib { + + namespace MPEG { + + class File; + + //! An implementation of audio property reading for MP3 + + /*! + * This reads the data from an MPEG Layer III stream found in the + * AudioProperties API. + */ + + class TAGLIB_EXPORT Properties : public AudioProperties + { + public: + + /*! + * Destroys this MPEG Properties instance. + */ + ~Properties() override {} + + Properties(const Properties &) = delete; + Properties &operator=(const Properties &) = delete; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + int lengthInMilliseconds() const override {} + + /*! + * Returns the average bit rate of the file in kb/s. + */ + int bitrate() const override {} + + /*! + * Returns the sample rate in Hz. + */ + int sampleRate() const override {} + + /*! + * Returns the number of audio channels. + */ + int channels() const override {} + + private: + + class PropertiesPrivate; + std::unique_ptr d; + }; + } // namespace MPEG +} // namespace TagLib + +#endif diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp new file mode 100644 index 00000000..923fc11f --- /dev/null +++ b/taglib/matroska/matroskasimpletag.cpp @@ -0,0 +1,115 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskasimpletag.h" +#include "matroskatag.h" +#include "tstring.h" +#include "tbytevector.h" + +using namespace TagLib; + +class Matroska::SimpleTag::SimpleTagPrivate +{ + public: + SimpleTagPrivate() = default; + Tag::TargetTypeValue targetTypeValue; + String name; + +}; + +class Matroska::SimpleTagString::SimpleTagStringPrivate +{ + public: + SimpleTagStringPrivate() = default; + String value; + +}; + +class Matroska::SimpleTagBinary::SimpleTagBinaryPrivate +{ + public: + SimpleTagBinaryPrivate() = default; + ByteVector value; + +}; + +Matroska::SimpleTag::SimpleTag(Tag::TargetTypeValue targetTypeValue) +: d(std::make_unique()) +{ + d->targetTypeValue = targetTypeValue; +} +Matroska::SimpleTag::~SimpleTag() = default; + + +Matroska::Tag::TargetTypeValue Matroska::SimpleTag::targetTypeValue() const +{ + return d->targetTypeValue; +} + +void Matroska::SimpleTag::setTargetTypeValue(Matroska::Tag::TargetTypeValue targetTypeValue) +{ + d->targetTypeValue = targetTypeValue; +} + +const String& Matroska::SimpleTag::name() const +{ + return d->name; +} + +void Matroska::SimpleTag::setName(const String &name) +{ + d->name = name; +} + +Matroska::SimpleTagString::SimpleTagString(Tag::TargetTypeValue targetTypeValue) +: Matroska::SimpleTag(targetTypeValue), + dd(std::make_unique()) +{ + +} +Matroska::SimpleTagString::~SimpleTagString() = default; + +const String& Matroska::SimpleTagString::value() const +{ + return dd->value; +} + +void Matroska::SimpleTagString::setValue(const String &value) +{ + dd->value = value; +} + +Matroska::SimpleTagBinary::SimpleTagBinary(Tag::TargetTypeValue targetTypeValue) +: Matroska::SimpleTag(targetTypeValue), + dd(std::make_unique()) +{ + +} +Matroska::SimpleTagBinary::~SimpleTagBinary() = default; + +const ByteVector& Matroska::SimpleTagBinary::value() const +{ + return dd->value; +} + +void Matroska::SimpleTagBinary::setValue(const ByteVector &value) +{ + dd->value = value; +} diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h new file mode 100644 index 00000000..52df0db0 --- /dev/null +++ b/taglib/matroska/matroskasimpletag.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef HAS_MATROSKASIMPLETAG_H +#define HAS_MATROSKASIMPLETAG_H + +#include +#include "tag.h" +#include "matroskatag.h" + +namespace TagLib { + class String; + class ByteVector; + namespace Matroska { + class TAGLIB_EXPORT SimpleTag + { + public: + const String& name() const; + Tag::TargetTypeValue targetTypeValue() const; + void setTargetTypeValue(Tag::TargetTypeValue targetTypeValue); + void setName(const String &name); + virtual ~SimpleTag(); + + private: + class SimpleTagPrivate; + std::unique_ptr d; + + protected: + SimpleTag(Tag::TargetTypeValue targetTypeValue); + }; + + class TAGLIB_EXPORT SimpleTagString : public SimpleTag + { + public: + SimpleTagString(Tag::TargetTypeValue targetTypeValue); + ~SimpleTagString() override; + const String& value() const; + void setValue(const String &value); + + private: + class SimpleTagStringPrivate; + std::unique_ptr dd; + }; + + class TAGLIB_EXPORT SimpleTagBinary : public SimpleTag + { + public: + SimpleTagBinary(Tag::TargetTypeValue targetTypeValue); + ~SimpleTagBinary() override; + const ByteVector& value() const; + void setValue(const ByteVector &value); + + private: + class SimpleTagBinaryPrivate; + std::unique_ptr dd; + }; + + } +} + +#endif diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp new file mode 100644 index 00000000..d7fec1b0 --- /dev/null +++ b/taglib/matroska/matroskatag.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskatag.h" +#include "matroskasimpletag.h" +#include "tlist.h" +#include "tdebug.h" + +using namespace TagLib; + +class Matroska::Tag::TagPrivate +{ + public: + TagPrivate() = default; + ~TagPrivate() { + for (auto tag : tags) + delete tag; + } + List tags; + +}; + +Matroska::Tag::Tag() +: TagLib::Tag(), + d(std::make_unique()) +{ + +} +Matroska::Tag::~Tag() = default; + +void Matroska::Tag::addSimpleTag(SimpleTag *tag) +{ + d->tags.append(tag); +} + +void Matroska::Tag::removeSimpleTag(SimpleTag *tag) +{ + auto it = d->tags.find(tag); + if (it != d->tags.end()) + d->tags.erase(it); +} + +const Matroska::SimpleTagsList& Matroska::Tag::simpleTagsList() const +{ + return d->tags; +} diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h new file mode 100644 index 00000000..af5f9e3b --- /dev/null +++ b/taglib/matroska/matroskatag.h @@ -0,0 +1,76 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef HAS_MATROSKATAG_H +#define HAS_MATROSKATAG_H + +#include + +#include "tag.h" +#include "tstring.h" +#include "tlist.h" +//#include "matroskasimpletag.h + +namespace TagLib { + namespace Matroska { + class SimpleTag; + using SimpleTagsList = List; + 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); + 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; } + + private: + class TagPrivate; + std::unique_ptr d; + }; + } +} + +#endif From 80837485cfe28c4b51fc178c3676d31adf0332f7 Mon Sep 17 00:00:00 2001 From: complexlogic Date: Sat, 30 Sep 2023 22:02:22 -0700 Subject: [PATCH 02/31] 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; }; From b4e79a4a274cd204a6a15c37ef7ed13f6bd26a30 Mon Sep 17 00:00:00 2001 From: complexlogic Date: Fri, 6 Oct 2023 17:07:35 -0700 Subject: [PATCH 03/31] Fix Clang build --- taglib/matroska/matroskasimpletag.h | 2 +- taglib/matroska/matroskatag.cpp | 10 ++++++++-- taglib/matroska/matroskatag.h | 18 +++++++++--------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index 91da7f6b..4cdafc7b 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -23,7 +23,7 @@ #include #include "tag.h" -#include "matroskatag.h" +//#include "matroskatag.h" namespace TagLib { class String; diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 999e4521..1fa2e0ea 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -284,7 +284,10 @@ namespace bool Matroska::Tag::setTag(const String &key, const String &value) { - const auto& [name, targetTypeValue] = Matroska::Utils::translateKey(key); + const auto pair = Matroska::Utils::translateKey(key); + // Workaround Clang issue - no lambda capture of structured bindings + const String &name = pair.first; + auto targetTypeValue = pair.second; if (name.isEmpty()) return false; removeSimpleTags( @@ -305,7 +308,10 @@ bool Matroska::Tag::setTag(const String &key, const String &value) const String* Matroska::Tag::getTag(const String &key) const { - const auto& [name, targetTypeValue] = Matroska::Utils::translateKey(key); + const auto pair = Matroska::Utils::translateKey(key); + // Workaround Clang issue - no lambda capture of structured bindings + const String &name = pair.first; + auto targetTypeValue = pair.second; if (name.isEmpty()) return nullptr; auto tag = dynamic_cast( diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 793f0737..8e0f474f 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -29,10 +29,10 @@ #include "tstring.h" #include "tlist.h" #include "matroskafile.h" +#include "matroskasimpletag.h" namespace TagLib { namespace Matroska { - class SimpleTag; using SimpleTagsList = List; class TAGLIB_EXPORT Tag : public TagLib::Tag { @@ -50,14 +50,14 @@ namespace TagLib { 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; + 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; PropertyMap properties() const override; PropertyMap setProperties(const PropertyMap &propertyMap) override; template From 6342f00e8bd4eda5c12305abb9b30f0c91c2fa54 Mon Sep 17 00:00:00 2001 From: complexlogic Date: Sun, 8 Oct 2023 08:11:01 -0700 Subject: [PATCH 04/31] Support tag language --- examples/matroskareader.cpp | 2 ++ examples/matroskawriter.cpp | 1 + taglib/matroska/ebml/ebmlelement.h | 22 ++++++++++++---------- taglib/matroska/ebml/ebmlmktags.cpp | 9 +++++++++ taglib/matroska/matroskasimpletag.cpp | 22 ++++++++++++++++++++++ taglib/matroska/matroskasimpletag.h | 6 +++++- taglib/matroska/matroskatag.cpp | 15 +++++++++++++-- 7 files changed, 64 insertions(+), 13 deletions(-) diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp index e1b6045b..66993e9e 100644 --- a/examples/matroskareader.cpp +++ b/examples/matroskareader.cpp @@ -44,6 +44,8 @@ int main(int argc, char *argv[]) PRINT_PRETTY("Target Type Value", targetTypeValue == 0 ? "None" : TagLib::Utils::formatString("%i", targetTypeValue).toCString(false) ); + const TagLib::String &language = t->language(); + PRINT_PRETTY("Language", !language.isEmpty() ? language.toCString(false) : "Not set"); printf("\n"); } diff --git a/examples/matroskawriter.cpp b/examples/matroskawriter.cpp index 0ad1f929..637876f6 100644 --- a/examples/matroskawriter.cpp +++ b/examples/matroskawriter.cpp @@ -23,6 +23,7 @@ int main(int argc, char *argv[]) simpleTag->setName("Test Name 1"); simpleTag->setTargetTypeValue(TagLib::Matroska::SimpleTag::TargetTypeValue::Track); simpleTag->setValue("Test Value 1"); + simpleTag->setLanguage("en"); tag->addSimpleTag(simpleTag); simpleTag = new TagLib::Matroska::SimpleTagString(); diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 0338659e..74efc3fd 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -59,16 +59,18 @@ namespace TagLib { }; 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; + 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; + inline constexpr Element::Id MkTagsTagLanguage = 0x447A; + inline constexpr Element::Id MkTagsLanguageDefault = 0x4484; } } } diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index ae2db490..bfc3e606 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -71,6 +71,8 @@ Matroska::Tag* EBML::MkTags::parse() const String *tagName = nullptr; const String *tagValueString = nullptr; const ByteVector *tagValueBinary = nullptr; + const String *language = nullptr; + bool defaultLanguageFlag = true; for (auto simpleTagChild : *simpleTag) { Id id = simpleTagChild->getId(); @@ -78,6 +80,10 @@ Matroska::Tag* EBML::MkTags::parse() tagName = &(static_cast(simpleTagChild)->getValue()); else if (id == ElementIDs::MkTagString && !tagValueString) tagValueString = &(static_cast(simpleTagChild)->getValue()); + else if (id == ElementIDs::MkTagsTagLanguage && !language) + language = &(static_cast(simpleTagChild)->getValue()); + else if (id == ElementIDs::MkTagsLanguageDefault) + defaultLanguageFlag = static_cast(simpleTagChild)->getValue() ? true : false; } if (!tagName || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) continue; @@ -97,6 +103,9 @@ Matroska::Tag* EBML::MkTags::parse() sTag = sTagBinary; } sTag->setName(*tagName); + if (language) + sTag->setLanguage(*language); + sTag->setDefaultLanguageFlag(defaultLanguageFlag); mTag->addSimpleTag(sTag); } } diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp index 47cf0307..49094fc6 100644 --- a/taglib/matroska/matroskasimpletag.cpp +++ b/taglib/matroska/matroskasimpletag.cpp @@ -31,6 +31,8 @@ class Matroska::SimpleTag::SimpleTagPrivate SimpleTagPrivate() = default; SimpleTag::TargetTypeValue targetTypeValue = TargetTypeValue::None; String name; + String language; + bool defaultLanguageFlag = true; }; @@ -73,6 +75,26 @@ const String& Matroska::SimpleTag::name() const return d->name; } +const String& Matroska::SimpleTag::language() const +{ + return d->language; +} + +void Matroska::SimpleTag::setLanguage(const String &language) +{ + d->language = language; +} + +bool Matroska::SimpleTag::defaultLanguageFlag() const +{ + return d->defaultLanguageFlag; +} + +void Matroska::SimpleTag::setDefaultLanguageFlag(bool flag) +{ + d->defaultLanguageFlag = flag; +} + void Matroska::SimpleTag::setName(const String &name) { d->name = name; diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index 4cdafc7b..74553a97 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -44,8 +44,12 @@ namespace TagLib { }; const String& name() const; TargetTypeValue targetTypeValue() const; - void setTargetTypeValue(TargetTypeValue targetTypeValue); + const String& language() const; + bool defaultLanguageFlag() const; void setName(const String &name); + void setTargetTypeValue(TargetTypeValue targetTypeValue); + void setLanguage(const String &language); + void setDefaultLanguageFlag(bool flag); virtual ~SimpleTag(); private: diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 1fa2e0ea..31113faf 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -210,7 +210,7 @@ ByteVector Matroska::Tag::render() auto targetTypeValue = frontTag->targetTypeValue(); auto tag = new EBML::MasterElement(EBML::ElementIDs::MkTag); - // Build + // Build element auto targets = new EBML::MasterElement(EBML::ElementIDs::MkTagTargets); if (targetTypeValue != Matroska::SimpleTag::TargetTypeValue::None) { auto element = new EBML::UIntElement(EBML::ElementIDs::MkTagTargetTypeValue); @@ -226,6 +226,7 @@ ByteVector Matroska::Tag::render() tagName->setValue(simpleTag->name()); t->appendElement(tagName); + // Tag Value Matroska::SimpleTagString *tStr = nullptr; Matroska::SimpleTagBinary *tBin = nullptr; if((tStr = dynamic_cast(simpleTag))) { @@ -237,7 +238,17 @@ ByteVector Matroska::Tag::render() // Todo } - // Todo: language + // Language + auto language = new EBML::Latin1StringElement(EBML::ElementIDs::MkTagsTagLanguage); + const String &lang = simpleTag->language(); + language->setValue(!lang.isEmpty() ? lang : "und"); + t->appendElement(language); + + // Default language flag + auto dlf = new EBML::UIntElement(EBML::ElementIDs::MkTagsLanguageDefault); + dlf->setValue(simpleTag->defaultLanguageFlag() ? 1 : 0); + t->appendElement(dlf); + tag->appendElement(t); } tags.appendElement(tag); From 6d019a894ce159f1e305e0255c1b946f11ed3255 Mon Sep 17 00:00:00 2001 From: complexlogic Date: Wed, 18 Oct 2023 17:58:56 -0700 Subject: [PATCH 05/31] Initial attachments support --- examples/matroskareader.cpp | 39 +++++-- examples/matroskawriter.cpp | 22 +++- taglib/CMakeLists.txt | 10 ++ taglib/matroska/ebml/ebmlbinaryelement.cpp | 46 ++++++++ taglib/matroska/ebml/ebmlbinaryelement.h | 54 +++++++++ taglib/matroska/ebml/ebmlelement.cpp | 32 ++++-- taglib/matroska/ebml/ebmlelement.h | 35 +++--- taglib/matroska/ebml/ebmlmasterelement.cpp | 8 +- taglib/matroska/ebml/ebmlmasterelement.h | 12 +- taglib/matroska/ebml/ebmlmkattachments.cpp | 77 +++++++++++++ taglib/matroska/ebml/ebmlmkattachments.h | 49 ++++++++ taglib/matroska/ebml/ebmlmksegment.cpp | 40 +++++-- taglib/matroska/ebml/ebmlmksegment.h | 13 ++- taglib/matroska/ebml/ebmlmktags.cpp | 38 ++++--- taglib/matroska/ebml/ebmlmktags.h | 6 +- taglib/matroska/ebml/ebmlstringelement.cpp | 6 +- taglib/matroska/ebml/ebmluintelement.cpp | 30 ++++- taglib/matroska/ebml/ebmluintelement.h | 6 +- taglib/matroska/ebml/ebmlutils.cpp | 25 +++-- taglib/matroska/ebml/ebmlutils.h | 24 ++-- taglib/matroska/matroskaattachedfile.cpp | 77 +++++++++++++ taglib/matroska/matroskaattachedfile.h | 57 ++++++++++ taglib/matroska/matroskaattachments.cpp | 97 ++++++++++++++++ taglib/matroska/matroskaattachments.h | 60 ++++++++++ taglib/matroska/matroskaelement.cpp | 44 ++++++++ taglib/matroska/matroskaelement.h | 51 +++++++++ taglib/matroska/matroskafile.cpp | 125 +++++++++++++++++---- taglib/matroska/matroskafile.h | 2 + taglib/matroska/matroskatag.cpp | 36 +++--- taglib/matroska/matroskatag.h | 20 +++- 30 files changed, 983 insertions(+), 158 deletions(-) create mode 100644 taglib/matroska/ebml/ebmlbinaryelement.cpp create mode 100644 taglib/matroska/ebml/ebmlbinaryelement.h create mode 100644 taglib/matroska/ebml/ebmlmkattachments.cpp create mode 100644 taglib/matroska/ebml/ebmlmkattachments.h create mode 100644 taglib/matroska/matroskaattachedfile.cpp create mode 100644 taglib/matroska/matroskaattachedfile.h create mode 100644 taglib/matroska/matroskaattachments.cpp create mode 100644 taglib/matroska/matroskaattachments.h create mode 100644 taglib/matroska/matroskaelement.cpp create mode 100644 taglib/matroska/matroskaelement.h diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp index 66993e9e..76f8891a 100644 --- a/examples/matroskareader.cpp +++ b/examples/matroskareader.cpp @@ -2,40 +2,42 @@ #include "matroskafile.h" #include "matroskatag.h" #include "matroskasimpletag.h" +#include "matroskaattachments.h" +#include "matroskaattachedfile.h" #include "tstring.h" #include "tutils.h" #include "tbytevector.h" #define GREEN_TEXT(s) "" s "" -#define PRINT_PRETTY(label, value) printf("" GREEN_TEXT(label) ": %s\n", value) +#define PRINT_PRETTY(label, value) printf(" " GREEN_TEXT(label) ": %s\n", value) int main(int argc, char *argv[]) { - if (argc != 2) { + if(argc != 2) { printf("Usage: matroskareader FILE\n"); return 1; } TagLib::Matroska::File file(argv[1]); - if (!file.isValid()) { + if(!file.isValid()) { printf("File is not valid\n"); return 1; } auto tag = dynamic_cast(file.tag()); - if (!tag) { + if(!tag) { printf("File has no tag\n"); return 0; } const TagLib::Matroska::SimpleTagsList &list = tag->simpleTagsList(); - printf("Found %i tags:\n\n", list.size()); + printf("Found %u tag(s):\n", list.size()); - for (TagLib::Matroska::SimpleTag *t : list) { + for(TagLib::Matroska::SimpleTag *t : list) { PRINT_PRETTY("Tag Name", t->name().toCString(true)); TagLib::Matroska::SimpleTagString *tString = nullptr; TagLib::Matroska::SimpleTagBinary *tBinary = nullptr; - if ((tString = dynamic_cast(t))) + if((tString = dynamic_cast(t))) PRINT_PRETTY("Tag Value", tString->value().toCString(true)); - else if ((tBinary = dynamic_cast(t))) + else if((tBinary = dynamic_cast(t))) PRINT_PRETTY("Tag Value", TagLib::Utils::formatString("Binary with size %i", tBinary->value().size()).toCString(false) ); @@ -50,5 +52,26 @@ int main(int argc, char *argv[]) printf("\n"); } + TagLib::Matroska::Attachments *attachments = file.attachments(); + if(attachments) { + const TagLib::Matroska::Attachments::AttachedFileList &list = attachments->attachedFileList(); + printf("Found %u attachment(s)\n", list.size()); + for(auto attachedFile : list) { + PRINT_PRETTY("Filename", attachedFile->fileName().toCString(true)); + const TagLib::String &description = attachedFile->description(); + PRINT_PRETTY("Description", !description.isEmpty() ? description.toCString(true) : "None"); + const TagLib::String &mediaType = attachedFile->mediaType(); + PRINT_PRETTY("Media Type", !mediaType.isEmpty() ? mediaType.toCString(false) : "None"); + PRINT_PRETTY("Data Size", + TagLib::Utils::formatString("%u byte(s)",attachedFile->data().size()).toCString(false) + ); + PRINT_PRETTY("UID", + TagLib::Utils::formatString("%llu",attachedFile->uid()).toCString(false) + ); + } + } + else + printf("File has no attachments\n"); + return 0; } diff --git a/examples/matroskawriter.cpp b/examples/matroskawriter.cpp index 637876f6..6ae11953 100644 --- a/examples/matroskawriter.cpp +++ b/examples/matroskawriter.cpp @@ -2,17 +2,20 @@ #include "matroskafile.h" #include "matroskatag.h" #include "matroskasimpletag.h" +#include "matroskaattachments.h" +#include "matroskaattachedfile.h" +#include "tfilestream.h" #include "tstring.h" #include "tutils.h" int main(int argc, char *argv[]) { - if (argc != 2) { - printf("Usage: matroskawriter FILE\n"); + if(argc != 3) { + printf("Usage: matroskawriter FILE ARTWORK\n"); return 1; } TagLib::Matroska::File file(argv[1]); - if (!file.isValid()) { + if(!file.isValid()) { printf("File is not valid\n"); return 1; } @@ -31,6 +34,19 @@ int main(int argc, char *argv[]) simpleTag->setTargetTypeValue(TagLib::Matroska::SimpleTag::TargetTypeValue::Album); simpleTag->setValue("Test Value 2"); tag->addSimpleTag(simpleTag); + tag->setTitle("Test title"); + tag->setArtist("Test artist"); + tag->setYear(1969); + + TagLib::FileStream image(argv[2]); + auto data = image.readBlock(image.length()); + auto attachments = file.attachments(true); + auto attachedFile = new TagLib::Matroska::AttachedFile(); + attachedFile->setFileName("cover.jpg"); + attachedFile->setMediaType("image/jpeg"); + attachedFile->setData(data); + //attachedFile->setUID(5081000385627515072ull); + attachments->addAttachedFile(attachedFile); file.save(); diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index f833298d..00176940 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -92,9 +92,12 @@ set(tag_PUBLIC_HDRS toolkit/tpropertymap.h toolkit/tdebuglistener.h toolkit/tversionnumber.h + matroska/matroskaattachedfile.h + matroska/matroskaattachments.h matroska/matroskafile.h matroska/matroskatag.h matroska/matroskasimpletag.h + matroska/matroskaelement.h mpeg/mpegfile.h mpeg/mpegproperties.h mpeg/mpegheader.h @@ -227,8 +230,10 @@ if(WITH_SHORTEN) endif() set(tag_PRIVATE_HDRS + matroska/ebml/ebmlbinaryelement.h matroska/ebml/ebmlelement.h matroska/ebml/ebmlmasterelement.h + matroska/ebml/ebmlmkattachments.h matroska/ebml/ebmlmksegment.h matroska/ebml/ebmlmktags.h matroska/ebml/ebmlstringelement.h @@ -239,14 +244,19 @@ set(tag_PRIVATE_HDRS set(tag_HDRS ${tag_PUBLIC_HDRS} ${tag_PRIVATE_HDRS}) set(matroska_SRCS + matroska/matroskaattachedfile.cpp + matroska/matroskaattachments.cpp + matroska/matroskaelement.cpp matroska/matroskafile.cpp matroska/matroskasimpletag.cpp matroska/matroskatag.cpp ) set(ebml_SRCS + matroska/ebml/ebmlbinaryelement.cpp matroska/ebml/ebmlelement.cpp matroska/ebml/ebmlmasterelement.cpp + matroska/ebml/ebmlmkattachments.cpp matroska/ebml/ebmlmksegment.cpp matroska/ebml/ebmlmktags.cpp matroska/ebml/ebmlstringelement.cpp diff --git a/taglib/matroska/ebml/ebmlbinaryelement.cpp b/taglib/matroska/ebml/ebmlbinaryelement.cpp new file mode 100644 index 00000000..74fa1e3a --- /dev/null +++ b/taglib/matroska/ebml/ebmlbinaryelement.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlbinaryelement.h" +#include "tfile.h" +#include "tbytevector.h" +#include "tdebug.h" +#include + +using namespace TagLib; + +bool EBML::BinaryElement::read(TagLib::File &file) +{ + value = file.readBlock(dataSize); + if(value.size() != dataSize) { + debug("Failed to read binary element"); + return false; + } + return true; +} + +ByteVector EBML::BinaryElement::render() +{ + ByteVector buffer = renderId(); + dataSize = value.size(); + buffer.append(renderVINT(dataSize, 0)); + buffer.append(value); + return buffer; +} diff --git a/taglib/matroska/ebml/ebmlbinaryelement.h b/taglib/matroska/ebml/ebmlbinaryelement.h new file mode 100644 index 00000000..4e8bcb3d --- /dev/null +++ b/taglib/matroska/ebml/ebmlbinaryelement.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLBINARYELEMENT_H +#define TAGLIB_EBMLBINARYELEMENT_H +#ifndef DO_NOT_DOCUMENT + +#include +#include "ebmlelement.h" +#include "ebmlutils.h" +#include "tbytevector.h" + +namespace TagLib { + class File; + namespace EBML { + class BinaryElement : public Element + { + public: + BinaryElement(Id id, int sizeLength, offset_t dataSize) + : Element(id, sizeLength, dataSize) + {} + BinaryElement(Id id) + : Element(id, 0, 0) + {} + const ByteVector& getValue() const { return value; } + void setValue(const ByteVector &value) { this->value = value; } + bool read(File &file) override; + ByteVector render() override; + + private: + ByteVector value; + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 69467b4d..7c3ff3d5 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -20,8 +20,10 @@ #include "ebmlelement.h" #include "ebmlmasterelement.h" +#include "ebmlbinaryelement.h" #include "ebmlmksegment.h" #include "ebmlmktags.h" +#include "ebmlmkattachments.h" #include "ebmlstringelement.h" #include "ebmluintelement.h" #include "ebmlutils.h" @@ -36,15 +38,16 @@ using namespace TagLib; EBML::Element* EBML::Element::factory(File &file) { // Get the element ID + offset_t offset = file.tell(); Id id = readId(file); - if (!id) { + if(!id) { debug("Failed to parse EMBL ElementID"); return nullptr; } // Get the size length and data length const auto& [sizeLength, dataSize] = readVINT(file); - if (!sizeLength) + if(!sizeLength) return nullptr; // Return the subclass @@ -53,26 +56,37 @@ EBML::Element* EBML::Element::factory(File &file) return new Element(id, sizeLength, dataSize); case ElementIDs::MkSegment: - return new MkSegment(sizeLength, dataSize); + return new MkSegment(sizeLength, dataSize, offset); case ElementIDs::MkTags: - return new MkTags(sizeLength, dataSize); + return new MkTags(sizeLength, dataSize, offset); + + case ElementIDs::MkAttachments: + return new MkAttachments(sizeLength, dataSize, offset); case ElementIDs::MkTag: case ElementIDs::MkTagTargets: case ElementIDs::MkSimpleTag: - return new MasterElement(id, sizeLength, dataSize); + case ElementIDs::MkAttachedFile: + return new MasterElement(id, sizeLength, dataSize, offset); case ElementIDs::MkTagName: case ElementIDs::MkTagString: + case ElementIDs::MkAttachedFileName: + case ElementIDs::MkAttachedFileDescription: return new UTF8StringElement(id, sizeLength, dataSize); case ElementIDs::MkTagLanguage: + case ElementIDs::MkAttachedFileMediaType: return new Latin1StringElement(id, sizeLength, dataSize); case ElementIDs::MkTagTargetTypeValue: + case ElementIDs::MkAttachedFileUID: return new UIntElement(id, sizeLength, dataSize); + case ElementIDs::MkAttachedFileData: + return new BinaryElement(id, sizeLength, dataSize); + default: return new Element(id, sizeLength, dataSize); } @@ -83,16 +97,16 @@ EBML::Element* EBML::Element::factory(File &file) EBML::Element::Id EBML::Element::readId(File &file) { auto buffer = file.readBlock(1); - if (buffer.size() != 1) { + if(buffer.size() != 1) { debug("Failed to read VINT size"); return 0; } unsigned int nb_bytes = VINTSizeLength<4>(*buffer.begin()); - if (!nb_bytes) + if(!nb_bytes) return 0; - if (nb_bytes > 1) + if(nb_bytes > 1) buffer.append(file.readBlock(nb_bytes - 1)); - if (buffer.size() != nb_bytes) { + if(buffer.size() != nb_bytes) { debug("Failed to read VINT data"); return 0; } diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 74efc3fd..3da66a8e 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -24,7 +24,6 @@ #include "tfile.h" #include "tutils.h" -//#include "ebmlutils.h" #include "taglib.h" namespace TagLib { @@ -37,7 +36,6 @@ namespace TagLib { : id(id), sizeLength(sizeLength), dataSize(dataSize) {} virtual ~Element() = default; - virtual bool isMaster() const { return false; } virtual bool read(File &file) { skipData(file); return true; @@ -59,22 +57,29 @@ namespace TagLib { }; 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; - inline constexpr Element::Id MkTagsTagLanguage = 0x447A; - inline constexpr Element::Id MkTagsLanguageDefault = 0x4484; + 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; + inline constexpr Element::Id MkTagsTagLanguage = 0x447A; + inline constexpr Element::Id MkTagsLanguageDefault = 0x4484; + inline constexpr Element::Id MkAttachments = 0x1941A469; + inline constexpr Element::Id MkAttachedFile = 0x61A7; + inline constexpr Element::Id MkAttachedFileDescription = 0x467E; + inline constexpr Element::Id MkAttachedFileName = 0x466E; + inline constexpr Element::Id MkAttachedFileMediaType = 0x4660; + inline constexpr Element::Id MkAttachedFileData = 0x465C; + inline constexpr Element::Id MkAttachedFileUID = 0x46AE; + } } } - #endif #endif diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp index b63da33a..aa632c97 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.cpp +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -30,7 +30,7 @@ using namespace TagLib; EBML::MasterElement::~MasterElement() { - for (auto element : elements) + for(auto element : elements) delete element; } @@ -38,8 +38,8 @@ bool EBML::MasterElement::read(File &file) { offset_t maxOffset = file.tell() + dataSize; Element *element = nullptr; - while ((element = findNextElement(file, maxOffset))) { - if (!element->read(file)) + while((element = findNextElement(file, maxOffset))) { + if(!element->read(file)) return false; elements.append(element); } @@ -50,7 +50,7 @@ ByteVector EBML::MasterElement::render() { ByteVector buffer = renderId(); ByteVector data; - for (auto element : elements) + for(auto element : elements) data.append(element->render()); dataSize = data.size(); buffer.append(renderVINT(dataSize, 0)); diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h index e08c54b2..d9809f65 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.h +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -33,15 +33,16 @@ namespace TagLib { class MasterElement : public Element { public: - MasterElement(Id id, int sizeLength, offset_t dataSize) - : Element(id, sizeLength, dataSize) + MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset) + : Element(id, sizeLength, dataSize), offset(offset) {} MasterElement(Id id) - : Element(id, 0, 0) + : Element(id, 0, 0), offset(0) {} ~MasterElement() override; - virtual bool isMaster() const override { return true; } - virtual bool read(File &file) override; + offset_t getOffset() const { return offset; } + offset_t getSize() const { return headSize() + dataSize; } + bool read(File &file) override; ByteVector render() override; void appendElement(Element *element) { elements.append(element); } List::Iterator begin () { return elements.begin(); } @@ -50,6 +51,7 @@ namespace TagLib { List::ConstIterator cend () const { return elements.cend(); } protected: + offset_t offset; List elements; }; diff --git a/taglib/matroska/ebml/ebmlmkattachments.cpp b/taglib/matroska/ebml/ebmlmkattachments.cpp new file mode 100644 index 00000000..cfa681b9 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkattachments.cpp @@ -0,0 +1,77 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmkattachments.h" +#include "ebmlstringelement.h" +#include "ebmluintelement.h" +#include "ebmlbinaryelement.h" +#include "matroskaattachments.h" +#include "matroskaattachedfile.h" +#include "tdebug.h" +#include "tutils.h" + +using namespace TagLib; + +Matroska::Attachments* EBML::MkAttachments::parse() +{ + auto attachments = new Matroska::Attachments(); + attachments->setOffset(offset); + attachments->setSize(getSize()); + + for(auto element : elements) { + if(element->getId() != ElementIDs::MkAttachedFile) + continue; + + const String *filename = nullptr; + const String *description = nullptr; + const String *mediaType = nullptr; + const ByteVector *data = nullptr; + Matroska::AttachedFile::UID uid = 0; + auto attachedFile = static_cast(element); + for(auto attachedFileChild : *attachedFile) { + Id id = attachedFileChild->getId(); + if(id == ElementIDs::MkAttachedFileName) + filename = &(static_cast(attachedFileChild)->getValue()); + else if(id == ElementIDs::MkAttachedFileData) + data = &(static_cast(attachedFileChild)->getValue()); + else if(id == ElementIDs::MkAttachedFileDescription) + description = &(static_cast(attachedFileChild)->getValue()); + else if(id == ElementIDs::MkAttachedFileMediaType) + mediaType = &(static_cast(attachedFileChild)->getValue()); + else if(id == ElementIDs::MkAttachedFileUID) + uid = static_cast(attachedFileChild)->getValue(); + } + if(!(filename && data)) + continue; + + auto file = new Matroska::AttachedFile(); + file->setFileName(*filename); + file->setData(*data); + if(description) + file->setDescription(*description); + if(mediaType) + file->setMediaType(*mediaType); + if(uid) + file->setUID(uid); + + attachments->addAttachedFile(file); + } + return attachments; +} diff --git a/taglib/matroska/ebml/ebmlmkattachments.h b/taglib/matroska/ebml/ebmlmkattachments.h new file mode 100644 index 00000000..dcdcc71e --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkattachments.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + +#ifndef TAGLIB_EBMLMKATTACHMENTS_H +#define TAGLIB_EBMLMKATTACHMENTS_H +#ifndef DO_NOT_DOCUMENT + +namespace TagLib { + namespace Matroska { + class Attachments; + } + namespace EBML { + class MkAttachments : public MasterElement + { + public: + MkAttachments(int sizeLength, offset_t dataSize, offset_t offset) + : MasterElement(ElementIDs::MkAttachments, sizeLength, dataSize, offset) + {} + MkAttachments() + : MasterElement(ElementIDs::MkAttachments, 0, 0, 0) + {} + Matroska::Attachments* parse(); + + }; + } +} +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index 8cbbd0a6..bfbd2182 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -20,9 +20,11 @@ #include "ebmlmksegment.h" #include "ebmlmktags.h" +#include "ebmlmkattachments.h" #include "ebmlutils.h" #include "matroskafile.h" #include "matroskatag.h" +#include "matroskaattachments.h" #include "tutils.h" #include "tbytevector.h" #include "tdebug.h" @@ -32,26 +34,40 @@ using namespace TagLib; EBML::MkSegment::~MkSegment() { delete tags; + delete attachments; } bool EBML::MkSegment::read(File &file) { offset_t maxOffset = file.tell() + dataSize; - 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; + EBML::Element *element = nullptr; + + while((element = findNextElement(file, maxOffset))) { + Id id = element->getId(); + if(id == ElementIDs::MkTags) { + tags = static_cast(element); + if(!tags->read(file)) + return false; + } + else if(id == ElementIDs::MkAttachments) { + attachments = static_cast(element); + if(!attachments->read(file)) + return false; + } + else { + element->skipData(file); + delete element; + } } return true; } -std::tuple EBML::MkSegment::parseTag() +Matroska::Tag* EBML::MkSegment::parseTag() { - if (tags) - return {tags->parse(), tagsOffset, tagsOriginalSize}; - else - return {nullptr, 0, 0}; + return tags ? tags->parse() : nullptr; +} + +Matroska::Attachments* EBML::MkSegment::parseAttachments() +{ + return attachments ? attachments->parse() : nullptr; } diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index b3a0a256..0cbdb51e 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -29,24 +29,25 @@ namespace TagLib { namespace Matroska { class Tag; + class Attachments; } namespace EBML { - class Tags; class MkTags; + class MkAttachments; class MkSegment : public MasterElement { public: - MkSegment(int sizeLength, offset_t dataSize) - : MasterElement(ElementIDs::MkSegment, sizeLength, dataSize) + MkSegment(int sizeLength, offset_t dataSize, offset_t offset) + : MasterElement(ElementIDs::MkSegment, sizeLength, dataSize, offset) {} ~MkSegment() override; bool read(File &file) override; - std::tuple parseTag(); + Matroska::Tag* parseTag(); + Matroska::Attachments* parseAttachments(); private: MkTags *tags = nullptr; - offset_t tagsOffset = 0; - offset_t tagsOriginalSize = 0; + MkAttachments *attachments = nullptr; }; } diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index bfc3e606..20ec482c 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -34,30 +34,32 @@ using namespace TagLib; Matroska::Tag* EBML::MkTags::parse() { auto mTag = new Matroska::Tag(); + mTag->setOffset(offset); + mTag->setSize(getSize()); // Loop through each element - for (auto tagsChild : elements) { - if (tagsChild->getId() != ElementIDs::MkTag) + for(auto tagsChild : elements) { + if(tagsChild->getId() != ElementIDs::MkTag) continue; auto tag = static_cast(tagsChild); List simpleTags; MasterElement *targets = nullptr; // Identify the element and the elements - for (auto tagChild : *tag) { + for(auto tagChild : *tag) { Id tagChildId = tagChild->getId(); - if (!targets && tagChildId == ElementIDs::MkTagTargets) + if(!targets && tagChildId == ElementIDs::MkTagTargets) targets = static_cast(tagChild); - else if (tagChildId == ElementIDs::MkSimpleTag) + else if(tagChildId == ElementIDs::MkSimpleTag) simpleTags.append(static_cast(tagChild)); } // Parse the element Matroska::SimpleTag::TargetTypeValue targetTypeValue = Matroska::SimpleTag::TargetTypeValue::None; - if (targets) { - for (auto targetsChild : *targets) { + if(targets) { + for(auto targetsChild : *targets) { Id id = targetsChild->getId(); - if (id == ElementIDs::MkTagTargetTypeValue + if(id == ElementIDs::MkTagTargetTypeValue && targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) { targetTypeValue = static_cast( static_cast(targetsChild)->getValue() @@ -67,43 +69,43 @@ Matroska::Tag* EBML::MkTags::parse() } // Parse each - for (auto simpleTag : simpleTags) { + for(auto simpleTag : simpleTags) { const String *tagName = nullptr; const String *tagValueString = nullptr; const ByteVector *tagValueBinary = nullptr; const String *language = nullptr; bool defaultLanguageFlag = true; - for (auto simpleTagChild : *simpleTag) { + for(auto simpleTagChild : *simpleTag) { Id id = simpleTagChild->getId(); - if (id == ElementIDs::MkTagName && !tagName) + if(id == ElementIDs::MkTagName && !tagName) tagName = &(static_cast(simpleTagChild)->getValue()); - else if (id == ElementIDs::MkTagString && !tagValueString) + else if(id == ElementIDs::MkTagString && !tagValueString) tagValueString = &(static_cast(simpleTagChild)->getValue()); - else if (id == ElementIDs::MkTagsTagLanguage && !language) + else if(id == ElementIDs::MkTagsTagLanguage && !language) language = &(static_cast(simpleTagChild)->getValue()); - else if (id == ElementIDs::MkTagsLanguageDefault) + else if(id == ElementIDs::MkTagsLanguageDefault) defaultLanguageFlag = static_cast(simpleTagChild)->getValue() ? true : false; } - if (!tagName || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) + if(!tagName || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) continue; // Create a Simple Tag object and add it to the Tag object Matroska::SimpleTag *sTag = nullptr; - if (tagValueString) { + if(tagValueString) { auto sTagString = new Matroska::SimpleTagString(); sTagString->setTargetTypeValue(targetTypeValue); sTagString->setValue(*tagValueString); sTag = sTagString; } - else if (tagValueBinary) { + else if(tagValueBinary) { auto sTagBinary = new Matroska::SimpleTagBinary(); sTagBinary->setTargetTypeValue(targetTypeValue); sTagBinary->setValue(*tagValueBinary); sTag = sTagBinary; } sTag->setName(*tagName); - if (language) + if(language) sTag->setLanguage(*language); sTag->setDefaultLanguageFlag(defaultLanguageFlag); mTag->addSimpleTag(sTag); diff --git a/taglib/matroska/ebml/ebmlmktags.h b/taglib/matroska/ebml/ebmlmktags.h index d8447273..a2b550e2 100644 --- a/taglib/matroska/ebml/ebmlmktags.h +++ b/taglib/matroska/ebml/ebmlmktags.h @@ -35,11 +35,11 @@ namespace TagLib { class MkTags : public MasterElement { public: - MkTags(int sizeLength, offset_t dataSize) - : MasterElement(ElementIDs::MkTags, sizeLength, dataSize) + MkTags(int sizeLength, offset_t dataSize, offset_t offset) + : MasterElement(ElementIDs::MkTags, sizeLength, dataSize, offset) {} MkTags() - : MasterElement(ElementIDs::MkTags, 0, 0) + : MasterElement(ElementIDs::MkTags, 0, 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 475d0abf..f7bb4430 100644 --- a/taglib/matroska/ebml/ebmlstringelement.cpp +++ b/taglib/matroska/ebml/ebmlstringelement.cpp @@ -31,15 +31,15 @@ template bool EBML::StringElement::read(TagLib::File &file) { ByteVector buffer = file.readBlock(dataSize); - if (buffer.size() != dataSize) { + if(buffer.size() != dataSize) { debug("Failed to read string"); return false; } // The EBML strings aren't supposed to be null-terminated, - // but we'll check for it and stip the null terminator if found + // but we'll check for it and strip the null terminator if found int nullByte = buffer.find('\0'); - if (nullByte >= 0) + if(nullByte >= 0) buffer = ByteVector(buffer.data(), nullByte); value = String(buffer, t); return true; diff --git a/taglib/matroska/ebml/ebmluintelement.cpp b/taglib/matroska/ebml/ebmluintelement.cpp index 2197a3d2..2c84ddff 100644 --- a/taglib/matroska/ebml/ebmluintelement.cpp +++ b/taglib/matroska/ebml/ebmluintelement.cpp @@ -29,22 +29,42 @@ using namespace TagLib; bool EBML::UIntElement::read(TagLib::File &file) { ByteVector buffer = file.readBlock(dataSize); - if (buffer.size() != dataSize) { + if(buffer.size() != dataSize) { debug("Failed to read EBML Uint element"); return false; } - value = buffer.toLongLong(true); + value = buffer.toULongLong(true); return true; } ByteVector EBML::UIntElement::render() { + int dataSize = 0; + if(value <= 0xFFull) + dataSize = 1; + else if(value <= 0xFFFFull) + dataSize = 2; + else if(value <= 0xFFFFFFull) + dataSize = 3; + else if(value <= 0xFFFFFFFFull) + dataSize = 4; + else if(value <= 0xFFFFFFFFFFull) + dataSize = 5; + else if(value <= 0xFFFFFFFFFFFFull) + dataSize = 6; + else if(value <= 0xFFFFFFFFFFFFFFull) + dataSize = 7; + else if(value <= 0xFFFFFFFFFFFFFFFFull) + dataSize = 8; + ByteVector buffer = renderId(); - dataSize = minSize(value); + //dataSize = minSize(value); buffer.append(renderVINT(dataSize, 0)); - uint64_t value = this->value; + unsigned long long value = this->value; + //debug(Utils::formatString("Writing %llu", value)); + static const auto byteOrder = Utils::systemByteOrder(); - if (byteOrder == Utils::LittleEndian) + if(byteOrder == Utils::LittleEndian) value = Utils::byteSwap((unsigned long long) value); buffer.append(ByteVector((char*) &value + (sizeof(value) - dataSize), dataSize)); diff --git a/taglib/matroska/ebml/ebmluintelement.h b/taglib/matroska/ebml/ebmluintelement.h index 0af32b7c..e1d83b5a 100644 --- a/taglib/matroska/ebml/ebmluintelement.h +++ b/taglib/matroska/ebml/ebmluintelement.h @@ -38,13 +38,13 @@ namespace TagLib { UIntElement(Id id) : UIntElement(id, 0, 0) {} - unsigned int getValue() const { return value; } - void setValue(unsigned int value) { this->value = value; } + unsigned long long getValue() const { return value; } + void setValue(unsigned long long value) { this->value = value; } bool read(File &file) override; ByteVector render() override; private: - uint64_t value = 0; + unsigned long long value = 0; //protected: diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index 05290726..cc898aef 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -18,6 +18,7 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include #include "ebmlutils.h" #include "ebmlelement.h" #include "tbytevector.h" @@ -32,9 +33,9 @@ using namespace TagLib; EBML::Element* EBML::findElement(File &file, EBML::Element::Id id, offset_t maxOffset) { Element *element = nullptr; - while (file.tell() < maxOffset) { + while(file.tell() < maxOffset) { element = Element::factory(file); - if (!element || element->getId() == id) + if(!element || element->getId() == id) return element; element->skipData(file); delete element; @@ -53,15 +54,15 @@ std::pair EBML::readVINT(File &file) { static_assert(sizeof(T) == 8); auto buffer = file.readBlock(1); - if (buffer.size() != 1) { + if(buffer.size() != 1) { debug("Failed to read VINT size"); return {0, 0}; } unsigned int nb_bytes = VINTSizeLength<8>(*buffer.begin()); - if (!nb_bytes) + if(!nb_bytes) return {0, 0}; - if (nb_bytes > 1) + if(nb_bytes > 1) buffer.append(file.readBlock(nb_bytes - 1)); int bits_to_shift = (sizeof(T) * 8) - (7 * nb_bytes); offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; @@ -75,11 +76,11 @@ namespace TagLib::EBML { template std::pair EBML::parseVINT(const ByteVector &buffer) { - if (buffer.isEmpty()) + if(buffer.isEmpty()) return {0, 0}; unsigned int numBytes = VINTSizeLength<8>(*buffer.begin()); - if (!numBytes) + if(!numBytes) return {0, 0}; int bits_to_shift = (sizeof(T) * 8) - (7 * numBytes); @@ -96,7 +97,15 @@ 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) + if(byteOrder == Utils::LittleEndian) number = Utils::byteSwap(static_cast(number)); return ByteVector((char*) &number + (sizeof(number) - numBytes), numBytes); } + +unsigned long long EBML::randomUID() +{ + static std::random_device device; + static std::mt19937 generator(device()); + static std::uniform_int_distribution distribution; + return distribution(generator); +} diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h index 8f6f5a87..ad5b6e2b 100644 --- a/taglib/matroska/ebml/ebmlutils.h +++ b/taglib/matroska/ebml/ebmlutils.h @@ -52,24 +52,23 @@ namespace TagLib { constexpr unsigned int VINTSizeLength(uint8_t firstByte) { static_assert(maxSizeLength >= 1 && maxSizeLength <= 8); - if (!firstByte) { + if(!firstByte) { debug("VINT with greater than 8 bytes not allowed"); return 0; } uint8_t mask = 0b10000000; unsigned int numBytes = 1; - while (!(mask & firstByte)) { + while(!(mask & firstByte)) { numBytes++; mask >>= 1; } - if (numBytes > maxSizeLength) { + if(numBytes > maxSizeLength) { debug(Utils::formatString("VINT size length exceeds %i bytes", maxSizeLength)); return 0; } return numBytes; } - //Id readId(File &file); template std::pair readVINT(File &file); template @@ -77,18 +76,19 @@ namespace TagLib { Element* findElement(File &file, Element::Id id, offset_t maxLength); Element* findNextElement(File &file, offset_t maxOffset); ByteVector renderVINT(uint64_t number, int minSizeLength); + unsigned long long randomUID(); constexpr int minSize(uint64_t data) { - if (data <= 0x7Fu) + if(data <= 0x7Fu) return 1; - else if (data <= 0x3FFFu) + else if(data <= 0x3FFFu) return 2; - else if (data <= 0x1FFFFFu) + else if(data <= 0x1FFFFFu) return 3; - else if (data <= 0xFFFFFFFu) + else if(data <= 0xFFFFFFFu) return 4; - else if (data <= 0x7FFFFFFFFu) + else if(data <= 0x7FFFFFFFFu) return 5; else return 0; @@ -96,11 +96,11 @@ namespace TagLib { constexpr int idSize(Element::Id id) { - if (id <= 0xFF) + if(id <= 0xFF) return 1; - else if (id <= 0xFFFF) + else if(id <= 0xFFFF) return 2; - else if (id <= 0xFFFFFF) + else if(id <= 0xFFFFFF) return 3; else return 4; diff --git a/taglib/matroska/matroskaattachedfile.cpp b/taglib/matroska/matroskaattachedfile.cpp new file mode 100644 index 00000000..78f8eadd --- /dev/null +++ b/taglib/matroska/matroskaattachedfile.cpp @@ -0,0 +1,77 @@ +#include +#include "matroskaattachedfile.h" +#include "tstring.h" +#include "tbytevector.h" + +using namespace TagLib; + +class Matroska::AttachedFile::AttachedFilePrivate +{ +public: + AttachedFilePrivate() {} + ~AttachedFilePrivate() = default; + AttachedFilePrivate(const AttachedFilePrivate &) = delete; + AttachedFilePrivate &operator=(const AttachedFilePrivate &) = delete; + String fileName; + String description; + String mediaType; + ByteVector data; + UID uid = 0; +}; + +Matroska::AttachedFile::AttachedFile() +: d(std::make_unique()) +{ + +} +Matroska::AttachedFile::~AttachedFile() = default; + +void Matroska::AttachedFile::setFileName(const String &fileName) +{ + d->fileName = fileName; +} + +const String& Matroska::AttachedFile::fileName() const +{ + return d->fileName; +} + +void Matroska::AttachedFile::setDescription(const String &description) +{ + d->description = description; +} + +const String& Matroska::AttachedFile::description() const +{ + return d->description; +} + +void Matroska::AttachedFile::setMediaType(const String &mediaType) +{ + d->mediaType = mediaType; +} + +const String& Matroska::AttachedFile::mediaType() const +{ + return d->mediaType; +} + +void Matroska::AttachedFile::setData(const ByteVector &data) +{ + d->data = data; +} + +const ByteVector& Matroska::AttachedFile::data() const +{ + return d->data; +} + +void Matroska::AttachedFile::setUID(UID uid) +{ + d->uid = uid; +} + +Matroska::AttachedFile::UID Matroska::AttachedFile::uid() const +{ + return d->uid; +} diff --git a/taglib/matroska/matroskaattachedfile.h b/taglib/matroska/matroskaattachedfile.h new file mode 100644 index 00000000..acb62839 --- /dev/null +++ b/taglib/matroska/matroskaattachedfile.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef HAS_MATROSKAATTACHEDFILE_H +#define HAS_MATROSKAATTACHEDFILE_H + +#include "taglib_export.h" + + +namespace TagLib { + class String; + class ByteVector; + namespace Matroska { + class TAGLIB_EXPORT AttachedFile + { + public: + using UID = unsigned long long; + AttachedFile(); + ~AttachedFile(); + + void setFileName(const String &fileName); + const String& fileName() const; + void setDescription(const String &description); + const String& description() const; + void setMediaType(const String &mediaType); + const String& mediaType() const; + void setData(const ByteVector &data); + const ByteVector& data() const; + void setUID(UID uid); + UID uid() const; + + private: + class AttachedFilePrivate; + std::unique_ptr d; + + }; + } +} + +#endif \ No newline at end of file diff --git a/taglib/matroska/matroskaattachments.cpp b/taglib/matroska/matroskaattachments.cpp new file mode 100644 index 00000000..9504e86d --- /dev/null +++ b/taglib/matroska/matroskaattachments.cpp @@ -0,0 +1,97 @@ +#include +#include "matroskaattachments.h" +#include "matroskaattachedfile.h" +#include "ebmlmkattachments.h" +#include "ebmlmasterelement.h" +#include "ebmlstringelement.h" +#include "ebmlbinaryelement.h" +#include "ebmluintelement.h" +#include "tlist.h" +#include "tbytevector.h" + +using namespace TagLib; + +class Matroska::Attachments::AttachmentsPrivate +{ +public: + AttachmentsPrivate() {} + ~AttachmentsPrivate() = default; + AttachmentsPrivate(const AttachmentsPrivate &) = delete; + AttachmentsPrivate &operator=(const AttachmentsPrivate &) = delete; + List files; + +}; + +Matroska::Attachments::Attachments() +: d(std::make_unique()) +{ + d->files.setAutoDelete(true); +} +Matroska::Attachments::~Attachments() = default; + +void Matroska::Attachments::addAttachedFile(AttachedFile *file) +{ + d->files.append(file); +} + +void Matroska::Attachments::removeAttachedFile(AttachedFile *file) +{ + auto it = d->files.find(file); + if(it != d->files.end()) { + delete *it; + d->files.erase(it); + } +} + +void Matroska::Attachments::clear() +{ + d->files.clear(); +} + +const Matroska::Attachments::AttachedFileList& Matroska::Attachments::attachedFileList() const +{ + return d->files; +} + +ByteVector Matroska::Attachments::render() +{ + EBML::MkAttachments attachments; + for(const auto attachedFile : d->files) { + auto attachedFileElement = new EBML::MasterElement(EBML::ElementIDs::MkAttachedFile); + + // Filename + auto fileNameElement = new EBML::UTF8StringElement(EBML::ElementIDs::MkAttachedFileName); + fileNameElement->setValue(attachedFile->fileName()); + attachedFileElement->appendElement(fileNameElement); + + // Media/MIME type + auto mediaTypeElement = new EBML::Latin1StringElement(EBML::ElementIDs::MkAttachedFileMediaType); + mediaTypeElement->setValue(attachedFile->mediaType()); + attachedFileElement->appendElement(mediaTypeElement); + + // Description + const String &description = attachedFile->description(); + if(!description.isEmpty()) { + auto descriptionElement = new EBML::UTF8StringElement(EBML::ElementIDs::MkAttachedFileDescription); + descriptionElement->setValue(description); + attachedFileElement->appendElement(descriptionElement); + } + + // Data + auto dataElement = new EBML::BinaryElement(EBML::ElementIDs::MkAttachedFileData); + dataElement->setValue(attachedFile->data()); + attachedFileElement->appendElement(dataElement); + + // UID + auto uidElement = new EBML::UIntElement(EBML::ElementIDs::MkAttachedFileUID); + AttachedFile::UID uid = attachedFile->uid(); + if(!uid) + uid = EBML::randomUID(); + uidElement->setValue(uid); + attachedFileElement->appendElement(uidElement); + + attachments.appendElement(attachedFileElement); + } + + return attachments.render(); +} diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h new file mode 100644 index 00000000..84ba1332 --- /dev/null +++ b/taglib/matroska/matroskaattachments.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef HAS_MATROSKAATTACHMENTS_H +#define HAS_MATROSKAATTACHMENTS_H + +#include +#include "taglib_export.h" +#include "tlist.h" +#include "matroskaelement.h" + + +namespace TagLib { + namespace EBML { + class MkAttachments; + } + namespace Matroska { + class AttachedFile; + class File; + class TAGLIB_EXPORT Attachments : private Element + { + public: + using AttachedFileList = List; + Attachments(); + virtual ~Attachments(); + + void addAttachedFile(AttachedFile *file); + void removeAttachedFile(AttachedFile *file); + void clear(); + const AttachedFileList& attachedFileList() const; + ByteVector render() override; + + private: + friend class EBML::MkAttachments; + friend class Matroska::File; + class AttachmentsPrivate; + std::unique_ptr d; + + }; + } +} + +#endif \ No newline at end of file diff --git a/taglib/matroska/matroskaelement.cpp b/taglib/matroska/matroskaelement.cpp new file mode 100644 index 00000000..43503734 --- /dev/null +++ b/taglib/matroska/matroskaelement.cpp @@ -0,0 +1,44 @@ +#include +#include "matroskaelement.h" +#include "tlist.h" +#include "tbytevector.h" + +using namespace TagLib; + +class Matroska::Element::ElementPrivate +{ +public: + ElementPrivate() {} + ~ElementPrivate() = default; + ElementPrivate(const ElementPrivate &) = delete; + ElementPrivate &operator=(const ElementPrivate &) = delete; + offset_t size = 0; + offset_t offset = 0; + +}; + +Matroska::Element::Element() +: e(std::make_unique()) +{ +} +Matroska::Element::~Element() = default; + +offset_t Matroska::Element::size() const +{ + return e->size; +} + +offset_t Matroska::Element::offset() const +{ + return e->offset; +} + +void Matroska::Element::setOffset(offset_t offset) +{ + e->offset = offset; +} + +void Matroska::Element::setSize(offset_t size) +{ + e->size = size; +} \ No newline at end of file diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h new file mode 100644 index 00000000..16d4a142 --- /dev/null +++ b/taglib/matroska/matroskaelement.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef HAS_MATROSKAELEMENT_H +#define HAS_MATROSKAELEMENT_H + +#include +#include "taglib_export.h" +#include "tutils.h" +#include "tbytevector.h" + + +namespace TagLib { + namespace Matroska { + class TAGLIB_EXPORT Element + { + public: + Element(); + virtual ~Element(); + virtual ByteVector render() = 0; + offset_t size() const; + offset_t offset() const; + void setOffset(offset_t offset); + void setSize(offset_t size); + + private: + class ElementPrivate; + std::unique_ptr e; + + }; + } +} + + #endif \ No newline at end of file diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index ebbf7fbd..83b12dd0 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -20,6 +20,7 @@ #include "matroskafile.h" #include "matroskatag.h" +#include "matroskaattachments.h" #include "ebmlutils.h" #include "ebmlelement.h" #include "ebmlmksegment.h" @@ -28,6 +29,8 @@ #include "tutils.h" #include +#include +#include using namespace TagLib; @@ -35,29 +38,27 @@ class Matroska::File::FilePrivate { public: FilePrivate() {} - ~FilePrivate() { delete tag; + delete attachments; } FilePrivate(const FilePrivate &) = delete; FilePrivate &operator=(const FilePrivate &) = delete; Matroska::Tag *tag = nullptr; - offset_t tagsOffset = 0; - offset_t tagsOriginalSize = 0; + Matroska::Attachments *attachments = nullptr; offset_t segmentSizeOffset = 0; offset_t segmentSizeLength = 0; offset_t segmentDataSize = 0; - //Properties *properties = nullptr; }; Matroska::File::File(FileName file, bool readProperties) : TagLib::File(file), d(std::make_unique()) { - if (!isOpen()) { + if(!isOpen()) { debug("Failed to open matroska file"); setValid(false); return; @@ -68,7 +69,7 @@ Matroska::File::File(IOStream *stream, bool readProperties) : TagLib::File(stream), d(std::make_unique()) { - if (!isOpen()) { + if(!isOpen()) { debug("Failed to open matroska file"); setValid(false); return; @@ -84,22 +85,33 @@ TagLib::Tag* Matroska::File::tag() const Matroska::Tag* Matroska::File::tag(bool create) const { - if (d->tag) + if(d->tag) return d->tag; else { - if (create) + if(create) d->tag = new Matroska::Tag(); return d->tag; } } +Matroska::Attachments* Matroska::File::attachments(bool create) const +{ + if(d->attachments) + return d->attachments; + else { + if(create) + d->attachments = new Attachments(); + return d->attachments; + } +} + void Matroska::File::read(bool readProperties) { offset_t fileLength = length(); // Find the EBML Header std::unique_ptr head(EBML::Element::factory(*this)); - if (!head || head->getId() != EBML::ElementIDs::EBMLHeader) { + if(!head || head->getId() != EBML::ElementIDs::EBMLHeader) { debug("Failed to find EBML head"); setValid(false); return; @@ -112,7 +124,7 @@ void Matroska::File::read(bool readProperties) EBML::findElement(*this, EBML::ElementIDs::MkSegment, fileLength - tell()) ) ); - if (!segment) { + if(!segment) { debug("Failed to find Matroska segment"); setValid(false); return; @@ -122,32 +134,103 @@ void Matroska::File::read(bool readProperties) d->segmentDataSize = segment->getDataSize(); // Read the segment into memory from file - if (!segment->read(*this)) { + if(!segment->read(*this)) { debug("Failed to read segment"); setValid(false); return; } // Parse the tag - const auto& [tag, tagsOffset, tagsOriginalSize] = segment->parseTag(); - d->tag = tag; - d->tagsOffset = tagsOffset; - d->tagsOriginalSize = tagsOriginalSize; + d->tag = segment->parseTag(); + // Parse the attachments + d->attachments = segment->parseAttachments(); + + setValid(true); } bool Matroska::File::save() { - if (d->tag) { + std::vector renderListExisting; + std::vector renderListNew; + offset_t newSegmentDataSize = d->segmentDataSize; + + + if(d->tag) { + if(d->tag->size()) + renderListExisting.push_back(d->tag); + else + renderListNew.push_back(d->tag); + } + + if(d->attachments) { + //d->attachments->setOffset(d->tag->offset()); + //renderListExisting.push_back(d->attachments); + if(d->attachments->size()) + renderListExisting.push_back(d->attachments); + else + renderListNew.push_back(d->attachments); + + + } + + + // Render from end to beginning so we don't have to shift + // the file offsets + std::sort(renderListExisting.begin(), + renderListExisting.end(), + [](auto a, auto b) { return a->offset() > b->offset(); } + ); + + // Overwrite existing elements + for(auto element : renderListExisting) { + offset_t offset = element->offset(); + offset_t originalSize = element->size(); + ByteVector data = element->render(); + insert(data, offset, originalSize); + newSegmentDataSize += (data.size() - originalSize); + } + + // Add new elements to the end of file + for(auto element : renderListNew) { + offset_t offset = length(); + ByteVector data = element->render(); + insert(data, offset, 0); + newSegmentDataSize += data.size(); + } + + // Write the new segment data size + if(newSegmentDataSize != d->segmentDataSize) { + auto segmentDataSizeBuffer = EBML::renderVINT(newSegmentDataSize, d->segmentSizeLength); + insert(segmentDataSizeBuffer, d->segmentSizeOffset, d->segmentSizeLength); + d->segmentDataSize = newSegmentDataSize; + } + +/* + auto&& renderElements [&d->segmentDataSize](Element *element) { + auto offset = element->offset(); + if(!offset) + } + + if(d->tag) { ByteVector tag = d->tag->render(); - if (!d->tagsOriginalSize) { - d->tagsOffset = d->segmentSizeOffset + d->segmentSizeLength + d->segmentDataSize; + auto tagsOriginalSize = d->tag->size(); + auto tagsOffset = d->tag->offset(); + + if(!tagsOriginalSize) { + tagsOffset = d->segmentSizeOffset + d->segmentSizeLength + d->segmentDataSize; } - insert(tag, d->tagsOffset, d->tagsOriginalSize); - d->segmentDataSize += (tag.size() - d->tagsOriginalSize); + insert(tag, tagsOffset, tagsOriginalSize); + d->segmentDataSize += (tag.size() - tagsOriginalSize); auto segmentDataSizeBuffer = EBML::renderVINT(d->segmentDataSize, d->segmentSizeLength); insert(segmentDataSizeBuffer, d->segmentSizeOffset, d->segmentSizeLength); - } + */ + /* + if(d->attachments) { + ByteVector attachments = d->attachments->render(); + } + */ + return true; } diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index 69550c16..a729cfa3 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -35,6 +35,7 @@ namespace TagLib { namespace Matroska { class Properties; class Tag; + class Attachments; class TAGLIB_EXPORT File : public TagLib::File { public: @@ -45,6 +46,7 @@ namespace TagLib { File &operator=(const File &) = delete; AudioProperties *audioProperties() const override { return nullptr; } TagLib::Tag *tag() const override; + Attachments* attachments(bool create = false) const; Matroska::Tag *tag(bool create) const; bool save() override; //PropertyMap properties() const override { return PropertyMap(); } diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 31113faf..b03a6f2f 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -69,8 +69,10 @@ void Matroska::Tag::addSimpleTag(SimpleTag *tag) void Matroska::Tag::removeSimpleTag(SimpleTag *tag) { auto it = d->tags.find(tag); - if (it != d->tags.end()) + if(it != d->tags.end()) { + delete *it; d->tags.erase(it); + } } void Matroska::Tag::clearSimpleTags() @@ -161,7 +163,7 @@ String Matroska::Tag::genre() const unsigned int Matroska::Tag::year() const { auto value = getTag("DATE"); - if (!value) + if(!value) return 0; auto list = value->split("-"); return static_cast(list.front().toInt()); @@ -170,7 +172,7 @@ unsigned int Matroska::Tag::year() const unsigned int Matroska::Tag::track() const { auto value = getTag("TRACKNUMBER"); - if (!value) + if(!value) return 0; auto list = value->split("-"); return static_cast(list.front().toInt()); @@ -188,7 +190,7 @@ ByteVector Matroska::Tag::render() targetList.setAutoDelete(true); // Build target-based list - for (auto tag : d->tags) { + for(auto tag : d->tags) { auto targetTypeValue = tag->targetTypeValue(); auto it = std::find_if(targetList.begin(), targetList.end(), @@ -197,7 +199,7 @@ ByteVector Matroska::Tag::render() return simpleTag->targetTypeValue() == targetTypeValue; } ); - if (it == targetList.end()) { + if(it == targetList.end()) { auto list = new List(); list->append(tag); targetList.append(list); @@ -205,14 +207,14 @@ ByteVector Matroska::Tag::render() else (*it)->append(tag); } - for (auto list : targetList) { + for(auto list : targetList) { auto frontTag = list->front(); auto targetTypeValue = frontTag->targetTypeValue(); auto tag = new EBML::MasterElement(EBML::ElementIDs::MkTag); // Build element auto targets = new EBML::MasterElement(EBML::ElementIDs::MkTagTargets); - if (targetTypeValue != Matroska::SimpleTag::TargetTypeValue::None) { + if(targetTypeValue != Matroska::SimpleTag::TargetTypeValue::None) { auto element = new EBML::UIntElement(EBML::ElementIDs::MkTagTargetTypeValue); element->setValue(static_cast(targetTypeValue)); targets->appendElement(element); @@ -220,7 +222,7 @@ ByteVector Matroska::Tag::render() tag->appendElement(targets); // Build element - for (auto simpleTag : *list) { + for(auto simpleTag : *list) { auto t = new EBML::MasterElement(EBML::ElementIDs::MkSimpleTag); auto tagName = new EBML::UTF8StringElement(EBML::ElementIDs::MkTagName); tagName->setValue(simpleTag->name()); @@ -299,7 +301,7 @@ bool Matroska::Tag::setTag(const String &key, const String &value) // Workaround Clang issue - no lambda capture of structured bindings const String &name = pair.first; auto targetTypeValue = pair.second; - if (name.isEmpty()) + if(name.isEmpty()) return false; removeSimpleTags( [&name, targetTypeValue] (auto t) { @@ -307,7 +309,7 @@ bool Matroska::Tag::setTag(const String &key, const String &value) && t->targetTypeValue() == targetTypeValue; } ); - if (!value.isEmpty()) { + if(!value.isEmpty()) { auto t = new Matroska::SimpleTagString(); t->setTargetTypeValue(targetTypeValue); t->setName(name); @@ -323,7 +325,7 @@ const String* Matroska::Tag::getTag(const String &key) const // Workaround Clang issue - no lambda capture of structured bindings const String &name = pair.first; auto targetTypeValue = pair.second; - if (name.isEmpty()) + if(name.isEmpty()) return nullptr; auto tag = dynamic_cast( findSimpleTag( @@ -342,7 +344,7 @@ std::pair Matroska::Utils::transla simpleTagsTranslation.cend(), [&key](const auto &t) { return key == std::get<0>(t); } ); - if (it != simpleTagsTranslation.end()) + if(it != simpleTagsTranslation.end()) return { std::get<1>(*it), std::get<2>(*it) }; else return { String(), Matroska::SimpleTag::TargetTypeValue::None }; @@ -363,8 +365,8 @@ String Matroska::Utils::translateTag(const String &name, Matroska::SimpleTag::Ta PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) { PropertyMap unsupportedProperties; - for (const auto& [key, value] : propertyMap) { - if (!setTag(key, value.toString())) + for(const auto& [key, value] : propertyMap) { + if(!setTag(key, value.toString())) unsupportedProperties[key] = value; } return unsupportedProperties; @@ -374,10 +376,10 @@ PropertyMap Matroska::Tag::properties() const { PropertyMap properties; Matroska::SimpleTagString *tStr = nullptr; - for (auto simpleTag : d->tags) { - if ((tStr = dynamic_cast(simpleTag))) { + 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)) + if(!key.isEmpty() && !properties.contains(key)) properties[key] = tStr->value(); } } diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 8e0f474f..f352195f 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -29,12 +29,17 @@ #include "tstring.h" #include "tlist.h" #include "matroskafile.h" +#include "matroskaelement.h" #include "matroskasimpletag.h" namespace TagLib { + namespace EBML { + class MkTags; + } + namespace Matroska { using SimpleTagsList = List; - class TAGLIB_EXPORT Tag : public TagLib::Tag + class TAGLIB_EXPORT Tag : public TagLib::Tag, private Element { public: Tag(); @@ -58,6 +63,7 @@ namespace TagLib { void setYear(unsigned int i) override; void setTrack(unsigned int i) override; bool isEmpty() const override; + ByteVector render() override; PropertyMap properties() const override; PropertyMap setProperties(const PropertyMap &propertyMap) override; template @@ -65,9 +71,9 @@ namespace TagLib { { auto &list = simpleTagsListPrivate(); int numRemoved = 0; - for (auto it = list.begin(); it != list.end();) { + for(auto it = list.begin(); it != list.end();) { it = std::find_if(it, list.end(), std::forward(p)); - if (it != list.end()) { + if(it != list.end()) { delete *it; *it = nullptr; it = list.erase(it); @@ -81,15 +87,16 @@ namespace TagLib { SimpleTagsList findSimpleTags(T&& p) { auto &list = simpleTagsListPrivate(); - for (auto it = list.begin(); it != list.end();) { + for(auto it = list.begin(); it != list.end();) { it = std::find_if(it, list.end(), std::forward(p)); - if (it != list.end()) { + if(it != list.end()) { list.append(*it); ++it; } } return list; } + template const Matroska::SimpleTag* findSimpleTag(T&& p) const { @@ -97,6 +104,7 @@ namespace TagLib { auto it = std::find_if(list.begin(), list.end(), std::forward(p)); return it != list.end() ? *it : nullptr; } + template Matroska::SimpleTag* findSimpleTag(T&&p) { @@ -107,7 +115,7 @@ namespace TagLib { private: friend class Matroska::File; - ByteVector render(); + friend class EBML::MkTags; SimpleTagsList& simpleTagsListPrivate(); const SimpleTagsList& simpleTagsListPrivate() const; bool setTag(const String &key, const String &value); From 975eaac3ca7f295defa65d23315376970b96bf9a Mon Sep 17 00:00:00 2001 From: complexlogic Date: Sun, 22 Oct 2023 16:56:01 -0700 Subject: [PATCH 06/31] Fix seekhead --- taglib/CMakeLists.txt | 8 + taglib/matroska/ebml/ebmlelement.cpp | 11 ++ taglib/matroska/ebml/ebmlelement.h | 6 + taglib/matroska/ebml/ebmlmasterelement.cpp | 6 + taglib/matroska/ebml/ebmlmasterelement.h | 8 +- taglib/matroska/ebml/ebmlmkseekhead.cpp | 59 ++++++++ taglib/matroska/ebml/ebmlmkseekhead.h | 49 ++++++ taglib/matroska/ebml/ebmlmksegment.cpp | 31 +++- taglib/matroska/ebml/ebmlmksegment.h | 7 + taglib/matroska/ebml/ebmlmktags.cpp | 1 + taglib/matroska/ebml/ebmlmktags.h | 2 +- taglib/matroska/ebml/ebmluintelement.cpp | 2 - taglib/matroska/ebml/ebmlvoidelement.cpp | 60 ++++++++ taglib/matroska/ebml/ebmlvoidelement.h | 56 +++++++ taglib/matroska/matroskaattachments.cpp | 17 ++- taglib/matroska/matroskaattachments.h | 8 +- taglib/matroska/matroskaelement.cpp | 114 +++++++++++++- taglib/matroska/matroskaelement.h | 34 ++++- taglib/matroska/matroskafile.cpp | 166 ++++++++++----------- taglib/matroska/matroskafile.h | 5 - taglib/matroska/matroskaseekhead.cpp | 121 +++++++++++++++ taglib/matroska/matroskaseekhead.h | 55 +++++++ taglib/matroska/matroskasegment.cpp | 45 ++++++ taglib/matroska/matroskasegment.h | 52 +++++++ taglib/matroska/matroskatag.cpp | 14 +- taglib/matroska/matroskatag.h | 8 +- 26 files changed, 834 insertions(+), 111 deletions(-) create mode 100644 taglib/matroska/ebml/ebmlmkseekhead.cpp create mode 100644 taglib/matroska/ebml/ebmlmkseekhead.h create mode 100644 taglib/matroska/ebml/ebmlvoidelement.cpp create mode 100644 taglib/matroska/ebml/ebmlvoidelement.h create mode 100644 taglib/matroska/matroskaseekhead.cpp create mode 100644 taglib/matroska/matroskaseekhead.h create mode 100644 taglib/matroska/matroskasegment.cpp create mode 100644 taglib/matroska/matroskasegment.h diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 00176940..73f3f4a1 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -230,15 +230,19 @@ if(WITH_SHORTEN) endif() set(tag_PRIVATE_HDRS + matroska/matroskaseekhead.h + matroska/matroskasegment.h matroska/ebml/ebmlbinaryelement.h matroska/ebml/ebmlelement.h matroska/ebml/ebmlmasterelement.h matroska/ebml/ebmlmkattachments.h + matroska/ebml/ebmlmkseekhead.h matroska/ebml/ebmlmksegment.h matroska/ebml/ebmlmktags.h matroska/ebml/ebmlstringelement.h matroska/ebml/ebmluintelement.h matroska/ebml/ebmlutils.h + matroska/ebml/ebmlvoidelement.h ) set(tag_HDRS ${tag_PUBLIC_HDRS} ${tag_PRIVATE_HDRS}) @@ -248,6 +252,8 @@ set(matroska_SRCS matroska/matroskaattachments.cpp matroska/matroskaelement.cpp matroska/matroskafile.cpp + matroska/matroskaseekhead.cpp + matroska/matroskasegment.cpp matroska/matroskasimpletag.cpp matroska/matroskatag.cpp ) @@ -257,11 +263,13 @@ set(ebml_SRCS matroska/ebml/ebmlelement.cpp matroska/ebml/ebmlmasterelement.cpp matroska/ebml/ebmlmkattachments.cpp + matroska/ebml/ebmlmkseekhead.cpp matroska/ebml/ebmlmksegment.cpp matroska/ebml/ebmlmktags.cpp matroska/ebml/ebmlstringelement.cpp matroska/ebml/ebmluintelement.cpp matroska/ebml/ebmlutils.cpp + matroska/ebml/ebmlvoidelement.cpp ) set(mpeg_SRCS diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 7c3ff3d5..a15b755b 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -19,8 +19,10 @@ ***************************************************************************/ #include "ebmlelement.h" +#include "ebmlvoidelement.h" #include "ebmlmasterelement.h" #include "ebmlbinaryelement.h" +#include "ebmlmkseekhead.h" #include "ebmlmksegment.h" #include "ebmlmktags.h" #include "ebmlmkattachments.h" @@ -68,6 +70,7 @@ EBML::Element* EBML::Element::factory(File &file) case ElementIDs::MkTagTargets: case ElementIDs::MkSimpleTag: case ElementIDs::MkAttachedFile: + case ElementIDs::MkSeek: return new MasterElement(id, sizeLength, dataSize, offset); case ElementIDs::MkTagName: @@ -82,10 +85,18 @@ EBML::Element* EBML::Element::factory(File &file) case ElementIDs::MkTagTargetTypeValue: case ElementIDs::MkAttachedFileUID: + case ElementIDs::MkSeekPosition: return new UIntElement(id, sizeLength, dataSize); case ElementIDs::MkAttachedFileData: + case ElementIDs::MkSeekID: return new BinaryElement(id, sizeLength, dataSize); + + case ElementIDs::MkSeekHead: + return new MkSeekHead(sizeLength, dataSize, offset); + + case ElementIDs::VoidElement: + return new VoidElement(sizeLength, dataSize); default: return new Element(id, sizeLength, dataSize); diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 3da66a8e..d9a6c9e0 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -43,6 +43,7 @@ namespace TagLib { void skipData(File &file); Id getId() const { return id; } offset_t headSize() const; + offset_t getSize() const { return headSize() + dataSize; } int getSizeLength() const { return sizeLength; } int64_t getDataSize() const { return dataSize; } ByteVector renderId(); @@ -58,6 +59,7 @@ namespace TagLib { namespace ElementIDs { inline constexpr Element::Id EBMLHeader = 0x1A45DFA3; + inline constexpr Element::Id VoidElement = 0xEC; inline constexpr Element::Id MkSegment = 0x18538067; inline constexpr Element::Id MkTags = 0x1254C367; inline constexpr Element::Id MkTag = 0x7373; @@ -76,6 +78,10 @@ namespace TagLib { inline constexpr Element::Id MkAttachedFileMediaType = 0x4660; inline constexpr Element::Id MkAttachedFileData = 0x465C; inline constexpr Element::Id MkAttachedFileUID = 0x46AE; + inline constexpr Element::Id MkSeekHead = 0x114D9B74; + inline constexpr Element::Id MkSeek = 0x4DBB; + inline constexpr Element::Id MkSeekID = 0x53AB; + inline constexpr Element::Id MkSeekPosition = 0x53AC; } } diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp index aa632c97..73fe6f0f 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.cpp +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -19,6 +19,7 @@ ***************************************************************************/ #include "ebmlmasterelement.h" +#include "ebmlvoidelement.h" #include "ebmlutils.h" #include "matroskafile.h" @@ -55,5 +56,10 @@ ByteVector EBML::MasterElement::render() dataSize = data.size(); buffer.append(renderVINT(dataSize, 0)); buffer.append(data); + if (minRenderSize) { + auto bufferSize = buffer.size(); + if(minRenderSize >= (bufferSize + MIN_VOID_ELEMENT_SIZE)) + buffer.append(VoidElement::renderSize(minRenderSize - bufferSize)); + } return buffer; } diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h index d9809f65..1f0b2a1d 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.h +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -41,7 +41,6 @@ namespace TagLib { {} ~MasterElement() override; offset_t getOffset() const { return offset; } - offset_t getSize() const { return headSize() + dataSize; } bool read(File &file) override; ByteVector render() override; void appendElement(Element *element) { elements.append(element); } @@ -49,9 +48,16 @@ namespace TagLib { List::Iterator end () { return elements.end(); } List::ConstIterator cbegin () const { return elements.cbegin(); } List::ConstIterator cend () const { return elements.cend(); } + offset_t getPadding() const { return padding; } + void setPadding(offset_t padding) { this->padding = padding; } + offset_t getMinRenderSize() const { return minRenderSize; } + void setMinRenderSize(offset_t minRenderSize) { this->minRenderSize = minRenderSize; } + protected: offset_t offset; + offset_t padding = 0; + offset_t minRenderSize = 0; List elements; }; diff --git a/taglib/matroska/ebml/ebmlmkseekhead.cpp b/taglib/matroska/ebml/ebmlmkseekhead.cpp new file mode 100644 index 00000000..d79997ef --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkseekhead.cpp @@ -0,0 +1,59 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmkseekhead.h" +#include "matroskaseekhead.h" +#include "ebmluintelement.h" +#include "ebmlbinaryelement.h" + +using namespace TagLib; + +Matroska::SeekHead* EBML::MkSeekHead::parse() +{ + auto seekHead = new Matroska::SeekHead(); + seekHead->setOffset(offset); + seekHead->setSize(getSize() + padding); + + for(auto element : elements) { + if(element->getId() != ElementIDs::MkSeek) + continue; + auto seekElement = static_cast(element); + Matroska::Element::ID entryId = 0; + offset_t offset = 0; + for(auto seekElementChild : *seekElement) { + Id id = seekElementChild->getId(); + if(id == ElementIDs::MkSeekID && !entryId) { + auto data = static_cast(seekElementChild)->getValue(); + if(data.size() == 4) + entryId = data.toUInt(true); + } + else if(id == ElementIDs::MkSeekPosition && !offset) + offset = static_cast(seekElementChild)->getValue(); + } + if(entryId && offset) + seekHead->addEntry(entryId, offset); + else { + delete seekHead; + return nullptr; + } + } + + return seekHead; +} diff --git a/taglib/matroska/ebml/ebmlmkseekhead.h b/taglib/matroska/ebml/ebmlmkseekhead.h new file mode 100644 index 00000000..c07ca31f --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkseekhead.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmasterelement.h" +#include "taglib.h" + +#ifndef TAGLIB_EBMLMKSEEKHEAD_H +#define TAGLIB_EBMLMKSEEKHEAD_H +#ifndef DO_NOT_DOCUMENT + +namespace TagLib { + namespace Matroska { + class SeekHead; + } + namespace EBML { + class MkSeekHead : public MasterElement + { + public: + MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset) + : MasterElement(ElementIDs::MkSeekHead, sizeLength, dataSize, offset) + {} + MkSeekHead() + : MasterElement(ElementIDs::MkSeekHead, 0, 0, 0) + {} + + Matroska::SeekHead* parse(); + + }; + } +} +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index bfbd2182..759636bc 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -21,10 +21,13 @@ #include "ebmlmksegment.h" #include "ebmlmktags.h" #include "ebmlmkattachments.h" +#include "ebmlmkseekhead.h" #include "ebmlutils.h" #include "matroskafile.h" #include "matroskatag.h" #include "matroskaattachments.h" +#include "matroskaseekhead.h" +#include "matroskasegment.h" #include "tutils.h" #include "tbytevector.h" #include "tdebug.h" @@ -35,16 +38,24 @@ EBML::MkSegment::~MkSegment() { delete tags; delete attachments; + delete seekHead; } bool EBML::MkSegment::read(File &file) { offset_t maxOffset = file.tell() + dataSize; EBML::Element *element = nullptr; - + int i = 0; + int seekHeadIndex = -1; while((element = findNextElement(file, maxOffset))) { Id id = element->getId(); - if(id == ElementIDs::MkTags) { + if(id == ElementIDs::MkSeekHead) { + seekHeadIndex = i; + seekHead = static_cast(element); + if(!seekHead->read(file)) + return false; + } + else if(id == ElementIDs::MkTags) { tags = static_cast(element); if(!tags->read(file)) return false; @@ -55,9 +66,15 @@ bool EBML::MkSegment::read(File &file) return false; } else { + if(id == ElementIDs::VoidElement + && seekHead + && seekHeadIndex == (i - 1)) + seekHead->setPadding(element->getSize()); + element->skipData(file); delete element; } + i++; } return true; } @@ -71,3 +88,13 @@ Matroska::Attachments* EBML::MkSegment::parseAttachments() { return attachments ? attachments->parse() : nullptr; } + +Matroska::SeekHead* EBML::MkSegment::parseSeekHead() +{ + return seekHead ? seekHead->parse() : nullptr; +} + +Matroska::Segment* EBML::MkSegment::parseSegment() +{ + return new Matroska::Segment(sizeLength, dataSize, offset + idSize(id)); +} diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index 0cbdb51e..41f68983 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -30,10 +30,13 @@ namespace TagLib { namespace Matroska { class Tag; class Attachments; + class SeekHead; + class Segment; } namespace EBML { class MkTags; class MkAttachments; + class MkSeekHead; class MkSegment : public MasterElement { public: @@ -44,10 +47,14 @@ namespace TagLib { bool read(File &file) override; Matroska::Tag* parseTag(); Matroska::Attachments* parseAttachments(); + Matroska::SeekHead* parseSeekHead(); + Matroska::Segment* parseSegment(); private: + offset_t dataOffset = 0; MkTags *tags = nullptr; MkAttachments *attachments = nullptr; + MkSeekHead *seekHead = nullptr; }; } diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index 20ec482c..6d7d237a 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -36,6 +36,7 @@ Matroska::Tag* EBML::MkTags::parse() auto mTag = new Matroska::Tag(); mTag->setOffset(offset); mTag->setSize(getSize()); + mTag->setID(id); // Loop through each element for(auto tagsChild : elements) { diff --git a/taglib/matroska/ebml/ebmlmktags.h b/taglib/matroska/ebml/ebmlmktags.h index a2b550e2..f9d9aa5b 100644 --- a/taglib/matroska/ebml/ebmlmktags.h +++ b/taglib/matroska/ebml/ebmlmktags.h @@ -41,7 +41,7 @@ namespace TagLib { MkTags() : MasterElement(ElementIDs::MkTags, 0, 0, 0) {} - //virtual void read(File &file) override; + Matroska::Tag* parse(); }; diff --git a/taglib/matroska/ebml/ebmluintelement.cpp b/taglib/matroska/ebml/ebmluintelement.cpp index 2c84ddff..716b770b 100644 --- a/taglib/matroska/ebml/ebmluintelement.cpp +++ b/taglib/matroska/ebml/ebmluintelement.cpp @@ -58,10 +58,8 @@ ByteVector EBML::UIntElement::render() dataSize = 8; ByteVector buffer = renderId(); - //dataSize = minSize(value); buffer.append(renderVINT(dataSize, 0)); unsigned long long value = this->value; - //debug(Utils::formatString("Writing %llu", value)); static const auto byteOrder = Utils::systemByteOrder(); if(byteOrder == Utils::LittleEndian) diff --git a/taglib/matroska/ebml/ebmlvoidelement.cpp b/taglib/matroska/ebml/ebmlvoidelement.cpp new file mode 100644 index 00000000..2eae2fed --- /dev/null +++ b/taglib/matroska/ebml/ebmlvoidelement.cpp @@ -0,0 +1,60 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlvoidelement.h" +#include "ebmlutils.h" +#include "tbytevector.h" +#include "tdebug.h" + +#include + +using namespace TagLib; + +ByteVector EBML::VoidElement::render() +{ + offset_t bytesNeeded = targetSize; + ByteVector buffer = renderId(); + bytesNeeded -= buffer.size(); + sizeLength = std::min(bytesNeeded, static_cast(8)); + bytesNeeded -= sizeLength; + dataSize = bytesNeeded; + buffer.append(renderVINT(dataSize, sizeLength)); + if (dataSize) + buffer.append(ByteVector(dataSize, 0)); + + return buffer; +} + +offset_t EBML::VoidElement::getTargetSize() const +{ + return targetSize; +} + +void EBML::VoidElement::setTargetSize(offset_t targetSize) +{ + this->targetSize = std::max(targetSize, MIN_VOID_ELEMENT_SIZE); +} + +ByteVector EBML::VoidElement::renderSize(offset_t targetSize) +{ + VoidElement element; + element.setTargetSize(targetSize); + return element.render(); +} diff --git a/taglib/matroska/ebml/ebmlvoidelement.h b/taglib/matroska/ebml/ebmlvoidelement.h new file mode 100644 index 00000000..bfb0a5a3 --- /dev/null +++ b/taglib/matroska/ebml/ebmlvoidelement.h @@ -0,0 +1,56 @@ + /*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLVOIDELEMENT_H +#define TAGLIB_EBMLVOIDELEMENT_H +#ifndef DO_NOT_DOCUMENT + +#include +#include "ebmlelement.h" +#include "tutils.h" + +namespace TagLib { + class File; + + namespace EBML { + inline constexpr offset_t MIN_VOID_ELEMENT_SIZE = 2; + class VoidElement : public Element + { + public: + VoidElement(int sizeLength, offset_t dataSize) + : Element(ElementIDs::VoidElement, sizeLength, dataSize) + {} + VoidElement() + : Element(ElementIDs::VoidElement, 0, 0) + {} + ByteVector render() override; + offset_t getTargetSize() const; + void setTargetSize(offset_t targetSize); + static ByteVector renderSize(offset_t size); + + private: + offset_t targetSize = MIN_VOID_ELEMENT_SIZE; + + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/matroskaattachments.cpp b/taglib/matroska/matroskaattachments.cpp index 9504e86d..25fda0a9 100644 --- a/taglib/matroska/matroskaattachments.cpp +++ b/taglib/matroska/matroskaattachments.cpp @@ -19,11 +19,11 @@ public: AttachmentsPrivate(const AttachmentsPrivate &) = delete; AttachmentsPrivate &operator=(const AttachmentsPrivate &) = delete; List files; - }; Matroska::Attachments::Attachments() -: d(std::make_unique()) +: Element(ElementIDs::MkAttachments), + d(std::make_unique()) { d->files.setAutoDelete(true); } @@ -53,7 +53,7 @@ const Matroska::Attachments::AttachedFileList& Matroska::Attachments::attachedFi return d->files; } -ByteVector Matroska::Attachments::render() +bool Matroska::Attachments::render() { EBML::MkAttachments attachments; for(const auto attachedFile : d->files) { @@ -93,5 +93,14 @@ ByteVector Matroska::Attachments::render() attachments.appendElement(attachedFileElement); } - return attachments.render(); + auto beforeSize = size(); + auto data = attachments.render(); + auto afterSize = data.size(); + if (beforeSize != afterSize) { + if (!emitSizeChanged(afterSize - beforeSize)) + return false; + } + setData(data); + return true; } + diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index 84ba1332..dde699a0 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -28,13 +28,17 @@ namespace TagLib { + class File; namespace EBML { class MkAttachments; } namespace Matroska { class AttachedFile; class File; - class TAGLIB_EXPORT Attachments : private Element + class TAGLIB_EXPORT Attachments +#ifndef DO_NOT_DOCUMENT + : private Element +#endif { public: using AttachedFileList = List; @@ -45,7 +49,7 @@ namespace TagLib { void removeAttachedFile(AttachedFile *file); void clear(); const AttachedFileList& attachedFileList() const; - ByteVector render() override; + bool render() override; private: friend class EBML::MkAttachments; diff --git a/taglib/matroska/matroskaelement.cpp b/taglib/matroska/matroskaelement.cpp index 43503734..5d406a38 100644 --- a/taglib/matroska/matroskaelement.cpp +++ b/taglib/matroska/matroskaelement.cpp @@ -1,6 +1,26 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ #include #include "matroskaelement.h" #include "tlist.h" +#include "tfile.h" #include "tbytevector.h" using namespace TagLib; @@ -14,12 +34,17 @@ public: ElementPrivate &operator=(const ElementPrivate &) = delete; offset_t size = 0; offset_t offset = 0; + ID id = 0; + ByteVector data; + List sizeListeners; + List offsetListeners; }; -Matroska::Element::Element() +Matroska::Element::Element(ID id) : e(std::make_unique()) { + e->id = id; } Matroska::Element::~Element() = default; @@ -33,12 +58,97 @@ offset_t Matroska::Element::offset() const return e->offset; } +void Matroska::Element::setData(const ByteVector &data) +{ + e->data = data; +} + +const ByteVector& Matroska::Element::data() const +{ + return e->data; +} + + void Matroska::Element::setOffset(offset_t offset) { e->offset = offset; } +void Matroska::Element::adjustOffset(offset_t delta) +{ + e->offset += delta; +} + void Matroska::Element::setSize(offset_t size) { e->size = size; -} \ No newline at end of file +} + +Matroska::Element::ID Matroska::Element::id() const +{ + return e->id; +} + +void Matroska::Element::addSizeListener(Element *element) +{ + e->sizeListeners.append(element); +} + +void Matroska::Element::addSizeListeners(const List &elements) +{ + e->sizeListeners.append(elements); +} + +void Matroska::Element::addOffsetListener(Element *element) +{ + e->offsetListeners.append(element); +} + +void Matroska::Element::addOffsetListeners(const List &elements) +{ + e->offsetListeners.append(elements); +} + +void Matroska::Element::setID(ID id) +{ + e->id = id; +} + +bool Matroska::Element::emitSizeChanged(offset_t delta) +{ + for(auto element : e->sizeListeners) { + if (!element->sizeChanged(*this, delta)) + return false; + } + return true; +} + +bool Matroska::Element::emitOffsetChanged(offset_t delta) +{ + for(auto element : e->offsetListeners) { + if(!element->offsetChanged(*this, delta)) + return false; + } + return true; +} + +bool Matroska::Element::sizeChanged(Element &caller, offset_t delta) +{ + if (caller.offset() < e->offset) { + e->offset += delta; + //return emitOffsetChanged(delta); + } + return true; +} + +bool Matroska::Element::offsetChanged(Element &caller, offset_t delta) +{ + // Most elements don't need to handle this + return true; +} + +void Matroska::Element::write(TagLib::File &file) +{ + file.insert(e->data, e->offset, e->size); + e->size = e->data.size(); +} diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index 16d4a142..9eef6f03 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -20,32 +20,60 @@ #ifndef HAS_MATROSKAELEMENT_H #define HAS_MATROSKAELEMENT_H +#ifndef DO_NOT_DOCUMENT #include #include "taglib_export.h" #include "tutils.h" #include "tbytevector.h" +#include "tlist.h" namespace TagLib { + class File; namespace Matroska { class TAGLIB_EXPORT Element { public: - Element(); + using ID = unsigned int; + Element(ID id); virtual ~Element(); - virtual ByteVector render() = 0; + offset_t size() const; offset_t offset() const; + ID id() const; void setOffset(offset_t offset); + void adjustOffset(offset_t delta); void setSize(offset_t size); + void setID(ID id); + //virtual ByteVector render() = 0; + virtual bool render() = 0; + void setData(const ByteVector &data); + const ByteVector& data() const; + virtual void write(TagLib::File &file); + void addSizeListener(Element *element); + void addSizeListeners(const List &elements); + void addOffsetListener(Element *element); + void addOffsetListeners(const List &elements); + //virtual void updatePosition(Element &caller, offset_t delta) = 0; + bool emitSizeChanged(offset_t delta); + bool emitOffsetChanged(offset_t delta); + virtual bool offsetChanged(Element &caller, offset_t delta); + virtual bool sizeChanged(Element &caller, offset_t delta); private: class ElementPrivate; std::unique_ptr e; }; + namespace ElementIDs { + inline constexpr Element::ID MkTags = 0x1254C367; + inline constexpr Element::ID MkAttachments = 0x1941A469; + inline constexpr Element::ID MkSeekHead = 0x114D9B74; + inline constexpr Element::ID MkSegment = 0x18538067; + } } } - #endif \ No newline at end of file +#endif +#endif \ No newline at end of file diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index 83b12dd0..dc9f2f6a 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -21,6 +21,8 @@ #include "matroskafile.h" #include "matroskatag.h" #include "matroskaattachments.h" +#include "matroskaseekhead.h" +#include "matroskasegment.h" #include "ebmlutils.h" #include "ebmlelement.h" #include "ebmlmksegment.h" @@ -42,16 +44,15 @@ public: { delete tag; delete attachments; + delete seekHead; } FilePrivate(const FilePrivate &) = delete; FilePrivate &operator=(const FilePrivate &) = delete; Matroska::Tag *tag = nullptr; - Matroska::Attachments *attachments = nullptr; - offset_t segmentSizeOffset = 0; - offset_t segmentSizeLength = 0; - offset_t segmentDataSize = 0; - + Attachments *attachments = nullptr; + SeekHead *seekHead = nullptr; + Segment *segment = nullptr; }; Matroska::File::File(FileName file, bool readProperties) @@ -65,6 +66,7 @@ Matroska::File::File(FileName file, bool readProperties) } read(readProperties); } + Matroska::File::File(IOStream *stream, bool readProperties) : TagLib::File(stream), d(std::make_unique()) @@ -76,6 +78,7 @@ Matroska::File::File(IOStream *stream, bool readProperties) } read(readProperties); } + Matroska::File::~File() = default; TagLib::Tag* Matroska::File::tag() const @@ -129,9 +132,6 @@ void Matroska::File::read(bool readProperties) 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)) { @@ -139,11 +139,11 @@ void Matroska::File::read(bool readProperties) setValid(false); return; } - - // Parse the tag + + // Parse the elements + d->segment = segment->parseSegment(); + d->seekHead = segment->parseSeekHead(); d->tag = segment->parseTag(); - - // Parse the attachments d->attachments = segment->parseAttachments(); setValid(true); @@ -151,86 +151,82 @@ void Matroska::File::read(bool readProperties) bool Matroska::File::save() { - std::vector renderListExisting; - std::vector renderListNew; - offset_t newSegmentDataSize = d->segmentDataSize; - - - if(d->tag) { - if(d->tag->size()) - renderListExisting.push_back(d->tag); - else - renderListNew.push_back(d->tag); + if(readOnly()) { + debug("Matroska::File::save() -- File is read only."); + return false; + } + if(!isValid()) { + debug("Matroska::File::save() -- File is not valid."); + return false; } - if(d->attachments) { - //d->attachments->setOffset(d->tag->offset()); - //renderListExisting.push_back(d->attachments); - if(d->attachments->size()) - renderListExisting.push_back(d->attachments); - else - renderListNew.push_back(d->attachments); + List renderList; + List newElements; - - } + // List of all possible elements we can write + List elements { + d->attachments, + d->tag + }; - - // Render from end to beginning so we don't have to shift - // the file offsets - std::sort(renderListExisting.begin(), - renderListExisting.end(), - [](auto a, auto b) { return a->offset() > b->offset(); } - ); - - // Overwrite existing elements - for(auto element : renderListExisting) { - offset_t offset = element->offset(); - offset_t originalSize = element->size(); - ByteVector data = element->render(); - insert(data, offset, originalSize); - newSegmentDataSize += (data.size() - originalSize); - } - - // Add new elements to the end of file - for(auto element : renderListNew) { - offset_t offset = length(); - ByteVector data = element->render(); - insert(data, offset, 0); - newSegmentDataSize += data.size(); - } - - // Write the new segment data size - if(newSegmentDataSize != d->segmentDataSize) { - auto segmentDataSizeBuffer = EBML::renderVINT(newSegmentDataSize, d->segmentSizeLength); - insert(segmentDataSizeBuffer, d->segmentSizeOffset, d->segmentSizeLength); - d->segmentDataSize = newSegmentDataSize; - } - -/* - auto&& renderElements [&d->segmentDataSize](Element *element) { - auto offset = element->offset(); - if(!offset) - } - - if(d->tag) { - ByteVector tag = d->tag->render(); - auto tagsOriginalSize = d->tag->size(); - auto tagsOffset = d->tag->offset(); - - if(!tagsOriginalSize) { - tagsOffset = d->segmentSizeOffset + d->segmentSizeLength + d->segmentDataSize; + /* Build render list. New elements will be added + * to the end of the file. For new elements, + * the order is from least likely to change, + * to most likely to change: + * 1. Bookmarks (todo) + * 2. Attachments + * 3. Tags + */ + for (auto element : elements) { + if (!element) + continue; + if (element->size()) + renderList.append(element); + else { + element->setOffset(length()); + newElements.append(element); } - insert(tag, tagsOffset, tagsOriginalSize); - d->segmentDataSize += (tag.size() - tagsOriginalSize); - auto segmentDataSizeBuffer = EBML::renderVINT(d->segmentDataSize, d->segmentSizeLength); - insert(segmentDataSizeBuffer, d->segmentSizeOffset, d->segmentSizeLength); } - */ - /* - if(d->attachments) { - ByteVector attachments = d->attachments->render(); + if (renderList.isEmpty()) + return true; + + auto sortAscending = [](const auto a, const auto b) { return a->offset() < b->offset(); }; + renderList.sort(sortAscending); + renderList.append(newElements); + + // Add our new elements to the Seek Head (if the file has one) + if (d->seekHead) { + auto segmentDataOffset = d->segment->dataOffset(); + for (auto element : newElements) + d->seekHead->addEntry(element->id(), element->offset() - segmentDataOffset); + d->seekHead->sort(); } - */ + + // Set up listeners, add seek head and segment length to the end + for(auto it = renderList.begin(); it != renderList.end(); ++it) { + for (auto it2 = std::next(it); it2 != renderList.end(); ++it2) + (*it)->addSizeListener(*it2); + if (d->seekHead) + (*it)->addSizeListener(d->seekHead); + (*it)->addSizeListener(d->segment); + } + if(d->seekHead) { + d->seekHead->addSizeListeners(renderList); + renderList.append(d->seekHead); + } + d->segment->addSizeListeners(renderList); + renderList.append(d->segment); + + // Render the elements + for(auto element : renderList) { + if (!element->render()) + return false; + } + + // Write out to file + renderList.sort(sortAscending); + for(auto element : renderList) + element->write(*this); return true; } diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index a729cfa3..a8e99e19 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -1,8 +1,3 @@ -/*************************************************************************** - copyright : (C) 2002 - 2008 by Scott Wheeler - email : wheeler@kde.org - ***************************************************************************/ - /*************************************************************************** * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License version * diff --git a/taglib/matroska/matroskaseekhead.cpp b/taglib/matroska/matroskaseekhead.cpp new file mode 100644 index 00000000..81cc2564 --- /dev/null +++ b/taglib/matroska/matroskaseekhead.cpp @@ -0,0 +1,121 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + #include "matroskaseekhead.h" + #include "ebmlmkseekhead.h" + #include "ebmlbinaryelement.h" + #include "ebmluintelement.h" + #include "ebmlmasterelement.h" + + #include "tdebug.h" + #include "tfile.h" + #include "tutils.h" + + using namespace TagLib; + +void Matroska::SeekHead::addEntry(Element &element) +{ + entries.append({element.id(), element.offset()}); + debug("adding to seekhead"); + needsRender = true; +} + +void Matroska::SeekHead::addEntry(ID id, offset_t offset) +{ + entries.append({id, offset}); + needsRender = true; +} + +ByteVector Matroska::SeekHead::renderInternal() +{ + auto beforeSize = size(); + EBML::MkSeekHead seekHead; + seekHead.setMinRenderSize(beforeSize); + for(const auto& [id, position] : entries) { + auto seekElement = new EBML::MasterElement(EBML::ElementIDs::MkSeek); + auto idElement = new EBML::BinaryElement(EBML::ElementIDs::MkSeekID); + idElement->setValue(ByteVector::fromUInt(id, true)); + seekElement->appendElement(idElement); + + auto positionElement = new EBML::UIntElement(EBML::ElementIDs::MkSeekPosition); + positionElement->setValue(static_cast(position)); + seekElement->appendElement(positionElement); + + seekHead.appendElement(seekElement); + } + return seekHead.render(); +} + +bool Matroska::SeekHead::render() +{ + if (!needsRender) + return true; + + auto beforeSize = size(); + auto data = renderInternal(); + needsRender = false; + auto afterSize = data.size(); + if (afterSize != beforeSize) { + return false; + // To do, handle expansion of seek head + if (!emitSizeChanged(afterSize - beforeSize)) + return false; + } + + setData(data); + return true; +} + +void Matroska::SeekHead::write(TagLib::File &file) +{ + if (!data().isEmpty()) + Element::write(file); +} + +void Matroska::SeekHead::sort() +{ + entries.sort([](const auto &a, const auto &b) { return a.second < b.second; }); +} + +bool Matroska::SeekHead::sizeChanged(Element &caller, offset_t delta) +{ + ID callerID = caller.id(); + if (callerID == ElementIDs::MkSegment) { + adjustOffset(delta); + return true; + } + else { + offset_t offset = caller.offset(); + auto it = entries.begin(); + while (it != entries.end()) { + it = std::find_if(it, + entries.end(), + [offset](const auto a){ return a.second > offset; } + ); + if (it != entries.end()) { + it->second += delta; + needsRender = true; + ++it; + } + } + return true; + } + return false; +} diff --git a/taglib/matroska/matroskaseekhead.h b/taglib/matroska/matroskaseekhead.h new file mode 100644 index 00000000..6c90f4fa --- /dev/null +++ b/taglib/matroska/matroskaseekhead.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MATROSKASEEKHEAD_H +#define TAGLIB_MATROSKASEEKHEAD_H +#ifndef DO_NOT_DOCUMENT + +#include "matroskaelement.h" +#include "tbytevector.h" +#include "tutils.h" +#include "tlist.h" + +namespace TagLib { + class File; + namespace Matroska { + class SeekHead : public Element + { + public: + SeekHead() : Element(ElementIDs::MkSeekHead) {}; + virtual ~SeekHead() {}; + void addEntry(Element &element); + void addEntry(ID id, offset_t offset); + bool render() override; + void write(TagLib::File &file) override; + void sort(); + //bool offsetChanged(Element &caller, offset_t delta) override; + bool sizeChanged(Element &caller, offset_t delta) override; + + private: + ByteVector renderInternal(); + List> entries; + bool needsRender = false; + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/matroskasegment.cpp b/taglib/matroska/matroskasegment.cpp new file mode 100644 index 00000000..f047e1b9 --- /dev/null +++ b/taglib/matroska/matroskasegment.cpp @@ -0,0 +1,45 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskasegment.h" +#include "ebmlutils.h" + +using namespace TagLib; + +bool Matroska::Segment::render() +{ + auto data = EBML::renderVINT(dataSize, sizeLength); + if (data.size() != sizeLength) { + sizeLength = 8; + if (!emitSizeChanged(sizeLength - data.size())) + return false; + data = EBML::renderVINT(dataSize, sizeLength); + if (data.size() != sizeLength) + return false; + } + setData(data); + return true; +} + +bool Matroska::Segment::sizeChanged(Element &caller, offset_t delta) +{ + dataSize += delta; + return true; +} \ No newline at end of file diff --git a/taglib/matroska/matroskasegment.h b/taglib/matroska/matroskasegment.h new file mode 100644 index 00000000..5f275e6c --- /dev/null +++ b/taglib/matroska/matroskasegment.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MATROSKASEGMENT_H +#define TAGLIB_MATROSKASEGMENT_H +#ifndef DO_NOT_DOCUMENT + +#include "matroskaelement.h" +#include "tutils.h" + +namespace TagLib { + namespace Matroska { + class Segment : public Element + { + public: + Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset) + : Element(ElementIDs::MkSegment), sizeLength(sizeLength), dataSize(dataSize) + { + setOffset(lengthOffset); + setSize(sizeLength); + } + virtual ~Segment() = default; + bool render() override; + bool sizeChanged(Element &caller, offset_t delta) override; + offset_t dataOffset() const { return offset() + sizeLength; } + + private: + offset_t sizeLength; + offset_t dataSize; + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index b03a6f2f..cc06e2a1 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -50,11 +50,13 @@ class Matroska::Tag::TagPrivate TagPrivate() = default; ~TagPrivate() = default; List tags; + ByteVector data; }; Matroska::Tag::Tag() : TagLib::Tag(), + Element(ElementIDs::MkTags), d(std::make_unique()) { d->tags.setAutoDelete(true); @@ -183,7 +185,7 @@ bool Matroska::Tag::isEmpty() const return d->tags.isEmpty(); } -ByteVector Matroska::Tag::render() +bool Matroska::Tag::render() { EBML::MkTags tags; List*> targetList; @@ -256,7 +258,15 @@ ByteVector Matroska::Tag::render() tags.appendElement(tag); } - return tags.render(); + auto data = tags.render(); + auto beforeSize = size(); + auto afterSize = data.size(); + if (afterSize != beforeSize) { + if (!emitSizeChanged(afterSize - beforeSize)) + return false; + } + setData(data); + return true; } namespace diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index f352195f..2370faec 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -33,13 +33,17 @@ #include "matroskasimpletag.h" namespace TagLib { + class File; namespace EBML { class MkTags; } namespace Matroska { using SimpleTagsList = List; - class TAGLIB_EXPORT Tag : public TagLib::Tag, private Element + class TAGLIB_EXPORT Tag : public TagLib::Tag +#ifndef DO_NOT_DOCUMENT + , private Element +#endif { public: Tag(); @@ -63,7 +67,7 @@ namespace TagLib { void setYear(unsigned int i) override; void setTrack(unsigned int i) override; bool isEmpty() const override; - ByteVector render() override; + bool render() override; PropertyMap properties() const override; PropertyMap setProperties(const PropertyMap &propertyMap) override; template From f94843614f1f1b2b87b7e5f95c7dcd5b642592ca Mon Sep 17 00:00:00 2001 From: complexlogic Date: Tue, 5 Aug 2025 06:42:16 -0700 Subject: [PATCH 07/31] Initial cues work --- taglib/matroska/ebml/ebmlmkcues.cpp | 81 ++++++++++++++ taglib/matroska/ebml/ebmlmkcues.h | 51 +++++++++ taglib/matroska/matroskacues.cpp | 161 ++++++++++++++++++++++++++++ taglib/matroska/matroskacues.h | 119 ++++++++++++++++++++ 4 files changed, 412 insertions(+) create mode 100644 taglib/matroska/ebml/ebmlmkcues.cpp create mode 100644 taglib/matroska/ebml/ebmlmkcues.h create mode 100644 taglib/matroska/matroskacues.cpp create mode 100644 taglib/matroska/matroskacues.h diff --git a/taglib/matroska/ebml/ebmlmkcues.cpp b/taglib/matroska/ebml/ebmlmkcues.cpp new file mode 100644 index 00000000..cf93a41f --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkcues.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmkcues.h" +#include "ebmluintelement.h" +#include "ebmlstringelement.h" +#include "ebmlutils.h" +#include "matroskafile.h" +#include "matroskacues.h" +#include "tdebug.h" +#include "tutils.h" + +using namespace TagLib; + +Matroska::Cues* EBML::MkCues::parse() +{ + auto cues = new Matroska::Cues(); + cues->setOffset(offset); + cues->setSize(getSize()); + cues->setID(id); + + for (auto cuesChild : elements) { + if (cuesChild->getId() != ElementIDs::MkCuePoint) + continue; + auto cuePointElement = static_cast(cuesChild); + auto cuePoint = new Matroska::CuePoint(); + + for (auto cuePointChild : *cuePointElement) { + Id id = cuePointChild->getId(); + if (id == ElementIDs::MkCueTime) + cuePoint->setTime(static_cast(cuePointChild)->getValue()); + else if (id == ElementIDs::MkCueTrackPositions) { + auto cueTrack = new Matroska::CueTrack(); + auto cueTrackElement = static_cast(cuePointChild); + for (auto cueTrackChild : *cueTrackElement) { + Id trackId = cueTrackChild->getId(); + if (trackId == ElementIDs::MkCueTrack) + cueTrack->setTrackNumber(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueClusterPosition) + cueTrack->setClusterPosition(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueRelativePosition) + cueTrack->setRelativePosition(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueDuration) + cueTrack->setDuration(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueBlockNumber) + cueTrack->setBlockNumber(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueCodecState) + cueTrack->setCodecState(static_cast(cueTrackChild)->getValue()); + else if (trackId == ElementIDs::MkCueReference) { + auto cueReference = static_cast(cueTrackChild); + for (auto cueReferenceChild : *cueReference) { + if (cueReferenceChild->getId() != ElementIDs::MkCueReference) + continue; + cueTrack->addReferenceTime(static_cast(cueReferenceChild)->getValue()); + } + } + } + cuePoint->addCueTrack(cueTrack); + } + } + cues->addCuePoint(cuePoint); + } + return cues; +} diff --git a/taglib/matroska/ebml/ebmlmkcues.h b/taglib/matroska/ebml/ebmlmkcues.h new file mode 100644 index 00000000..78a194dc --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkcues.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + +#ifndef TAGLIB_EBMLMKCUES_H +#define TAGLIB_EBMLMKCUES_H +#ifndef DO_NOT_DOCUMENT + +namespace TagLib { + namespace Matroska { + class Cues; + } + //class Matroska::Tag; + namespace EBML { + class MkCues : public MasterElement + { + public: + MkCues(int sizeLength, offset_t dataSize, offset_t offset) + : MasterElement(ElementIDs::MkCues, sizeLength, dataSize, offset) + {} + MkCues() + : MasterElement(ElementIDs::MkCues, 0, 0, 0) + {} + + Matroska::Cues* parse(); + + }; + } +} +#endif +#endif diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp new file mode 100644 index 00000000..ef046017 --- /dev/null +++ b/taglib/matroska/matroskacues.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskacues.h" +#include "ebmlelement.h" +#include "ebmlmkcues.h" +#include "ebmlmasterelement.h" +#include "ebmluintelement.h" +#include "tlist.h" +#include "tdebug.h" +#include "tfile.h" + +using namespace TagLib; + +Matroska::Cues::Cues() +: Element(ElementIDs::MkCues) +{ + cuePoints.setAutoDelete(true); +} + +ByteVector Matroska::Cues::renderInternal() +{ + EBML::MkCues cues; + for (auto &cuePoint : cuePoints) { + auto cuePointElement = new EBML::MasterElement(EBML::ElementIDs::MkCuePoint); + auto timestamp = new EBML::UIntElement(EBML::ElementIDs::MkCueTime); + timestamp->setValue(cuePoint->getTime()); + cuePointElement->appendElement(timestamp); + + auto trackList = cuePoint->cueTrackList(); + for (auto &cueTrack : trackList) { + auto cueTrackElement = new EBML::MasterElement(EBML::ElementIDs::MkCueTrackPositions); + + // Track number + auto trackNumber = new EBML::UIntElement(EBML::ElementIDs::MkCueTrack); + trackNumber->setValue(cueTrack->getTrackNumber()); + cueTrackElement->appendElement(trackNumber); + + // Cluster position + auto clusterPosition = new EBML::UIntElement(EBML::ElementIDs::MkCueClusterPosition); + clusterPosition->setValue(cueTrack->getClusterPosition()); + cueTrackElement->appendElement(clusterPosition); + + // Todo - other elements + + + // Reference times + auto referenceTimes = cueTrack->referenceTimes(); + if (!referenceTimes.isEmpty()) { + auto cueReference = new EBML::MasterElement(EBML::ElementIDs::MkCueReference); + for (auto reference : referenceTimes) { + auto refTime = new EBML::UIntElement(EBML::ElementIDs::MkCueRefTime); + refTime->setValue(reference); + cueReference->appendElement(refTime); + } + cueTrackElement->appendElement(cueReference); + } + cuePointElement->appendElement(cueTrackElement); + } + } + return cues.render(); +} + +bool Matroska::Cues::render() +{ + if (!needsRender) + return true; + + + setData(cues.render()); + needsRender = false; + return true; +} + +bool Matroska::Cues::sizeChanged(Element &caller, offset_t delta) +{ + offset_t offset = caller.offset(); + for (auto cuePoint : cuePoints) + needsRender |= cuePoint->adjustOffset(offset, delta); + return true; +} + +bool Matroska::Cues::isValid(TagLib::File &file, offset_t segmentDataOffset) const +{ + for (const auto cuePoint : cuePoints) { + if (!cuePoint->isValid(file, segmentDataOffset)) + return false; + } + return true; +} + +Matroska::CuePoint::CuePoint() +{ + cueTracks.setAutoDelete(true); +} + +bool Matroska::CuePoint::isValid(TagLib::File &file, offset_t segmentDataOffset) const +{ + for (const auto track : cueTracks) { + if (!track->isValid(file, segmentDataOffset)) + return false; + } + return true; +} + +bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta) +{ + bool ret = false; + for (auto cueTrack : cueTracks) + ret |= cueTrack->adjustOffset(offset, delta); + + return ret; +} + +bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) const +{ + if (!trackNumber) { + debug("Cue track number not set"); + return false; + } + if (!clusterPosition) { + debug("Cue track cluster position not set"); + return false; + } + file.seek(segmentDataOffset + clusterPosition); + if (EBML::Element::readId(file) != EBML::ElementIDs::MkCluster) { + debug("No cluster found at position"); + return false; + } + if (codecState) { + file.seek(segmentDataOffset + codecState); + if (EBML::Element::readId(file) != EBML::ElementIDs::MkCodecState) { + debug("No codec state found at position"); + return false; + } + } + return true; +} + +bool Matroska::CueTrack::adjustOffset(offset_t offset, offset_t delta) +{ + return false; +} + diff --git a/taglib/matroska/matroskacues.h b/taglib/matroska/matroskacues.h new file mode 100644 index 00000000..57ed29f2 --- /dev/null +++ b/taglib/matroska/matroskacues.h @@ -0,0 +1,119 @@ +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef HAS_MATROSKACUES_H +#define HAS_MATROSKACUES_H +#ifndef DO_NOT_DOCUMENT + +#include +#include +#include + +#include "tlist.h" +#include "matroskaelement.h" + +namespace TagLib { + class File; + namespace EBML { + class MkCues; + } + + namespace Matroska { + class CuePoint; + class CueTrack; + class Cues : public Element + { + public: + using CuePointList = List; + Cues(); + ~Cues() override = default; + bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; + void addCuePoint(CuePoint *cuePoint) { cuePoints.append(cuePoint); } + CuePointList cuePointList() { return cuePoints; } + bool sizeChanged(Element &caller, offset_t delta) override; + bool render() override; + + + private: + friend class EBML::MkCues; + ByteVector renderInternal(); + bool needsRender = false; + + List cuePoints; + }; + + class CuePoint + { + public: + using CueTrackList = List; + using Time = unsigned long long; + CuePoint(); + ~CuePoint() = default; + bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; + void addCueTrack(CueTrack *cueTrack) { cueTracks.append(cueTrack); } + CueTrackList cueTrackList() const { return cueTracks; } + void setTime(Time time) { this->time = time; } + Time getTime() const { return time; } + bool adjustOffset(offset_t offset, offset_t delta); + + + private: + CueTrackList cueTracks; + Time time = 0; + }; + + class CueTrack + { + public: + using ReferenceTimeList = List; + CueTrack() = default; + ~CueTrack() = default; + bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; + void setTrackNumber(unsigned int trackNumber) { this->trackNumber = trackNumber; } + unsigned int getTrackNumber() const { return trackNumber; } + void setClusterPosition(offset_t clusterPosition) { this->clusterPosition = clusterPosition; } + offset_t getClusterPosition() const { return clusterPosition; } + void setRelativePosition(offset_t relativePosition) { this->relativePosition = relativePosition; } + offset_t getRelativePosition() const { return relativePosition; } + void setCodecState(offset_t codecState) { this->codecState = codecState; } + offset_t getCodecState() const { return codecState; } + void setBlockNumber(unsigned int blockNumber) { this->blockNumber = blockNumber; } + unsigned int getBlockNumber() const { return blockNumber; } + void setDuration(unsigned long long duration) { this->duration = duration; } + unsigned long long getDuration() const { return duration; } + void addReferenceTime(unsigned long long refTime) { refTimes.append(refTime); } + ReferenceTimeList referenceTimes() const { return refTimes; } + bool adjustOffset(offset_t offset, offset_t delta); + + + private: + unsigned int trackNumber = 0; + offset_t clusterPosition = 0; + offset_t relativePosition = 0; + unsigned int blockNumber = 0; + unsigned long long duration = 0; + offset_t codecState = 0; + ReferenceTimeList refTimes; + }; + } +} + +#endif +#endif From 4546c0041798d0b6956929301019cbcdd11c2bc5 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Tue, 5 Aug 2025 21:58:02 +0200 Subject: [PATCH 08/31] Compile time configuration WITH_MATROSKA --- CMakeLists.txt | 4 + INSTALL.md | 1 + examples/CMakeLists.txt | 14 +-- taglib/CMakeLists.txt | 121 ++++++++++++----------- taglib/fileref.cpp | 6 +- taglib/matroska/ebml/ebmluintelement.cpp | 9 +- taglib/matroska/ebml/ebmlutils.cpp | 8 +- taglib/matroska/matroskaelement.h | 3 +- taglib/taglib_config.h.cmake | 1 + 9 files changed, 91 insertions(+), 76 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ea44e54..f60afad9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ include(ConfigureChecks.cmake) option(WITH_APE "Build with APE, MPC, WavPack" ON) option(WITH_ASF "Build with ASF" ON) option(WITH_DSF "Build with DSF" ON) +option(WITH_MATROSKA "Build with Matroska" ON) option(WITH_MOD "Build with Tracker modules" ON) option(WITH_MP4 "Build with MP4" ON) option(WITH_RIFF "Build with AIFF, RIFF, WAV" ON) @@ -197,6 +198,9 @@ endif() if(WITH_DSF) set(TAGLIB_WITH_DSF TRUE) endif() +if(WITH_MATROSKA) + set(TAGLIB_WITH_MATROSKA TRUE) +endif() if(WITH_MOD) set(TAGLIB_WITH_MOD TRUE) endif() diff --git a/INSTALL.md b/INSTALL.md index beb4bf43..af9a7652 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -63,6 +63,7 @@ and ID3 tags cannot be disabled. The following CMake options are available: | `WITH_APE` | Build with APE, MPC, WavPack (default ON) | | `WITH_ASF` | Build with ASF (default ON) | | `WITH_DSF` | Build with DSF (default ON) | +| `WITH_MATROSKA` | Build with Matroska (default ON) | | `WITH_MOD` | Build with Tracker modules (default ON) | | `WITH_MP4` | Build with MP4 (default ON) | | `WITH_RIFF` | Build with AIFF, RIFF, WAV (default ON) | diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 777797ca..d5862407 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -39,15 +39,17 @@ target_link_libraries(framelist tag) add_executable(strip-id3v1 strip-id3v1.cpp) target_link_libraries(strip-id3v1 tag) -########### next target ############### +if(WITH_MATROSKA) + ########### next target ############### -add_executable(matroskareader matroskareader.cpp) -target_link_libraries(matroskareader tag) + add_executable(matroskareader matroskareader.cpp) + target_link_libraries(matroskareader tag) -########### next target ############### + ########### next target ############### -add_executable(matroskawriter matroskawriter.cpp) -target_link_libraries(matroskawriter tag) + add_executable(matroskawriter matroskawriter.cpp) + target_link_libraries(matroskawriter tag) +endif() install(TARGETS tagreader tagreader_c tagwriter framelist strip-id3v1 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 73f3f4a1..20fd4331 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -5,8 +5,6 @@ set(tag_HDR_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2 ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2/frames ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v1 - ${CMAKE_CURRENT_SOURCE_DIR}/matroska - ${CMAKE_CURRENT_SOURCE_DIR}/matroska/ebml ) if(WITH_ASF) set(tag_HDR_DIRS ${tag_HDR_DIRS} @@ -66,9 +64,15 @@ if(WITH_SHORTEN) ${CMAKE_CURRENT_SOURCE_DIR}/shorten ) endif() +if(WITH_MATROSKA) + set(tag_HDR_DIRS ${tag_HDR_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/matroska + ${CMAKE_CURRENT_SOURCE_DIR}/matroska/ebml + ) +endif() include_directories(${tag_HDR_DIRS}) -set(tag_PUBLIC_HDRS +set(tag_HDRS tag.h fileref.h audioproperties.h @@ -92,12 +96,6 @@ set(tag_PUBLIC_HDRS toolkit/tpropertymap.h toolkit/tdebuglistener.h toolkit/tversionnumber.h - matroska/matroskaattachedfile.h - matroska/matroskaattachments.h - matroska/matroskafile.h - matroska/matroskatag.h - matroska/matroskasimpletag.h - matroska/matroskaelement.h mpeg/mpegfile.h mpeg/mpegproperties.h mpeg/mpegheader.h @@ -228,49 +226,32 @@ if(WITH_SHORTEN) shorten/shortentag.h ) endif() - -set(tag_PRIVATE_HDRS - matroska/matroskaseekhead.h - matroska/matroskasegment.h - matroska/ebml/ebmlbinaryelement.h - matroska/ebml/ebmlelement.h - matroska/ebml/ebmlmasterelement.h - matroska/ebml/ebmlmkattachments.h - matroska/ebml/ebmlmkseekhead.h - matroska/ebml/ebmlmksegment.h - matroska/ebml/ebmlmktags.h - matroska/ebml/ebmlstringelement.h - matroska/ebml/ebmluintelement.h - matroska/ebml/ebmlutils.h - matroska/ebml/ebmlvoidelement.h -) - -set(tag_HDRS ${tag_PUBLIC_HDRS} ${tag_PRIVATE_HDRS}) - -set(matroska_SRCS - matroska/matroskaattachedfile.cpp - matroska/matroskaattachments.cpp - matroska/matroskaelement.cpp - matroska/matroskafile.cpp - matroska/matroskaseekhead.cpp - matroska/matroskasegment.cpp - matroska/matroskasimpletag.cpp - matroska/matroskatag.cpp -) - -set(ebml_SRCS - matroska/ebml/ebmlbinaryelement.cpp - matroska/ebml/ebmlelement.cpp - matroska/ebml/ebmlmasterelement.cpp - matroska/ebml/ebmlmkattachments.cpp - matroska/ebml/ebmlmkseekhead.cpp - matroska/ebml/ebmlmksegment.cpp - matroska/ebml/ebmlmktags.cpp - matroska/ebml/ebmlstringelement.cpp - matroska/ebml/ebmluintelement.cpp - matroska/ebml/ebmlutils.cpp - matroska/ebml/ebmlvoidelement.cpp -) +if(WITH_MATROSKA) + set(tag_HDRS ${tag_HDRS} + matroska/matroskafile.h + matroska/matroskatag.h + matroska/matroskasimpletag.h + matroska/matroskaattachedfile.h + matroska/matroskaattachments.h + matroska/matroskafile.h + matroska/matroskatag.h + matroska/matroskasimpletag.h + matroska/matroskaelement.h + matroska/matroskaseekhead.h + matroska/matroskasegment.h + matroska/ebml/ebmlbinaryelement.h + matroska/ebml/ebmlelement.h + matroska/ebml/ebmlmasterelement.h + matroska/ebml/ebmlmkattachments.h + matroska/ebml/ebmlmkseekhead.h + matroska/ebml/ebmlmksegment.h + matroska/ebml/ebmlmktags.h + matroska/ebml/ebmlstringelement.h + matroska/ebml/ebmluintelement.h + matroska/ebml/ebmlutils.h + matroska/ebml/ebmlvoidelement.h + ) +endif() set(mpeg_SRCS mpeg/mpegfile.cpp @@ -461,6 +442,33 @@ if(WITH_SHORTEN) ) endif() +if(WITH_MATROSKA) + set(matroska_SRCS + matroska/matroskaattachedfile.cpp + matroska/matroskaattachments.cpp + matroska/matroskaelement.cpp + matroska/matroskafile.cpp + matroska/matroskaseekhead.cpp + matroska/matroskasegment.cpp + matroska/matroskasimpletag.cpp + matroska/matroskatag.cpp + ) + + set(ebml_SRCS + matroska/ebml/ebmlbinaryelement.cpp + matroska/ebml/ebmlelement.cpp + matroska/ebml/ebmlmasterelement.cpp + matroska/ebml/ebmlmkattachments.cpp + matroska/ebml/ebmlmkseekhead.cpp + matroska/ebml/ebmlmksegment.cpp + matroska/ebml/ebmlmktags.cpp + matroska/ebml/ebmlstringelement.cpp + matroska/ebml/ebmluintelement.cpp + matroska/ebml/ebmlutils.cpp + matroska/ebml/ebmlvoidelement.cpp + ) +endif() + set(toolkit_SRCS toolkit/tstring.cpp toolkit/tstringlist.cpp @@ -480,11 +488,11 @@ set(toolkit_SRCS ) set(tag_LIB_SRCS - ${matroska_SRCS} ${ebml_SRCS} ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} + ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} - ${dsf_SRCS} ${dsdiff_SRCS} ${shorten_SRCS} + ${dsf_SRCS} ${dsdiff_SRCS} ${shorten_SRCS} ${matroska_SRCS} ${ebml_SRCS} tag.cpp tagunion.cpp fileref.cpp @@ -509,13 +517,8 @@ set_target_properties(tag PROPERTIES SOVERSION ${TAGLIB_SOVERSION_MAJOR} INSTALL_NAME_DIR ${CMAKE_INSTALL_FULL_LIBDIR} DEFINE_SYMBOL MAKE_TAGLIB_LIB -<<<<<<< HEAD INTERFACE_LINK_LIBRARIES "${ZLIB_INTERFACE_LINK_LIBRARIES}" PUBLIC_HEADER "${tag_HDRS}" -======= - LINK_INTERFACE_LIBRARIES "" - PUBLIC_HEADER "${tag_PUBLIC_HDRS}" ->>>>>>> 770c1012 (Initial matroska support) ) if(NOT BUILD_SHARED_LIBS) target_compile_definitions(tag PUBLIC TAGLIB_STATIC) diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 17ecc8b4..701f30a7 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -38,7 +38,6 @@ #include "tstringlist.h" #include "tvariant.h" #include "tdebug.h" -#include "matroskafile.h" #include "mpegfile.h" #ifdef TAGLIB_WITH_RIFF #include "aifffile.h" @@ -78,6 +77,9 @@ #ifdef TAGLIB_WITH_SHORTEN #include "shortenfile.h" #endif +#ifdef TAGLIB_WITH_MATROSKA +#include "matroskafile.h" +#endif using namespace TagLib; @@ -221,8 +223,10 @@ namespace else if(ext == "SHN") file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle); #endif +#ifdef TAGLIB_WITH_MATROSKA else if(ext == "MKA" || ext == "MKV" || ext == "WEBM") file = new Matroska::File(stream, readAudioProperties); +#endif // if file is not valid, leave it to content-based detection. diff --git a/taglib/matroska/ebml/ebmluintelement.cpp b/taglib/matroska/ebml/ebmluintelement.cpp index 716b770b..c49dd9f7 100644 --- a/taglib/matroska/ebml/ebmluintelement.cpp +++ b/taglib/matroska/ebml/ebmluintelement.cpp @@ -59,11 +59,10 @@ ByteVector EBML::UIntElement::render() ByteVector buffer = renderId(); buffer.append(renderVINT(dataSize, 0)); - unsigned long long value = this->value; - - static const auto byteOrder = Utils::systemByteOrder(); - if(byteOrder == Utils::LittleEndian) - value = Utils::byteSwap((unsigned long long) value); + uint64_t value = this->value; + static const auto byteOrder = Utils::systemByteOrder(); + if (byteOrder == Utils::LittleEndian) + value = Utils::byteSwap(value); buffer.append(ByteVector((char*) &value + (sizeof(value) - dataSize), dataSize)); return buffer; diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index cc898aef..cd41667c 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -97,14 +97,14 @@ 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); + if (byteOrder == Utils::LittleEndian) + number = Utils::byteSwap(number); + return ByteVector((char*) &number + (sizeof(number) - numBytes), numBytes); } unsigned long long EBML::randomUID() { - static std::random_device device; + static std::random_device device; static std::mt19937 generator(device()); static std::uniform_int_distribution distribution; return distribution(generator); diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index 9eef6f03..d8697931 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -24,6 +24,7 @@ #include #include "taglib_export.h" +#include "taglib.h" #include "tutils.h" #include "tbytevector.h" #include "tlist.h" @@ -76,4 +77,4 @@ namespace TagLib { } #endif -#endif \ No newline at end of file +#endif diff --git a/taglib/taglib_config.h.cmake b/taglib/taglib_config.h.cmake index 7a3a02c4..06e51756 100644 --- a/taglib/taglib_config.h.cmake +++ b/taglib/taglib_config.h.cmake @@ -6,6 +6,7 @@ #cmakedefine TAGLIB_WITH_APE 1 #cmakedefine TAGLIB_WITH_ASF 1 #cmakedefine TAGLIB_WITH_DSF 1 +#cmakedefine TAGLIB_WITH_MATROSKA 1 #cmakedefine TAGLIB_WITH_MOD 1 #cmakedefine TAGLIB_WITH_MP4 1 #cmakedefine TAGLIB_WITH_RIFF 1 From 6fa3db1e51535c49b66a2fb7d6855bea043829a1 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 10 Aug 2025 06:10:46 +0200 Subject: [PATCH 09/31] C bindings, fileref, fix warnings --- bindings/c/CMakeLists.txt | 6 ++ bindings/c/tag_c.cpp | 8 +++ bindings/c/tag_c.h | 3 +- taglib/fileref.cpp | 9 +++ taglib/matroska/ebml/ebmlmksegment.h | 2 - taglib/matroska/ebml/ebmlstringelement.cpp | 2 +- taglib/matroska/ebml/ebmlutils.cpp | 4 +- taglib/matroska/ebml/ebmlvoidelement.cpp | 4 +- taglib/matroska/matroskaattachedfile.h | 4 +- taglib/matroska/matroskaattachments.h | 4 +- taglib/matroska/matroskaelement.cpp | 2 +- taglib/matroska/matroskaelement.h | 2 +- taglib/matroska/matroskafile.cpp | 28 ++++++-- taglib/matroska/matroskafile.h | 33 ++++++---- taglib/matroska/matroskaproperties.cpp | 76 ++++++++++++++++++++++ taglib/matroska/matroskaproperties.h | 28 ++++---- taglib/matroska/matroskasegment.cpp | 8 +-- taglib/matroska/matroskasimpletag.h | 3 + taglib/matroska/matroskatag.h | 1 + tests/CMakeLists.txt | 10 +++ tests/test_fileref.cpp | 5 ++ tests/test_matroska.cpp | 28 ++++++++ tests/test_sizes.cpp | 11 ++++ 23 files changed, 227 insertions(+), 54 deletions(-) create mode 100644 taglib/matroska/matroskaproperties.cpp create mode 100644 tests/test_matroska.cpp diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 34c8993c..d825fde8 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -63,6 +63,12 @@ if(WITH_SHORTEN) ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/shorten ) endif() +if(WITH_MATROSKA) + set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/matroska + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/matroska/ebml + ) +endif() include_directories(${tag_c_HDR_DIRS}) set(tag_c_HDRS tag_c.h) diff --git a/bindings/c/tag_c.cpp b/bindings/c/tag_c.cpp index 35453a63..48afec7d 100644 --- a/bindings/c/tag_c.cpp +++ b/bindings/c/tag_c.cpp @@ -80,6 +80,9 @@ #ifdef TAGLIB_WITH_SHORTEN #include "shortenfile.h" #endif +#ifdef TAGLIB_WITH_MATROSKA +#include "matroskafile.h" +#endif using namespace TagLib; @@ -241,6 +244,11 @@ TagLib_File *taglib_file_new_type_any_char(const T *filename, TagLib_File_Type t case TagLib_File_SHORTEN: file = new Shorten::File(filename); break; +#endif +#ifdef TAGLIB_WITH_MATROSKA + case TagLib_File_MATROSKA: + file = new Matroska::File(filename); + break; #endif default: break; diff --git a/bindings/c/tag_c.h b/bindings/c/tag_c.h index 88c8d67b..59f7163e 100644 --- a/bindings/c/tag_c.h +++ b/bindings/c/tag_c.h @@ -132,7 +132,8 @@ typedef enum { TagLib_File_Opus, TagLib_File_DSF, TagLib_File_DSDIFF, - TagLib_File_SHORTEN + TagLib_File_SHORTEN, + TagLib_File_MATROSKA } TagLib_File_Type; /*! diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 701f30a7..a8c3404f 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -296,6 +296,10 @@ namespace else if(Shorten::File::isSupported(stream)) file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle); #endif +#ifdef TAGLIB_WITH_MATROSKA + else if(Matroska::File::isSupported(stream)) + file = new Matroska::File(stream, readAudioProperties, audioPropertiesStyle); +#endif // isSupported() only does a quick check, so double check the file here. @@ -520,6 +524,11 @@ StringList FileRef::defaultFileExtensions() #ifdef TAGLIB_WITH_SHORTEN l.append("shn"); #endif +#ifdef TAGLIB_WITH_MATROSKA + l.append("mkv"); + l.append("mka"); + l.append("webm"); +#endif return l; } diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index 41f68983..f34df50c 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -51,11 +51,9 @@ namespace TagLib { Matroska::Segment* parseSegment(); private: - offset_t dataOffset = 0; MkTags *tags = nullptr; MkAttachments *attachments = nullptr; MkSeekHead *seekHead = nullptr; - }; } } diff --git a/taglib/matroska/ebml/ebmlstringelement.cpp b/taglib/matroska/ebml/ebmlstringelement.cpp index f7bb4430..7c272352 100644 --- a/taglib/matroska/ebml/ebmlstringelement.cpp +++ b/taglib/matroska/ebml/ebmlstringelement.cpp @@ -54,7 +54,7 @@ ByteVector EBML::StringElement::render() std::string string = value.to8Bit(t == String::UTF8); dataSize = string.size(); buffer.append(renderVINT(dataSize, 0)); - buffer.append(ByteVector(string.data(), dataSize)); + buffer.append(ByteVector(string.data(), static_cast(dataSize))); return buffer; } template ByteVector EBML::StringElement::render(); diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index cd41667c..5c288b8b 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -64,7 +64,7 @@ std::pair EBML::readVINT(File &file) if(nb_bytes > 1) buffer.append(file.readBlock(nb_bytes - 1)); - int bits_to_shift = (sizeof(T) * 8) - (7 * nb_bytes); + int bits_to_shift = static_cast(sizeof(T) * 8) - (7 * nb_bytes); offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; return { nb_bytes, static_cast(buffer.toLongLong(true)) & mask }; } @@ -83,7 +83,7 @@ std::pair EBML::parseVINT(const ByteVector &buffer) if(!numBytes) return {0, 0}; - int bits_to_shift = (sizeof(T) * 8) - (7 * numBytes); + int bits_to_shift = static_cast(sizeof(T) * 8) - (7 * numBytes); offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; return { numBytes, static_cast(buffer.toLongLong(true)) & mask }; } diff --git a/taglib/matroska/ebml/ebmlvoidelement.cpp b/taglib/matroska/ebml/ebmlvoidelement.cpp index 2eae2fed..9cb54f23 100644 --- a/taglib/matroska/ebml/ebmlvoidelement.cpp +++ b/taglib/matroska/ebml/ebmlvoidelement.cpp @@ -32,12 +32,12 @@ ByteVector EBML::VoidElement::render() offset_t bytesNeeded = targetSize; ByteVector buffer = renderId(); bytesNeeded -= buffer.size(); - sizeLength = std::min(bytesNeeded, static_cast(8)); + sizeLength = static_cast(std::min(bytesNeeded, static_cast(8))); bytesNeeded -= sizeLength; dataSize = bytesNeeded; buffer.append(renderVINT(dataSize, sizeLength)); if (dataSize) - buffer.append(ByteVector(dataSize, 0)); + buffer.append(ByteVector(static_cast(dataSize), 0)); return buffer; } diff --git a/taglib/matroska/matroskaattachedfile.h b/taglib/matroska/matroskaattachedfile.h index acb62839..142196b3 100644 --- a/taglib/matroska/matroskaattachedfile.h +++ b/taglib/matroska/matroskaattachedfile.h @@ -48,10 +48,10 @@ namespace TagLib { private: class AttachedFilePrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; - }; } } -#endif \ No newline at end of file +#endif diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index dde699a0..7f75bb74 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -55,10 +55,10 @@ namespace TagLib { friend class EBML::MkAttachments; friend class Matroska::File; class AttachmentsPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; - }; } } -#endif \ No newline at end of file +#endif diff --git a/taglib/matroska/matroskaelement.cpp b/taglib/matroska/matroskaelement.cpp index 5d406a38..76181429 100644 --- a/taglib/matroska/matroskaelement.cpp +++ b/taglib/matroska/matroskaelement.cpp @@ -141,7 +141,7 @@ bool Matroska::Element::sizeChanged(Element &caller, offset_t delta) return true; } -bool Matroska::Element::offsetChanged(Element &caller, offset_t delta) +bool Matroska::Element::offsetChanged(Element &, offset_t) { // Most elements don't need to handle this return true; diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index d8697931..ec3e7863 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -64,8 +64,8 @@ namespace TagLib { private: class ElementPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr e; - }; namespace ElementIDs { inline constexpr Element::ID MkTags = 0x1254C367; diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index dc9f2f6a..eabc96e4 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -55,7 +55,22 @@ public: Segment *segment = nullptr; }; -Matroska::File::File(FileName file, bool readProperties) +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Matroska::File::isSupported(IOStream *) +{ + // TODO implement + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Matroska::File::File(FileName file, bool readProperties, + Properties::ReadStyle readStyle) : TagLib::File(file), d(std::make_unique()) { @@ -64,10 +79,11 @@ Matroska::File::File(FileName file, bool readProperties) setValid(false); return; } - read(readProperties); + read(readProperties, readStyle); } -Matroska::File::File(IOStream *stream, bool readProperties) +Matroska::File::File(IOStream *stream, bool readProperties, + Properties::ReadStyle readStyle) : TagLib::File(stream), d(std::make_unique()) { @@ -76,7 +92,7 @@ Matroska::File::File(IOStream *stream, bool readProperties) setValid(false); return; } - read(readProperties); + read(readProperties, readStyle); } Matroska::File::~File() = default; @@ -108,7 +124,7 @@ Matroska::Attachments* Matroska::File::attachments(bool create) const } } -void Matroska::File::read(bool readProperties) +void Matroska::File::read(bool, Properties::ReadStyle) { offset_t fileLength = length(); @@ -139,7 +155,7 @@ void Matroska::File::read(bool readProperties) setValid(false); return; } - + // Parse the elements d->segment = segment->parseSegment(); d->seekHead = segment->parseSeekHead(); diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index a8e99e19..b368412c 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -24,6 +24,7 @@ #include "taglib_export.h" #include "tfile.h" #include "tag.h" +#include "matroskaproperties.h" namespace TagLib { @@ -34,8 +35,10 @@ namespace TagLib { class TAGLIB_EXPORT File : public TagLib::File { public: - File(FileName file, bool readProperties = true); - File(IOStream *stream, bool readProperties = true); + File(FileName file, bool readProperties = true, + Properties::ReadStyle readStyle = Properties::Average); + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle readStyle = Properties::Average); ~File() override; File(const File &) = delete; File &operator=(const File &) = delete; @@ -46,19 +49,23 @@ namespace TagLib { bool save() override; //PropertyMap properties() const override { return PropertyMap(); } //void removeUnsupportedProperties(const StringList &properties) override { } - private: - void read(bool readProperties); - class FilePrivate; - std::unique_ptr d; + /*! + * Returns whether or not the given \a stream can be opened as a Matroska + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + + private: + void read(bool readProperties, Properties::ReadStyle readStyle); + class FilePrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; }; } } - - - - - - - #endif \ No newline at end of file + #endif diff --git a/taglib/matroska/matroskaproperties.cpp b/taglib/matroska/matroskaproperties.cpp new file mode 100644 index 00000000..3712856e --- /dev/null +++ b/taglib/matroska/matroskaproperties.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskaproperties.h" + +using namespace TagLib; + +class Matroska::Properties::PropertiesPrivate +{ +public: + int length { 0 }; + int bitrate { 0 }; + int sampleRate { 0 }; + int bitsPerSample { 0 }; + int channels { 0 }; +}; + +Matroska::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(std::make_unique()) +{ + read(file); +} + +Matroska::Properties::~Properties() = default; + +int Matroska::Properties::lengthInMilliseconds() const +{ + return d->length; +} + +int Matroska::Properties::bitrate() const +{ + return d->bitrate; +} + +int Matroska::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int Matroska::Properties::channels() const +{ + return d->channels; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Matroska::Properties::read(File *file) +{ + // TODO implement. +} diff --git a/taglib/matroska/matroskaproperties.h b/taglib/matroska/matroskaproperties.h index a3a735f3..33340be6 100644 --- a/taglib/matroska/matroskaproperties.h +++ b/taglib/matroska/matroskaproperties.h @@ -27,25 +27,17 @@ namespace TagLib { - namespace MPEG { + namespace Matroska { class File; - //! An implementation of audio property reading for MP3 - - /*! - * This reads the data from an MPEG Layer III stream found in the - * AudioProperties API. - */ - + //! An implementation of Matroska audio properties class TAGLIB_EXPORT Properties : public AudioProperties { public: - /*! - * Destroys this MPEG Properties instance. - */ - ~Properties() override {} + Properties(File *file, ReadStyle style = Average); + ~Properties() override; Properties(const Properties &) = delete; Properties &operator=(const Properties &) = delete; @@ -55,29 +47,31 @@ namespace TagLib { * * \see lengthInSeconds() */ - int lengthInMilliseconds() const override {} + int lengthInMilliseconds() const override; /*! * Returns the average bit rate of the file in kb/s. */ - int bitrate() const override {} + int bitrate() const override; /*! * Returns the sample rate in Hz. */ - int sampleRate() const override {} + int sampleRate() const override; /*! * Returns the number of audio channels. */ - int channels() const override {} + int channels() const override; private: + void read(File *file); class PropertiesPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; }; - } // namespace MPEG + } // namespace Matroska } // namespace TagLib #endif diff --git a/taglib/matroska/matroskasegment.cpp b/taglib/matroska/matroskasegment.cpp index f047e1b9..6afcac71 100644 --- a/taglib/matroska/matroskasegment.cpp +++ b/taglib/matroska/matroskasegment.cpp @@ -25,12 +25,12 @@ using namespace TagLib; bool Matroska::Segment::render() { - auto data = EBML::renderVINT(dataSize, sizeLength); + auto data = EBML::renderVINT(dataSize, static_cast(sizeLength)); if (data.size() != sizeLength) { sizeLength = 8; if (!emitSizeChanged(sizeLength - data.size())) return false; - data = EBML::renderVINT(dataSize, sizeLength); + data = EBML::renderVINT(dataSize, static_cast(sizeLength)); if (data.size() != sizeLength) return false; } @@ -38,8 +38,8 @@ bool Matroska::Segment::render() return true; } -bool Matroska::Segment::sizeChanged(Element &caller, offset_t delta) +bool Matroska::Segment::sizeChanged(Element &, offset_t delta) { dataSize += delta; return true; -} \ No newline at end of file +} diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index 74553a97..484decd5 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -54,6 +54,7 @@ namespace TagLib { private: class SimpleTagPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; protected: @@ -70,6 +71,7 @@ namespace TagLib { private: class SimpleTagStringPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr dd; }; @@ -83,6 +85,7 @@ namespace TagLib { private: class SimpleTagBinaryPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr dd; }; diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 2370faec..5424010e 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -125,6 +125,7 @@ namespace TagLib { bool setTag(const String &key, const String &value); const String* getTag(const String &key) const; class TagPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; }; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 25c6e1da..2d4a0c5d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,6 +65,11 @@ IF(WITH_SHORTEN) ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/shorten ) ENDIF() +IF(WITH_MATROSKA) + SET(test_HDR_DIRS ${test_HDR_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/matroska + ) +ENDIF() INCLUDE_DIRECTORIES(${test_HDR_DIRS}) SET(test_runner_SRCS @@ -152,6 +157,11 @@ IF(WITH_SHORTEN) test_shorten.cpp ) ENDIF() +IF(WITH_MATROSKA) + SET(test_runner_SRCS ${test_runner_SRCS} + test_matroska.cpp + ) +ENDIF() IF(BUILD_BINDINGS) SET(test_runner_SRCS ${test_runner_SRCS} test_tag_c.cpp diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index 5c3e4e0d..34adfd78 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -472,6 +472,11 @@ public: #endif #ifdef TAGLIB_WITH_SHORTEN CPPUNIT_ASSERT(extensions.contains("shn")); +#endif +#ifdef TAGLIB_WITH_MATROSKA + CPPUNIT_ASSERT(extensions.contains("mkv")); + CPPUNIT_ASSERT(extensions.contains("mka")); + CPPUNIT_ASSERT(extensions.contains("webm")); #endif } diff --git a/tests/test_matroska.cpp b/tests/test_matroska.cpp new file mode 100644 index 00000000..d1fc3c0a --- /dev/null +++ b/tests/test_matroska.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "tbytevectorlist.h" +#include "tpropertymap.h" +#include "tag.h" +#include "matroskafile.h" +#include "plainfile.h" +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestMatroska : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMatroska); + CPPUNIT_TEST(testTags); + CPPUNIT_TEST_SUITE_END(); + +public: + void testTags() + { + // TODO implement + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMatroska); diff --git a/tests/test_sizes.cpp b/tests/test_sizes.cpp index 0bf03cce..9bf0ee93 100644 --- a/tests/test_sizes.cpp +++ b/tests/test_sizes.cpp @@ -148,6 +148,11 @@ #include "trueaudiofile.h" #include "trueaudioproperties.h" #endif +#ifdef TAGLIB_WITH_MATROSKA +#include "matroskafile.h" +#include "matroskaproperties.h" +#include "matroskatag.h" +#endif #include @@ -297,6 +302,12 @@ public: #ifdef TAGLIB_WITH_TRUEAUDIO CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::TrueAudio::File)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::TrueAudio::Properties)); +#endif +#ifdef TAGLIB_WITH_MATROSKA + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::File)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Properties)); + // TODO move non pimpl fields into private class. + // CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Tag)); #endif } From 5a6f1f96f84b9d38773340aecf222fe5d9c6ae45 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 10 Aug 2025 07:46:53 +0200 Subject: [PATCH 10/31] Apply TagLib code formatting, fix static analysis issues --- examples/matroskareader.cpp | 4 +- taglib/CMakeLists.txt | 15 ++- taglib/matroska/ebml/ebmlbinaryelement.cpp | 7 +- taglib/matroska/ebml/ebmlbinaryelement.h | 20 ++-- taglib/matroska/ebml/ebmlelement.cpp | 93 ++++++++------- taglib/matroska/ebml/ebmlelement.h | 125 ++++++++++++--------- taglib/matroska/ebml/ebmlmasterelement.cpp | 8 +- taglib/matroska/ebml/ebmlmasterelement.h | 64 +++++------ taglib/matroska/ebml/ebmlmkattachments.cpp | 18 ++- taglib/matroska/ebml/ebmlmkattachments.h | 26 +++-- taglib/matroska/ebml/ebmlmkcues.cpp | 59 +++++----- taglib/matroska/ebml/ebmlmkcues.h | 26 +++-- taglib/matroska/ebml/ebmlmkseekhead.cpp | 8 +- taglib/matroska/ebml/ebmlmkseekhead.h | 24 ++-- taglib/matroska/ebml/ebmlmksegment.cpp | 23 ++-- taglib/matroska/ebml/ebmlmksegment.h | 22 ++-- taglib/matroska/ebml/ebmlmktags.cpp | 27 ++--- taglib/matroska/ebml/ebmlmktags.h | 26 +++-- taglib/matroska/ebml/ebmlstringelement.cpp | 13 ++- taglib/matroska/ebml/ebmlstringelement.h | 21 ++-- taglib/matroska/ebml/ebmluintelement.cpp | 6 +- taglib/matroska/ebml/ebmluintelement.h | 20 ++-- taglib/matroska/ebml/ebmlutils.cpp | 18 +-- taglib/matroska/ebml/ebmlutils.h | 24 +--- taglib/matroska/ebml/ebmlvoidelement.cpp | 3 +- taglib/matroska/ebml/ebmlvoidelement.h | 15 +-- taglib/matroska/matroskaattachedfile.cpp | 15 ++- taglib/matroska/matroskaattachedfile.h | 9 +- taglib/matroska/matroskaattachments.cpp | 15 ++- taglib/matroska/matroskaattachments.h | 17 ++- taglib/matroska/matroskacues.cpp | 47 ++++---- taglib/matroska/matroskacues.h | 31 ++--- taglib/matroska/matroskaelement.cpp | 24 ++-- taglib/matroska/matroskaelement.h | 11 +- taglib/matroska/matroskafile.cpp | 64 +++++------ taglib/matroska/matroskafile.h | 75 ++++++------- taglib/matroska/matroskaproperties.cpp | 2 +- taglib/matroska/matroskaproperties.h | 75 ++++++------- taglib/matroska/matroskaseekhead.cpp | 40 +++---- taglib/matroska/matroskaseekhead.h | 7 +- taglib/matroska/matroskasegment.cpp | 6 +- taglib/matroska/matroskasegment.h | 41 ++++--- taglib/matroska/matroskasimpletag.cpp | 49 ++++---- taglib/matroska/matroskasimpletag.h | 8 +- taglib/matroska/matroskatag.cpp | 98 ++++++++-------- taglib/matroska/matroskatag.h | 28 ++--- 46 files changed, 655 insertions(+), 722 deletions(-) diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp index 76f8891a..070fc75d 100644 --- a/examples/matroskareader.cpp +++ b/examples/matroskareader.cpp @@ -29,7 +29,7 @@ int main(int argc, char *argv[]) const TagLib::Matroska::SimpleTagsList &list = tag->simpleTagsList(); printf("Found %u tag(s):\n", list.size()); - + for(TagLib::Matroska::SimpleTag *t : list) { PRINT_PRETTY("Tag Name", t->name().toCString(true)); @@ -38,7 +38,7 @@ int main(int argc, char *argv[]) if((tString = dynamic_cast(t))) PRINT_PRETTY("Tag Value", tString->value().toCString(true)); else if((tBinary = dynamic_cast(t))) - PRINT_PRETTY("Tag Value", + PRINT_PRETTY("Tag Value", TagLib::Utils::formatString("Binary with size %i", tBinary->value().size()).toCString(false) ); diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 20fd4331..778b1d9c 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -228,21 +228,21 @@ if(WITH_SHORTEN) endif() if(WITH_MATROSKA) set(tag_HDRS ${tag_HDRS} - matroska/matroskafile.h - matroska/matroskatag.h - matroska/matroskasimpletag.h matroska/matroskaattachedfile.h matroska/matroskaattachments.h - matroska/matroskafile.h - matroska/matroskatag.h - matroska/matroskasimpletag.h + matroska/matroskacues.h matroska/matroskaelement.h + matroska/matroskafile.h + matroska/matroskaproperties.h matroska/matroskaseekhead.h matroska/matroskasegment.h + matroska/matroskasimpletag.h + matroska/matroskatag.h matroska/ebml/ebmlbinaryelement.h matroska/ebml/ebmlelement.h matroska/ebml/ebmlmasterelement.h matroska/ebml/ebmlmkattachments.h + matroska/ebml/ebmlmkcues.h matroska/ebml/ebmlmkseekhead.h matroska/ebml/ebmlmksegment.h matroska/ebml/ebmlmktags.h @@ -446,8 +446,10 @@ if(WITH_MATROSKA) set(matroska_SRCS matroska/matroskaattachedfile.cpp matroska/matroskaattachments.cpp + matroska/matroskacues.cpp matroska/matroskaelement.cpp matroska/matroskafile.cpp + matroska/matroskaproperties.cpp matroska/matroskaseekhead.cpp matroska/matroskasegment.cpp matroska/matroskasimpletag.cpp @@ -459,6 +461,7 @@ if(WITH_MATROSKA) matroska/ebml/ebmlelement.cpp matroska/ebml/ebmlmasterelement.cpp matroska/ebml/ebmlmkattachments.cpp + matroska/ebml/ebmlmkcues.cpp matroska/ebml/ebmlmkseekhead.cpp matroska/ebml/ebmlmksegment.cpp matroska/ebml/ebmlmktags.cpp diff --git a/taglib/matroska/ebml/ebmlbinaryelement.cpp b/taglib/matroska/ebml/ebmlbinaryelement.cpp index 74fa1e3a..81e35cbc 100644 --- a/taglib/matroska/ebml/ebmlbinaryelement.cpp +++ b/taglib/matroska/ebml/ebmlbinaryelement.cpp @@ -19,14 +19,11 @@ ***************************************************************************/ #include "ebmlbinaryelement.h" -#include "tfile.h" -#include "tbytevector.h" -#include "tdebug.h" -#include +#include "ebmlutils.h" using namespace TagLib; -bool EBML::BinaryElement::read(TagLib::File &file) +bool EBML::BinaryElement::read(File &file) { value = file.readBlock(dataSize); if(value.size() != dataSize) { diff --git a/taglib/matroska/ebml/ebmlbinaryelement.h b/taglib/matroska/ebml/ebmlbinaryelement.h index 4e8bcb3d..b8dd686e 100644 --- a/taglib/matroska/ebml/ebmlbinaryelement.h +++ b/taglib/matroska/ebml/ebmlbinaryelement.h @@ -22,10 +22,7 @@ #define TAGLIB_EBMLBINARYELEMENT_H #ifndef DO_NOT_DOCUMENT -#include #include "ebmlelement.h" -#include "ebmlutils.h" -#include "tbytevector.h" namespace TagLib { class File; @@ -33,13 +30,16 @@ namespace TagLib { class BinaryElement : public Element { public: - BinaryElement(Id id, int sizeLength, offset_t dataSize) - : Element(id, sizeLength, dataSize) - {} - BinaryElement(Id id) - : Element(id, 0, 0) - {} - const ByteVector& getValue() const { return value; } + BinaryElement(Id id, int sizeLength, offset_t dataSize) : + Element(id, sizeLength, dataSize) + { + } + + explicit BinaryElement(Id id) : + Element(id, 0, 0) + { + } + const ByteVector &getValue() const { return value; } void setValue(const ByteVector &value) { this->value = value; } bool read(File &file) override; ByteVector render() override; diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index a15b755b..e8bdbea3 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -37,7 +37,7 @@ using namespace TagLib; -EBML::Element* EBML::Element::factory(File &file) +EBML::Element *EBML::Element::factory(File &file) { // Get the element ID offset_t offset = file.tell(); @@ -48,61 +48,59 @@ EBML::Element* EBML::Element::factory(File &file) } // Get the size length and data length - const auto& [sizeLength, dataSize] = readVINT(file); + const auto &[sizeLength, dataSize] = readVINT(file); if(!sizeLength) return nullptr; // Return the subclass switch(id) { - case ElementIDs::EBMLHeader: - return new Element(id, sizeLength, dataSize); - - case ElementIDs::MkSegment: - return new MkSegment(sizeLength, dataSize, offset); + case ElementIDs::EBMLHeader: + return new Element(id, sizeLength, dataSize); - case ElementIDs::MkTags: - return new MkTags(sizeLength, dataSize, offset); + case ElementIDs::MkSegment: + return new MkSegment(sizeLength, dataSize, offset); - case ElementIDs::MkAttachments: - return new MkAttachments(sizeLength, dataSize, offset); + case ElementIDs::MkTags: + return new MkTags(sizeLength, dataSize, offset); - case ElementIDs::MkTag: - case ElementIDs::MkTagTargets: - case ElementIDs::MkSimpleTag: - case ElementIDs::MkAttachedFile: - case ElementIDs::MkSeek: - return new MasterElement(id, sizeLength, dataSize, offset); + case ElementIDs::MkAttachments: + return new MkAttachments(sizeLength, dataSize, offset); - case ElementIDs::MkTagName: - case ElementIDs::MkTagString: - case ElementIDs::MkAttachedFileName: - case ElementIDs::MkAttachedFileDescription: - return new UTF8StringElement(id, sizeLength, dataSize); + case ElementIDs::MkTag: + case ElementIDs::MkTagTargets: + case ElementIDs::MkSimpleTag: + case ElementIDs::MkAttachedFile: + case ElementIDs::MkSeek: + return new MasterElement(id, sizeLength, dataSize, offset); - case ElementIDs::MkTagLanguage: - case ElementIDs::MkAttachedFileMediaType: - return new Latin1StringElement(id, sizeLength, dataSize); + case ElementIDs::MkTagName: + case ElementIDs::MkTagString: + case ElementIDs::MkAttachedFileName: + case ElementIDs::MkAttachedFileDescription: + return new UTF8StringElement(id, sizeLength, dataSize); - case ElementIDs::MkTagTargetTypeValue: - case ElementIDs::MkAttachedFileUID: - case ElementIDs::MkSeekPosition: - return new UIntElement(id, sizeLength, dataSize); + case ElementIDs::MkTagLanguage: + case ElementIDs::MkAttachedFileMediaType: + return new Latin1StringElement(id, sizeLength, dataSize); - case ElementIDs::MkAttachedFileData: - case ElementIDs::MkSeekID: - return new BinaryElement(id, sizeLength, dataSize); - - case ElementIDs::MkSeekHead: - return new MkSeekHead(sizeLength, dataSize, offset); + case ElementIDs::MkTagTargetTypeValue: + case ElementIDs::MkAttachedFileUID: + case ElementIDs::MkSeekPosition: + return new UIntElement(id, sizeLength, dataSize); - case ElementIDs::VoidElement: - return new VoidElement(sizeLength, dataSize); + case ElementIDs::MkAttachedFileData: + case ElementIDs::MkSeekID: + return new BinaryElement(id, sizeLength, dataSize); - default: - return new Element(id, sizeLength, dataSize); + case ElementIDs::MkSeekHead: + return new MkSeekHead(sizeLength, dataSize, offset); + + case ElementIDs::VoidElement: + return new VoidElement(sizeLength, dataSize); + + default: + return new Element(id, sizeLength, dataSize); } - - return nullptr; } EBML::Element::Id EBML::Element::readId(File &file) @@ -124,14 +122,14 @@ EBML::Element::Id EBML::Element::readId(File &file) return buffer.toUInt(true); } -void EBML::Element::skipData(File &file) +void EBML::Element::skipData(File &file) { file.seek(dataSize, File::Position::Current); } offset_t EBML::Element::headSize() const -{ - return EBML::idSize(id) + sizeLength; +{ + return idSize(id) + sizeLength; } ByteVector EBML::Element::render() @@ -141,9 +139,10 @@ ByteVector EBML::Element::render() return buffer; } -ByteVector EBML::Element::renderId() +ByteVector EBML::Element::renderId() const { int numBytes = idSize(id); - id = Utils::byteSwap(id); - return ByteVector((char*) &id + (4 - numBytes), numBytes); + static const auto byteOrder = Utils::systemByteOrder(); + uint32_t data = byteOrder == Utils::LittleEndian ? Utils::byteSwap(id) : id; + return ByteVector(reinterpret_cast(&data) + (4 - numBytes), numBytes); } diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index d9a6c9e0..55141cc8 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -26,64 +26,77 @@ #include "tutils.h" #include "taglib.h" -namespace TagLib { - namespace EBML { - class Element +namespace TagLib::EBML { + class Element + { + public: + using Id = unsigned int; + Element(Id id, int sizeLength, offset_t dataSize) : + id(id), sizeLength(sizeLength), dataSize(dataSize) { - public: - using Id = unsigned int; - Element(Id id, int sizeLength, offset_t dataSize) - : id(id), sizeLength(sizeLength), dataSize(dataSize) - {} - virtual ~Element() = default; - virtual bool read(File &file) { - skipData(file); - return true; - } - void skipData(File &file); - Id getId() const { return id; } - offset_t headSize() const; - offset_t getSize() const { return headSize() + dataSize; } - 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 VoidElement = 0xEC; - 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; - inline constexpr Element::Id MkTagsTagLanguage = 0x447A; - inline constexpr Element::Id MkTagsLanguageDefault = 0x4484; - inline constexpr Element::Id MkAttachments = 0x1941A469; - inline constexpr Element::Id MkAttachedFile = 0x61A7; - inline constexpr Element::Id MkAttachedFileDescription = 0x467E; - inline constexpr Element::Id MkAttachedFileName = 0x466E; - inline constexpr Element::Id MkAttachedFileMediaType = 0x4660; - inline constexpr Element::Id MkAttachedFileData = 0x465C; - inline constexpr Element::Id MkAttachedFileUID = 0x46AE; - inline constexpr Element::Id MkSeekHead = 0x114D9B74; - inline constexpr Element::Id MkSeek = 0x4DBB; - inline constexpr Element::Id MkSeekID = 0x53AB; - inline constexpr Element::Id MkSeekPosition = 0x53AC; - } + virtual ~Element() = default; + virtual bool read(File &file) + { + skipData(file); + return true; + } + void skipData(File &file); + Id getId() const { return id; } + offset_t headSize() const; + offset_t getSize() const { return headSize() + dataSize; } + int getSizeLength() const { return sizeLength; } + int64_t getDataSize() const { return dataSize; } + ByteVector renderId() const; + 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 VoidElement = 0xEC; + 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; + inline constexpr Element::Id MkTagsTagLanguage = 0x447A; + inline constexpr Element::Id MkTagsLanguageDefault = 0x4484; + inline constexpr Element::Id MkAttachments = 0x1941A469; + inline constexpr Element::Id MkAttachedFile = 0x61A7; + inline constexpr Element::Id MkAttachedFileDescription = 0x467E; + inline constexpr Element::Id MkAttachedFileName = 0x466E; + inline constexpr Element::Id MkAttachedFileMediaType = 0x4660; + inline constexpr Element::Id MkAttachedFileData = 0x465C; + inline constexpr Element::Id MkAttachedFileUID = 0x46AE; + inline constexpr Element::Id MkSeekHead = 0x114D9B74; + inline constexpr Element::Id MkSeek = 0x4DBB; + inline constexpr Element::Id MkSeekID = 0x53AB; + inline constexpr Element::Id MkSeekPosition = 0x53AC; + inline constexpr Element::Id MkCluster = 0x1F43B675; + inline constexpr Element::Id MkCodecState = 0xA4; + inline constexpr Element::Id MkCues = 0x1C53BB6B; + inline constexpr Element::Id MkCuePoint = 0xBB; + inline constexpr Element::Id MkCueTime = 0xB3; + inline constexpr Element::Id MkCueTrackPositions = 0xB7; + inline constexpr Element::Id MkCueTrack = 0xF7; + inline constexpr Element::Id MkCueClusterPosition = 0xF1; + inline constexpr Element::Id MkCueRelativePosition = 0xF0; + inline constexpr Element::Id MkCueDuration = 0xB2; + inline constexpr Element::Id MkCueBlockNumber = 0x5378; + inline constexpr Element::Id MkCueCodecState = 0xEA; + inline constexpr Element::Id MkCueReference = 0xDB; + inline constexpr Element::Id MkCueRefTime = 0x96; } } diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp index 73fe6f0f..cfaea47e 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.cpp +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -21,11 +21,7 @@ #include "ebmlmasterelement.h" #include "ebmlvoidelement.h" #include "ebmlutils.h" -#include "matroskafile.h" - #include "tfile.h" -#include "tdebug.h" -#include "tutils.h" using namespace TagLib; @@ -56,9 +52,9 @@ ByteVector EBML::MasterElement::render() dataSize = data.size(); buffer.append(renderVINT(dataSize, 0)); buffer.append(data); - if (minRenderSize) { + if(minRenderSize) { auto bufferSize = buffer.size(); - if(minRenderSize >= (bufferSize + MIN_VOID_ELEMENT_SIZE)) + if(minRenderSize >= bufferSize + MIN_VOID_ELEMENT_SIZE) buffer.append(VoidElement::renderSize(minRenderSize - bufferSize)); } return buffer; diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h index 1f0b2a1d..cdccee75 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.h +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -22,48 +22,46 @@ #define TAGLIB_EBMLMASTERELEMENT_H #ifndef DO_NOT_DOCUMENT -#include "ebmlutils.h" #include "ebmlelement.h" #include "tbytevector.h" #include "tlist.h" #include "taglib.h" -namespace TagLib { - namespace EBML { - class MasterElement : public Element +namespace TagLib::EBML { + class MasterElement : public Element + { + public: + MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset) : + Element(id, sizeLength, dataSize), offset(offset) { - public: - MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset) - : Element(id, sizeLength, dataSize), offset(offset) - {} - MasterElement(Id id) - : Element(id, 0, 0), offset(0) - {} - ~MasterElement() override; - offset_t getOffset() const { return offset; } - 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(); } - List::ConstIterator cend () const { return elements.cend(); } - offset_t getPadding() const { return padding; } - void setPadding(offset_t padding) { this->padding = padding; } - offset_t getMinRenderSize() const { return minRenderSize; } - void setMinRenderSize(offset_t minRenderSize) { this->minRenderSize = minRenderSize; } + } + explicit MasterElement(Id id) : + Element(id, 0, 0), offset(0) + { + } + ~MasterElement() override; + offset_t getOffset() const { return offset; } + 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(); } + List::ConstIterator cend() const { return elements.cend(); } + offset_t getPadding() const { return padding; } + void setPadding(offset_t padding) { this->padding = padding; } + offset_t getMinRenderSize() const { return minRenderSize; } + void setMinRenderSize(offset_t minRenderSize) { this->minRenderSize = minRenderSize; } + + protected: + offset_t offset; + offset_t padding = 0; + offset_t minRenderSize = 0; + List elements; + }; - protected: - offset_t offset; - offset_t padding = 0; - offset_t minRenderSize = 0; - List elements; - }; - - } } - #endif #endif diff --git a/taglib/matroska/ebml/ebmlmkattachments.cpp b/taglib/matroska/ebml/ebmlmkattachments.cpp index cfa681b9..35c35700 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.cpp +++ b/taglib/matroska/ebml/ebmlmkattachments.cpp @@ -24,12 +24,10 @@ #include "ebmlbinaryelement.h" #include "matroskaattachments.h" #include "matroskaattachedfile.h" -#include "tdebug.h" -#include "tutils.h" using namespace TagLib; -Matroska::Attachments* EBML::MkAttachments::parse() +Matroska::Attachments *EBML::MkAttachments::parse() { auto attachments = new Matroska::Attachments(); attachments->setOffset(offset); @@ -44,19 +42,19 @@ Matroska::Attachments* EBML::MkAttachments::parse() const String *mediaType = nullptr; const ByteVector *data = nullptr; Matroska::AttachedFile::UID uid = 0; - auto attachedFile = static_cast(element); + auto attachedFile = static_cast(element); for(auto attachedFileChild : *attachedFile) { Id id = attachedFileChild->getId(); if(id == ElementIDs::MkAttachedFileName) - filename = &(static_cast(attachedFileChild)->getValue()); + filename = &(static_cast(attachedFileChild)->getValue()); else if(id == ElementIDs::MkAttachedFileData) - data = &(static_cast(attachedFileChild)->getValue()); + data = &(static_cast(attachedFileChild)->getValue()); else if(id == ElementIDs::MkAttachedFileDescription) - description = &(static_cast(attachedFileChild)->getValue()); + description = &(static_cast(attachedFileChild)->getValue()); else if(id == ElementIDs::MkAttachedFileMediaType) - mediaType = &(static_cast(attachedFileChild)->getValue()); + mediaType = &(static_cast(attachedFileChild)->getValue()); else if(id == ElementIDs::MkAttachedFileUID) - uid = static_cast(attachedFileChild)->getValue(); + uid = static_cast(attachedFileChild)->getValue(); } if(!(filename && data)) continue; @@ -70,7 +68,7 @@ Matroska::Attachments* EBML::MkAttachments::parse() file->setMediaType(*mediaType); if(uid) file->setUID(uid); - + attachments->addAttachedFile(file); } return attachments; diff --git a/taglib/matroska/ebml/ebmlmkattachments.h b/taglib/matroska/ebml/ebmlmkattachments.h index dcdcc71e..228493d5 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.h +++ b/taglib/matroska/ebml/ebmlmkattachments.h @@ -18,14 +18,14 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "ebmlmasterelement.h" -#include "ebmlutils.h" -#include "taglib.h" - #ifndef TAGLIB_EBMLMKATTACHMENTS_H #define TAGLIB_EBMLMKATTACHMENTS_H #ifndef DO_NOT_DOCUMENT +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + namespace TagLib { namespace Matroska { class Attachments; @@ -34,16 +34,18 @@ namespace TagLib { class MkAttachments : public MasterElement { public: - MkAttachments(int sizeLength, offset_t dataSize, offset_t offset) - : MasterElement(ElementIDs::MkAttachments, sizeLength, dataSize, offset) - {} - MkAttachments() - : MasterElement(ElementIDs::MkAttachments, 0, 0, 0) - {} - Matroska::Attachments* parse(); - + MkAttachments(int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(ElementIDs::MkAttachments, sizeLength, dataSize, offset) + { + } + MkAttachments() : + MasterElement(ElementIDs::MkAttachments, 0, 0, 0) + { + } + Matroska::Attachments *parse(); }; } } + #endif #endif diff --git a/taglib/matroska/ebml/ebmlmkcues.cpp b/taglib/matroska/ebml/ebmlmkcues.cpp index cf93a41f..c0d1bc10 100644 --- a/taglib/matroska/ebml/ebmlmkcues.cpp +++ b/taglib/matroska/ebml/ebmlmkcues.cpp @@ -20,55 +20,50 @@ #include "ebmlmkcues.h" #include "ebmluintelement.h" -#include "ebmlstringelement.h" -#include "ebmlutils.h" -#include "matroskafile.h" #include "matroskacues.h" -#include "tdebug.h" -#include "tutils.h" using namespace TagLib; -Matroska::Cues* EBML::MkCues::parse() +Matroska::Cues *EBML::MkCues::parse() { auto cues = new Matroska::Cues(); cues->setOffset(offset); cues->setSize(getSize()); cues->setID(id); - for (auto cuesChild : elements) { - if (cuesChild->getId() != ElementIDs::MkCuePoint) + for(auto cuesChild : elements) { + if(cuesChild->getId() != ElementIDs::MkCuePoint) continue; - auto cuePointElement = static_cast(cuesChild); + auto cuePointElement = static_cast(cuesChild); auto cuePoint = new Matroska::CuePoint(); - for (auto cuePointChild : *cuePointElement) { + for(auto cuePointChild : *cuePointElement) { Id id = cuePointChild->getId(); - if (id == ElementIDs::MkCueTime) - cuePoint->setTime(static_cast(cuePointChild)->getValue()); - else if (id == ElementIDs::MkCueTrackPositions) { + if(id == ElementIDs::MkCueTime) + cuePoint->setTime(static_cast(cuePointChild)->getValue()); + else if(id == ElementIDs::MkCueTrackPositions) { auto cueTrack = new Matroska::CueTrack(); - auto cueTrackElement = static_cast(cuePointChild); - for (auto cueTrackChild : *cueTrackElement) { + auto cueTrackElement = static_cast(cuePointChild); + for(auto cueTrackChild : *cueTrackElement) { Id trackId = cueTrackChild->getId(); - if (trackId == ElementIDs::MkCueTrack) - cueTrack->setTrackNumber(static_cast(cueTrackChild)->getValue()); - else if (trackId == ElementIDs::MkCueClusterPosition) - cueTrack->setClusterPosition(static_cast(cueTrackChild)->getValue()); - else if (trackId == ElementIDs::MkCueRelativePosition) - cueTrack->setRelativePosition(static_cast(cueTrackChild)->getValue()); - else if (trackId == ElementIDs::MkCueDuration) - cueTrack->setDuration(static_cast(cueTrackChild)->getValue()); - else if (trackId == ElementIDs::MkCueBlockNumber) - cueTrack->setBlockNumber(static_cast(cueTrackChild)->getValue()); - else if (trackId == ElementIDs::MkCueCodecState) - cueTrack->setCodecState(static_cast(cueTrackChild)->getValue()); - else if (trackId == ElementIDs::MkCueReference) { - auto cueReference = static_cast(cueTrackChild); - for (auto cueReferenceChild : *cueReference) { - if (cueReferenceChild->getId() != ElementIDs::MkCueReference) + if(trackId == ElementIDs::MkCueTrack) + cueTrack->setTrackNumber(static_cast(cueTrackChild)->getValue()); + else if(trackId == ElementIDs::MkCueClusterPosition) + cueTrack->setClusterPosition(static_cast(cueTrackChild)->getValue()); + else if(trackId == ElementIDs::MkCueRelativePosition) + cueTrack->setRelativePosition(static_cast(cueTrackChild)->getValue()); + else if(trackId == ElementIDs::MkCueDuration) + cueTrack->setDuration(static_cast(cueTrackChild)->getValue()); + else if(trackId == ElementIDs::MkCueBlockNumber) + cueTrack->setBlockNumber(static_cast(cueTrackChild)->getValue()); + else if(trackId == ElementIDs::MkCueCodecState) + cueTrack->setCodecState(static_cast(cueTrackChild)->getValue()); + else if(trackId == ElementIDs::MkCueReference) { + auto cueReference = static_cast(cueTrackChild); + for(auto cueReferenceChild : *cueReference) { + if(cueReferenceChild->getId() != ElementIDs::MkCueReference) continue; - cueTrack->addReferenceTime(static_cast(cueReferenceChild)->getValue()); + cueTrack->addReferenceTime(static_cast(cueReferenceChild)->getValue()); } } } diff --git a/taglib/matroska/ebml/ebmlmkcues.h b/taglib/matroska/ebml/ebmlmkcues.h index 78a194dc..2d31826e 100644 --- a/taglib/matroska/ebml/ebmlmkcues.h +++ b/taglib/matroska/ebml/ebmlmkcues.h @@ -18,14 +18,14 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "ebmlmasterelement.h" -#include "ebmlutils.h" -#include "taglib.h" - #ifndef TAGLIB_EBMLMKCUES_H #define TAGLIB_EBMLMKCUES_H #ifndef DO_NOT_DOCUMENT +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + namespace TagLib { namespace Matroska { class Cues; @@ -35,17 +35,19 @@ namespace TagLib { class MkCues : public MasterElement { public: - MkCues(int sizeLength, offset_t dataSize, offset_t offset) - : MasterElement(ElementIDs::MkCues, sizeLength, dataSize, offset) - {} - MkCues() - : MasterElement(ElementIDs::MkCues, 0, 0, 0) - {} - - Matroska::Cues* parse(); + MkCues(int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(ElementIDs::MkCues, sizeLength, dataSize, offset) + { + } + MkCues() : + MasterElement(ElementIDs::MkCues, 0, 0, 0) + { + } + Matroska::Cues *parse(); }; } } + #endif #endif diff --git a/taglib/matroska/ebml/ebmlmkseekhead.cpp b/taglib/matroska/ebml/ebmlmkseekhead.cpp index d79997ef..144d94e8 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.cpp +++ b/taglib/matroska/ebml/ebmlmkseekhead.cpp @@ -25,7 +25,7 @@ using namespace TagLib; -Matroska::SeekHead* EBML::MkSeekHead::parse() +Matroska::SeekHead *EBML::MkSeekHead::parse() { auto seekHead = new Matroska::SeekHead(); seekHead->setOffset(offset); @@ -34,18 +34,18 @@ Matroska::SeekHead* EBML::MkSeekHead::parse() for(auto element : elements) { if(element->getId() != ElementIDs::MkSeek) continue; - auto seekElement = static_cast(element); + auto seekElement = static_cast(element); Matroska::Element::ID entryId = 0; offset_t offset = 0; for(auto seekElementChild : *seekElement) { Id id = seekElementChild->getId(); if(id == ElementIDs::MkSeekID && !entryId) { - auto data = static_cast(seekElementChild)->getValue(); + auto data = static_cast(seekElementChild)->getValue(); if(data.size() == 4) entryId = data.toUInt(true); } else if(id == ElementIDs::MkSeekPosition && !offset) - offset = static_cast(seekElementChild)->getValue(); + offset = static_cast(seekElementChild)->getValue(); } if(entryId && offset) seekHead->addEntry(entryId, offset); diff --git a/taglib/matroska/ebml/ebmlmkseekhead.h b/taglib/matroska/ebml/ebmlmkseekhead.h index c07ca31f..bf6b8edf 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.h +++ b/taglib/matroska/ebml/ebmlmkseekhead.h @@ -18,13 +18,13 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "ebmlmasterelement.h" -#include "taglib.h" - #ifndef TAGLIB_EBMLMKSEEKHEAD_H #define TAGLIB_EBMLMKSEEKHEAD_H #ifndef DO_NOT_DOCUMENT +#include "ebmlmasterelement.h" +#include "taglib.h" + namespace TagLib { namespace Matroska { class SeekHead; @@ -33,17 +33,19 @@ namespace TagLib { class MkSeekHead : public MasterElement { public: - MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset) - : MasterElement(ElementIDs::MkSeekHead, sizeLength, dataSize, offset) - {} - MkSeekHead() - : MasterElement(ElementIDs::MkSeekHead, 0, 0, 0) - {} - - Matroska::SeekHead* parse(); + MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(ElementIDs::MkSeekHead, sizeLength, dataSize, offset) + { + } + MkSeekHead() : + MasterElement(ElementIDs::MkSeekHead, 0, 0, 0) + { + } + Matroska::SeekHead *parse(); }; } } + #endif #endif diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index 759636bc..de77a64c 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -28,9 +28,6 @@ #include "matroskaattachments.h" #include "matroskaseekhead.h" #include "matroskasegment.h" -#include "tutils.h" -#include "tbytevector.h" -#include "tdebug.h" using namespace TagLib; @@ -44,31 +41,31 @@ EBML::MkSegment::~MkSegment() bool EBML::MkSegment::read(File &file) { offset_t maxOffset = file.tell() + dataSize; - EBML::Element *element = nullptr; + Element *element = nullptr; int i = 0; int seekHeadIndex = -1; while((element = findNextElement(file, maxOffset))) { Id id = element->getId(); if(id == ElementIDs::MkSeekHead) { seekHeadIndex = i; - seekHead = static_cast(element); + seekHead = static_cast(element); if(!seekHead->read(file)) return false; } else if(id == ElementIDs::MkTags) { - tags = static_cast(element); + tags = static_cast(element); if(!tags->read(file)) return false; } else if(id == ElementIDs::MkAttachments) { - attachments = static_cast(element); + attachments = static_cast(element); if(!attachments->read(file)) return false; } else { if(id == ElementIDs::VoidElement - && seekHead - && seekHeadIndex == (i - 1)) + && seekHead + && seekHeadIndex == i - 1) seekHead->setPadding(element->getSize()); element->skipData(file); @@ -79,22 +76,22 @@ bool EBML::MkSegment::read(File &file) return true; } -Matroska::Tag* EBML::MkSegment::parseTag() +Matroska::Tag *EBML::MkSegment::parseTag() { return tags ? tags->parse() : nullptr; } -Matroska::Attachments* EBML::MkSegment::parseAttachments() +Matroska::Attachments *EBML::MkSegment::parseAttachments() { return attachments ? attachments->parse() : nullptr; } -Matroska::SeekHead* EBML::MkSegment::parseSeekHead() +Matroska::SeekHead *EBML::MkSegment::parseSeekHead() { return seekHead ? seekHead->parse() : nullptr; } -Matroska::Segment* EBML::MkSegment::parseSegment() +Matroska::Segment *EBML::MkSegment::parseSegment() { return new Matroska::Segment(sizeLength, dataSize, offset + idSize(id)); } diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index f34df50c..5ea0960c 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -18,14 +18,13 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "ebmlmasterelement.h" -#include "taglib.h" -#include - #ifndef TAGLIB_EBMLMKSEGMENT_H #define TAGLIB_EBMLMKSEGMENT_H #ifndef DO_NOT_DOCUMENT +#include "ebmlmasterelement.h" +#include "taglib.h" + namespace TagLib { namespace Matroska { class Tag; @@ -40,15 +39,16 @@ namespace TagLib { class MkSegment : public MasterElement { public: - MkSegment(int sizeLength, offset_t dataSize, offset_t offset) - : MasterElement(ElementIDs::MkSegment, sizeLength, dataSize, offset) - {} + MkSegment(int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(ElementIDs::MkSegment, sizeLength, dataSize, offset) + { + } ~MkSegment() override; bool read(File &file) override; - Matroska::Tag* parseTag(); - Matroska::Attachments* parseAttachments(); - Matroska::SeekHead* parseSeekHead(); - Matroska::Segment* parseSegment(); + Matroska::Tag *parseTag(); + Matroska::Attachments *parseAttachments(); + Matroska::SeekHead *parseSeekHead(); + Matroska::Segment *parseSegment(); private: MkTags *tags = nullptr; diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index 6d7d237a..422454a7 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -21,17 +21,13 @@ #include "ebmlmktags.h" #include "ebmluintelement.h" #include "ebmlstringelement.h" -#include "ebmlutils.h" -#include "matroskafile.h" #include "matroskatag.h" #include "matroskasimpletag.h" #include "tlist.h" -#include "tdebug.h" -#include "tutils.h" using namespace TagLib; -Matroska::Tag* EBML::MkTags::parse() +Matroska::Tag *EBML::MkTags::parse() { auto mTag = new Matroska::Tag(); mTag->setOffset(offset); @@ -42,17 +38,17 @@ Matroska::Tag* EBML::MkTags::parse() for(auto tagsChild : elements) { if(tagsChild->getId() != ElementIDs::MkTag) continue; - auto tag = static_cast(tagsChild); - List simpleTags; + auto tag = static_cast(tagsChild); + List simpleTags; MasterElement *targets = nullptr; // Identify the element and the elements for(auto tagChild : *tag) { Id tagChildId = tagChild->getId(); if(!targets && tagChildId == ElementIDs::MkTagTargets) - targets = static_cast(tagChild); + targets = static_cast(tagChild); else if(tagChildId == ElementIDs::MkSimpleTag) - simpleTags.append(static_cast(tagChild)); + simpleTags.append(static_cast(tagChild)); } // Parse the element @@ -63,8 +59,8 @@ Matroska::Tag* EBML::MkTags::parse() if(id == ElementIDs::MkTagTargetTypeValue && targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) { targetTypeValue = static_cast( - static_cast(targetsChild)->getValue() - ); + static_cast(targetsChild)->getValue() + ); } } } @@ -80,13 +76,14 @@ Matroska::Tag* EBML::MkTags::parse() for(auto simpleTagChild : *simpleTag) { Id id = simpleTagChild->getId(); if(id == ElementIDs::MkTagName && !tagName) - tagName = &(static_cast(simpleTagChild)->getValue()); + tagName = &(static_cast(simpleTagChild)->getValue()); else if(id == ElementIDs::MkTagString && !tagValueString) - tagValueString = &(static_cast(simpleTagChild)->getValue()); + tagValueString = &(static_cast(simpleTagChild)->getValue()); + // TODO implement binary else if(id == ElementIDs::MkTagsTagLanguage && !language) - language = &(static_cast(simpleTagChild)->getValue()); + language = &(static_cast(simpleTagChild)->getValue()); else if(id == ElementIDs::MkTagsLanguageDefault) - defaultLanguageFlag = static_cast(simpleTagChild)->getValue() ? true : false; + defaultLanguageFlag = static_cast(simpleTagChild)->getValue() ? true : false; } if(!tagName || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) continue; diff --git a/taglib/matroska/ebml/ebmlmktags.h b/taglib/matroska/ebml/ebmlmktags.h index f9d9aa5b..1f00e1e2 100644 --- a/taglib/matroska/ebml/ebmlmktags.h +++ b/taglib/matroska/ebml/ebmlmktags.h @@ -18,14 +18,14 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "ebmlmasterelement.h" -#include "ebmlutils.h" -#include "taglib.h" - #ifndef TAGLIB_EBMLMKTAGS_H #define TAGLIB_EBMLMKTAGS_H #ifndef DO_NOT_DOCUMENT +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + namespace TagLib { namespace Matroska { class Tag; @@ -35,17 +35,19 @@ namespace TagLib { class MkTags : public MasterElement { public: - MkTags(int sizeLength, offset_t dataSize, offset_t offset) - : MasterElement(ElementIDs::MkTags, sizeLength, dataSize, offset) - {} - MkTags() - : MasterElement(ElementIDs::MkTags, 0, 0, 0) - {} - - Matroska::Tag* parse(); + MkTags(int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(ElementIDs::MkTags, sizeLength, dataSize, offset) + { + } + MkTags() : + MasterElement(ElementIDs::MkTags, 0, 0, 0) + { + } + Matroska::Tag *parse(); }; } } + #endif #endif diff --git a/taglib/matroska/ebml/ebmlstringelement.cpp b/taglib/matroska/ebml/ebmlstringelement.cpp index 7c272352..77526c24 100644 --- a/taglib/matroska/ebml/ebmlstringelement.cpp +++ b/taglib/matroska/ebml/ebmlstringelement.cpp @@ -19,16 +19,17 @@ ***************************************************************************/ #include "ebmlstringelement.h" +#include #include "tfile.h" #include "tstring.h" #include "tbytevector.h" #include "tdebug.h" -#include +#include "ebmlutils.h" using namespace TagLib; -template -bool EBML::StringElement::read(TagLib::File &file) +template +bool EBML::StringElement::read(File &file) { ByteVector buffer = file.readBlock(dataSize); if(buffer.size() != dataSize) { @@ -44,10 +45,10 @@ bool EBML::StringElement::read(TagLib::File &file) value = String(buffer, t); return true; } -template bool EBML::StringElement::read(TagLib::File &file); -template bool EBML::StringElement::read(TagLib::File &file); +template bool EBML::StringElement::read(File &file); +template bool EBML::StringElement::read(File &file); -template +template ByteVector EBML::StringElement::render() { ByteVector buffer = renderId(); diff --git a/taglib/matroska/ebml/ebmlstringelement.h b/taglib/matroska/ebml/ebmlstringelement.h index 6421068f..afeb1ef3 100644 --- a/taglib/matroska/ebml/ebmlstringelement.h +++ b/taglib/matroska/ebml/ebmlstringelement.h @@ -22,26 +22,27 @@ #define TAGLIB_EBMLSTRINGELEMENT_H #ifndef DO_NOT_DOCUMENT -#include #include "ebmlelement.h" -#include "ebmlutils.h" #include "tbytevector.h" #include "tstring.h" namespace TagLib { class File; namespace EBML { - template + template class StringElement : public Element { public: - 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; } + StringElement(Id id, int sizeLength, offset_t dataSize) : + Element(id, sizeLength, dataSize) + { + } + + explicit StringElement(Id id) : + Element(id, 0, 0) + { + } + const String &getValue() const { return value; } void setValue(const String &value) { this->value = value; } bool read(File &file) override; ByteVector render() override; diff --git a/taglib/matroska/ebml/ebmluintelement.cpp b/taglib/matroska/ebml/ebmluintelement.cpp index c49dd9f7..39c38f0f 100644 --- a/taglib/matroska/ebml/ebmluintelement.cpp +++ b/taglib/matroska/ebml/ebmluintelement.cpp @@ -26,7 +26,7 @@ using namespace TagLib; -bool EBML::UIntElement::read(TagLib::File &file) +bool EBML::UIntElement::read(File &file) { ByteVector buffer = file.readBlock(dataSize); if(buffer.size() != dataSize) { @@ -61,9 +61,9 @@ ByteVector EBML::UIntElement::render() buffer.append(renderVINT(dataSize, 0)); uint64_t value = this->value; static const auto byteOrder = Utils::systemByteOrder(); - if (byteOrder == Utils::LittleEndian) + if(byteOrder == Utils::LittleEndian) value = Utils::byteSwap(value); - buffer.append(ByteVector((char*) &value + (sizeof(value) - dataSize), dataSize)); + buffer.append(ByteVector(reinterpret_cast(&value) + (sizeof(value) - dataSize), dataSize)); return buffer; } diff --git a/taglib/matroska/ebml/ebmluintelement.h b/taglib/matroska/ebml/ebmluintelement.h index e1d83b5a..581a19ce 100644 --- a/taglib/matroska/ebml/ebmluintelement.h +++ b/taglib/matroska/ebml/ebmluintelement.h @@ -22,7 +22,6 @@ #define TAGLIB_EBMLUINTELEMENT_H #ifndef DO_NOT_DOCUMENT -#include #include "ebmlelement.h" namespace TagLib { @@ -32,12 +31,15 @@ namespace TagLib { class UIntElement : public Element { public: - UIntElement(Id id, int sizeLength, offset_t dataSize) - : Element(id, sizeLength, dataSize) - {} - UIntElement(Id id) - : UIntElement(id, 0, 0) - {} + UIntElement(Id id, int sizeLength, offset_t dataSize) : + Element(id, sizeLength, dataSize) + { + } + + explicit UIntElement(Id id) : + UIntElement(id, 0, 0) + { + } unsigned long long getValue() const { return value; } void setValue(unsigned long long value) { this->value = value; } bool read(File &file) override; @@ -45,10 +47,6 @@ namespace TagLib { private: unsigned long long value = 0; - - //protected: - - }; } } diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index 5c288b8b..2793fb23 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -30,7 +30,7 @@ using namespace TagLib; -EBML::Element* EBML::findElement(File &file, EBML::Element::Id id, offset_t maxOffset) +EBML::Element *EBML::findElement(File &file, Element::Id id, offset_t maxOffset) { Element *element = nullptr; while(file.tell() < maxOffset) { @@ -44,12 +44,12 @@ EBML::Element* EBML::findElement(File &file, EBML::Element::Id id, offset_t maxO return element; } -EBML::Element* EBML::findNextElement(File &file, offset_t maxOffset) +EBML::Element *EBML::findNextElement(File &file, offset_t maxOffset) { return file.tell() < maxOffset ? Element::factory(file) : nullptr; } -template +template std::pair EBML::readVINT(File &file) { static_assert(sizeof(T) == 8); @@ -64,7 +64,7 @@ std::pair EBML::readVINT(File &file) if(nb_bytes > 1) buffer.append(file.readBlock(nb_bytes - 1)); - int bits_to_shift = static_cast(sizeof(T) * 8) - (7 * nb_bytes); + int bits_to_shift = static_cast(sizeof(T) * 8) - 7 * nb_bytes; offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; return { nb_bytes, static_cast(buffer.toLongLong(true)) & mask }; } @@ -73,7 +73,7 @@ namespace TagLib::EBML { template std::pair readVINT(File &file); } -template +template std::pair EBML::parseVINT(const ByteVector &buffer) { if(buffer.isEmpty()) @@ -83,7 +83,7 @@ std::pair EBML::parseVINT(const ByteVector &buffer) if(!numBytes) return {0, 0}; - int bits_to_shift = static_cast(sizeof(T) * 8) - (7 * numBytes); + int bits_to_shift = static_cast(sizeof(T) * 8) - 7 * numBytes; offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; return { numBytes, static_cast(buffer.toLongLong(true)) & mask }; } @@ -95,11 +95,11 @@ namespace TagLib::EBML { ByteVector EBML::renderVINT(uint64_t number, int minSizeLength) { int numBytes = std::max(minSizeLength, minSize(number)); - number |= (1ULL << (numBytes * 7)); + number |= 1ULL << (numBytes * 7); static const auto byteOrder = Utils::systemByteOrder(); - if (byteOrder == Utils::LittleEndian) + if(byteOrder == Utils::LittleEndian) number = Utils::byteSwap(number); - return ByteVector((char*) &number + (sizeof(number) - numBytes), numBytes); + return ByteVector(reinterpret_cast(&number) + (sizeof(number) - numBytes), numBytes); } unsigned long long EBML::randomUID() diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h index ad5b6e2b..9644cfba 100644 --- a/taglib/matroska/ebml/ebmlutils.h +++ b/taglib/matroska/ebml/ebmlutils.h @@ -23,32 +23,18 @@ #ifndef DO_NOT_DOCUMENT #include -#include #include "taglib.h" #include "tutils.h" #include "tdebug.h" #include "ebmlelement.h" #include "tbytevector.h" -/* -#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 { - template + template constexpr unsigned int VINTSizeLength(uint8_t firstByte) { static_assert(maxSizeLength >= 1 && maxSizeLength <= 8); @@ -69,12 +55,12 @@ namespace TagLib { return numBytes; } - template + template std::pair readVINT(File &file); - template + template std::pair parseVINT(const ByteVector &buffer); - Element* findElement(File &file, Element::Id id, offset_t maxLength); - Element* findNextElement(File &file, offset_t maxOffset); + Element *findElement(File &file, Element::Id id, offset_t maxOffset); + Element *findNextElement(File &file, offset_t maxOffset); ByteVector renderVINT(uint64_t number, int minSizeLength); unsigned long long randomUID(); diff --git a/taglib/matroska/ebml/ebmlvoidelement.cpp b/taglib/matroska/ebml/ebmlvoidelement.cpp index 9cb54f23..3377c327 100644 --- a/taglib/matroska/ebml/ebmlvoidelement.cpp +++ b/taglib/matroska/ebml/ebmlvoidelement.cpp @@ -21,7 +21,6 @@ #include "ebmlvoidelement.h" #include "ebmlutils.h" #include "tbytevector.h" -#include "tdebug.h" #include @@ -36,7 +35,7 @@ ByteVector EBML::VoidElement::render() bytesNeeded -= sizeLength; dataSize = bytesNeeded; buffer.append(renderVINT(dataSize, sizeLength)); - if (dataSize) + if(dataSize) buffer.append(ByteVector(static_cast(dataSize), 0)); return buffer; diff --git a/taglib/matroska/ebml/ebmlvoidelement.h b/taglib/matroska/ebml/ebmlvoidelement.h index bfb0a5a3..8e928e04 100644 --- a/taglib/matroska/ebml/ebmlvoidelement.h +++ b/taglib/matroska/ebml/ebmlvoidelement.h @@ -1,4 +1,4 @@ - /*************************************************************************** +/*************************************************************************** * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License version * * 2.1 as published by the Free Software Foundation. * @@ -22,9 +22,7 @@ #define TAGLIB_EBMLVOIDELEMENT_H #ifndef DO_NOT_DOCUMENT -#include #include "ebmlelement.h" -#include "tutils.h" namespace TagLib { class File; @@ -34,20 +32,19 @@ namespace TagLib { class VoidElement : public Element { public: - VoidElement(int sizeLength, offset_t dataSize) - : Element(ElementIDs::VoidElement, sizeLength, dataSize) + VoidElement(int sizeLength, offset_t dataSize) : + Element(ElementIDs::VoidElement, sizeLength, dataSize) {} - VoidElement() - : Element(ElementIDs::VoidElement, 0, 0) + VoidElement() : + Element(ElementIDs::VoidElement, 0, 0) {} ByteVector render() override; offset_t getTargetSize() const; void setTargetSize(offset_t targetSize); - static ByteVector renderSize(offset_t size); + static ByteVector renderSize(offset_t targetSize); private: offset_t targetSize = MIN_VOID_ELEMENT_SIZE; - }; } } diff --git a/taglib/matroska/matroskaattachedfile.cpp b/taglib/matroska/matroskaattachedfile.cpp index 78f8eadd..01c99d31 100644 --- a/taglib/matroska/matroskaattachedfile.cpp +++ b/taglib/matroska/matroskaattachedfile.cpp @@ -8,7 +8,7 @@ using namespace TagLib; class Matroska::AttachedFile::AttachedFilePrivate { public: - AttachedFilePrivate() {} + AttachedFilePrivate() = default; ~AttachedFilePrivate() = default; AttachedFilePrivate(const AttachedFilePrivate &) = delete; AttachedFilePrivate &operator=(const AttachedFilePrivate &) = delete; @@ -19,10 +19,9 @@ public: UID uid = 0; }; -Matroska::AttachedFile::AttachedFile() -: d(std::make_unique()) +Matroska::AttachedFile::AttachedFile() : + d(std::make_unique()) { - } Matroska::AttachedFile::~AttachedFile() = default; @@ -31,7 +30,7 @@ void Matroska::AttachedFile::setFileName(const String &fileName) d->fileName = fileName; } -const String& Matroska::AttachedFile::fileName() const +const String &Matroska::AttachedFile::fileName() const { return d->fileName; } @@ -41,7 +40,7 @@ void Matroska::AttachedFile::setDescription(const String &description) d->description = description; } -const String& Matroska::AttachedFile::description() const +const String &Matroska::AttachedFile::description() const { return d->description; } @@ -51,7 +50,7 @@ void Matroska::AttachedFile::setMediaType(const String &mediaType) d->mediaType = mediaType; } -const String& Matroska::AttachedFile::mediaType() const +const String &Matroska::AttachedFile::mediaType() const { return d->mediaType; } @@ -61,7 +60,7 @@ void Matroska::AttachedFile::setData(const ByteVector &data) d->data = data; } -const ByteVector& Matroska::AttachedFile::data() const +const ByteVector &Matroska::AttachedFile::data() const { return d->data; } diff --git a/taglib/matroska/matroskaattachedfile.h b/taglib/matroska/matroskaattachedfile.h index 142196b3..4eeaaa78 100644 --- a/taglib/matroska/matroskaattachedfile.h +++ b/taglib/matroska/matroskaattachedfile.h @@ -23,7 +23,6 @@ #include "taglib_export.h" - namespace TagLib { class String; class ByteVector; @@ -36,13 +35,13 @@ namespace TagLib { ~AttachedFile(); void setFileName(const String &fileName); - const String& fileName() const; + const String &fileName() const; void setDescription(const String &description); - const String& description() const; + const String &description() const; void setMediaType(const String &mediaType); - const String& mediaType() const; + const String &mediaType() const; void setData(const ByteVector &data); - const ByteVector& data() const; + const ByteVector &data() const; void setUID(UID uid); UID uid() const; diff --git a/taglib/matroska/matroskaattachments.cpp b/taglib/matroska/matroskaattachments.cpp index 25fda0a9..1dab3c1d 100644 --- a/taglib/matroska/matroskaattachments.cpp +++ b/taglib/matroska/matroskaattachments.cpp @@ -14,15 +14,15 @@ using namespace TagLib; class Matroska::Attachments::AttachmentsPrivate { public: - AttachmentsPrivate() {} + AttachmentsPrivate() = default; ~AttachmentsPrivate() = default; AttachmentsPrivate(const AttachmentsPrivate &) = delete; AttachmentsPrivate &operator=(const AttachmentsPrivate &) = delete; - List files; + List files; }; -Matroska::Attachments::Attachments() -: Element(ElementIDs::MkAttachments), +Matroska::Attachments::Attachments() : + Element(ElementIDs::MkAttachments), d(std::make_unique()) { d->files.setAutoDelete(true); @@ -48,7 +48,7 @@ void Matroska::Attachments::clear() d->files.clear(); } -const Matroska::Attachments::AttachedFileList& Matroska::Attachments::attachedFileList() const +const Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFileList() const { return d->files; } @@ -96,11 +96,10 @@ bool Matroska::Attachments::render() auto beforeSize = size(); auto data = attachments.render(); auto afterSize = data.size(); - if (beforeSize != afterSize) { - if (!emitSizeChanged(afterSize - beforeSize)) + if(beforeSize != afterSize) { + if(!emitSizeChanged(afterSize - beforeSize)) return false; } setData(data); return true; } - diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index 7f75bb74..b74ffb54 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -26,7 +26,6 @@ #include "tlist.h" #include "matroskaelement.h" - namespace TagLib { class File; namespace EBML { @@ -37,23 +36,23 @@ namespace TagLib { class File; class TAGLIB_EXPORT Attachments #ifndef DO_NOT_DOCUMENT - : private Element + : private Element #endif { public: - using AttachedFileList = List; + using AttachedFileList = List; Attachments(); virtual ~Attachments(); - void addAttachedFile(AttachedFile *file); - void removeAttachedFile(AttachedFile *file); - void clear(); - const AttachedFileList& attachedFileList() const; - bool render() override; + void addAttachedFile(AttachedFile *file); + void removeAttachedFile(AttachedFile *file); + void clear(); + const AttachedFileList &attachedFileList() const; + bool render() override; private: friend class EBML::MkAttachments; - friend class Matroska::File; + friend class File; class AttachmentsPrivate; TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp index ef046017..ac1d2547 100644 --- a/taglib/matroska/matroskacues.cpp +++ b/taglib/matroska/matroskacues.cpp @@ -29,8 +29,8 @@ using namespace TagLib; -Matroska::Cues::Cues() -: Element(ElementIDs::MkCues) +Matroska::Cues::Cues() : + Element(ElementIDs::MkCues) { cuePoints.setAutoDelete(true); } @@ -38,21 +38,21 @@ Matroska::Cues::Cues() ByteVector Matroska::Cues::renderInternal() { EBML::MkCues cues; - for (auto &cuePoint : cuePoints) { + for(auto &cuePoint : cuePoints) { auto cuePointElement = new EBML::MasterElement(EBML::ElementIDs::MkCuePoint); auto timestamp = new EBML::UIntElement(EBML::ElementIDs::MkCueTime); timestamp->setValue(cuePoint->getTime()); cuePointElement->appendElement(timestamp); auto trackList = cuePoint->cueTrackList(); - for (auto &cueTrack : trackList) { + for(auto &cueTrack : trackList) { auto cueTrackElement = new EBML::MasterElement(EBML::ElementIDs::MkCueTrackPositions); - + // Track number auto trackNumber = new EBML::UIntElement(EBML::ElementIDs::MkCueTrack); trackNumber->setValue(cueTrack->getTrackNumber()); cueTrackElement->appendElement(trackNumber); - + // Cluster position auto clusterPosition = new EBML::UIntElement(EBML::ElementIDs::MkCueClusterPosition); clusterPosition->setValue(cueTrack->getClusterPosition()); @@ -60,12 +60,11 @@ ByteVector Matroska::Cues::renderInternal() // Todo - other elements - // Reference times auto referenceTimes = cueTrack->referenceTimes(); - if (!referenceTimes.isEmpty()) { + if(!referenceTimes.isEmpty()) { auto cueReference = new EBML::MasterElement(EBML::ElementIDs::MkCueReference); - for (auto reference : referenceTimes) { + for(auto reference : referenceTimes) { auto refTime = new EBML::UIntElement(EBML::ElementIDs::MkCueRefTime); refTime->setValue(reference); cueReference->appendElement(refTime); @@ -80,11 +79,10 @@ ByteVector Matroska::Cues::renderInternal() bool Matroska::Cues::render() { - if (!needsRender) + if(!needsRender) return true; - - setData(cues.render()); + setData(renderInternal()); needsRender = false; return true; } @@ -92,15 +90,15 @@ bool Matroska::Cues::render() bool Matroska::Cues::sizeChanged(Element &caller, offset_t delta) { offset_t offset = caller.offset(); - for (auto cuePoint : cuePoints) + for(auto cuePoint : cuePoints) needsRender |= cuePoint->adjustOffset(offset, delta); return true; } bool Matroska::Cues::isValid(TagLib::File &file, offset_t segmentDataOffset) const { - for (const auto cuePoint : cuePoints) { - if (!cuePoint->isValid(file, segmentDataOffset)) + for(const auto cuePoint : cuePoints) { + if(!cuePoint->isValid(file, segmentDataOffset)) return false; } return true; @@ -113,8 +111,8 @@ Matroska::CuePoint::CuePoint() bool Matroska::CuePoint::isValid(TagLib::File &file, offset_t segmentDataOffset) const { - for (const auto track : cueTracks) { - if (!track->isValid(file, segmentDataOffset)) + for(const auto track : cueTracks) { + if(!track->isValid(file, segmentDataOffset)) return false; } return true; @@ -123,7 +121,7 @@ bool Matroska::CuePoint::isValid(TagLib::File &file, offset_t segmentDataOffset) bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta) { bool ret = false; - for (auto cueTrack : cueTracks) + for(auto cueTrack : cueTracks) ret |= cueTrack->adjustOffset(offset, delta); return ret; @@ -131,22 +129,22 @@ bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta) bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) const { - if (!trackNumber) { + if(!trackNumber) { debug("Cue track number not set"); return false; } - if (!clusterPosition) { + if(!clusterPosition) { debug("Cue track cluster position not set"); return false; } file.seek(segmentDataOffset + clusterPosition); - if (EBML::Element::readId(file) != EBML::ElementIDs::MkCluster) { + if(EBML::Element::readId(file) != EBML::ElementIDs::MkCluster) { debug("No cluster found at position"); return false; } - if (codecState) { + if(codecState) { file.seek(segmentDataOffset + codecState); - if (EBML::Element::readId(file) != EBML::ElementIDs::MkCodecState) { + if(EBML::Element::readId(file) != EBML::ElementIDs::MkCodecState) { debug("No codec state found at position"); return false; } @@ -154,8 +152,7 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) return true; } -bool Matroska::CueTrack::adjustOffset(offset_t offset, offset_t delta) +bool Matroska::CueTrack::adjustOffset(offset_t, offset_t) { return false; } - diff --git a/taglib/matroska/matroskacues.h b/taglib/matroska/matroskacues.h index 57ed29f2..109c90ce 100644 --- a/taglib/matroska/matroskacues.h +++ b/taglib/matroska/matroskacues.h @@ -22,10 +22,6 @@ #define HAS_MATROSKACUES_H #ifndef DO_NOT_DOCUMENT -#include -#include -#include - #include "tlist.h" #include "matroskaelement.h" @@ -41,39 +37,37 @@ namespace TagLib { class Cues : public Element { public: - using CuePointList = List; + using CuePointList = List; Cues(); ~Cues() override = default; - bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; + bool isValid(File &file, offset_t segmentDataOffset) const; void addCuePoint(CuePoint *cuePoint) { cuePoints.append(cuePoint); } CuePointList cuePointList() { return cuePoints; } bool sizeChanged(Element &caller, offset_t delta) override; bool render() override; - private: friend class EBML::MkCues; ByteVector renderInternal(); bool needsRender = false; - List cuePoints; + List cuePoints; }; class CuePoint { public: - using CueTrackList = List; + using CueTrackList = List; using Time = unsigned long long; CuePoint(); ~CuePoint() = default; - bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; + bool isValid(File &file, offset_t segmentDataOffset) const; void addCueTrack(CueTrack *cueTrack) { cueTracks.append(cueTrack); } CueTrackList cueTrackList() const { return cueTracks; } void setTime(Time time) { this->time = time; } Time getTime() const { return time; } bool adjustOffset(offset_t offset, offset_t delta); - private: CueTrackList cueTracks; Time time = 0; @@ -85,29 +79,28 @@ namespace TagLib { using ReferenceTimeList = List; CueTrack() = default; ~CueTrack() = default; - bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; - void setTrackNumber(unsigned int trackNumber) { this->trackNumber = trackNumber; } - unsigned int getTrackNumber() const { return trackNumber; } + bool isValid(File &file, offset_t segmentDataOffset) const; + void setTrackNumber(unsigned long long trackNumber) { this->trackNumber = trackNumber; } + unsigned long long getTrackNumber() const { return trackNumber; } void setClusterPosition(offset_t clusterPosition) { this->clusterPosition = clusterPosition; } offset_t getClusterPosition() const { return clusterPosition; } void setRelativePosition(offset_t relativePosition) { this->relativePosition = relativePosition; } offset_t getRelativePosition() const { return relativePosition; } void setCodecState(offset_t codecState) { this->codecState = codecState; } offset_t getCodecState() const { return codecState; } - void setBlockNumber(unsigned int blockNumber) { this->blockNumber = blockNumber; } - unsigned int getBlockNumber() const { return blockNumber; } + void setBlockNumber(unsigned long long blockNumber) { this->blockNumber = blockNumber; } + unsigned long long getBlockNumber() const { return blockNumber; } void setDuration(unsigned long long duration) { this->duration = duration; } unsigned long long getDuration() const { return duration; } void addReferenceTime(unsigned long long refTime) { refTimes.append(refTime); } ReferenceTimeList referenceTimes() const { return refTimes; } bool adjustOffset(offset_t offset, offset_t delta); - private: - unsigned int trackNumber = 0; + unsigned long long trackNumber = 0; offset_t clusterPosition = 0; offset_t relativePosition = 0; - unsigned int blockNumber = 0; + unsigned long long blockNumber = 0; unsigned long long duration = 0; offset_t codecState = 0; ReferenceTimeList refTimes; diff --git a/taglib/matroska/matroskaelement.cpp b/taglib/matroska/matroskaelement.cpp index 76181429..3fb65d6d 100644 --- a/taglib/matroska/matroskaelement.cpp +++ b/taglib/matroska/matroskaelement.cpp @@ -28,7 +28,7 @@ using namespace TagLib; class Matroska::Element::ElementPrivate { public: - ElementPrivate() {} + ElementPrivate() = default; ~ElementPrivate() = default; ElementPrivate(const ElementPrivate &) = delete; ElementPrivate &operator=(const ElementPrivate &) = delete; @@ -36,13 +36,12 @@ public: offset_t offset = 0; ID id = 0; ByteVector data; - List sizeListeners; - List offsetListeners; - + List sizeListeners; + List offsetListeners; }; -Matroska::Element::Element(ID id) -: e(std::make_unique()) +Matroska::Element::Element(ID id) : + e(std::make_unique()) { e->id = id; } @@ -63,12 +62,11 @@ void Matroska::Element::setData(const ByteVector &data) e->data = data; } -const ByteVector& Matroska::Element::data() const +const ByteVector &Matroska::Element::data() const { return e->data; } - void Matroska::Element::setOffset(offset_t offset) { e->offset = offset; @@ -94,7 +92,7 @@ void Matroska::Element::addSizeListener(Element *element) e->sizeListeners.append(element); } -void Matroska::Element::addSizeListeners(const List &elements) +void Matroska::Element::addSizeListeners(const List &elements) { e->sizeListeners.append(elements); } @@ -104,7 +102,7 @@ void Matroska::Element::addOffsetListener(Element *element) e->offsetListeners.append(element); } -void Matroska::Element::addOffsetListeners(const List &elements) +void Matroska::Element::addOffsetListeners(const List &elements) { e->offsetListeners.append(elements); } @@ -117,7 +115,7 @@ void Matroska::Element::setID(ID id) bool Matroska::Element::emitSizeChanged(offset_t delta) { for(auto element : e->sizeListeners) { - if (!element->sizeChanged(*this, delta)) + if(!element->sizeChanged(*this, delta)) return false; } return true; @@ -134,7 +132,7 @@ bool Matroska::Element::emitOffsetChanged(offset_t delta) bool Matroska::Element::sizeChanged(Element &caller, offset_t delta) { - if (caller.offset() < e->offset) { + if(caller.offset() < e->offset) { e->offset += delta; //return emitOffsetChanged(delta); } @@ -147,7 +145,7 @@ bool Matroska::Element::offsetChanged(Element &, offset_t) return true; } -void Matroska::Element::write(TagLib::File &file) +void Matroska::Element::write(File &file) { file.insert(e->data, e->offset, e->size); e->size = e->data.size(); diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index ec3e7863..ed11584d 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -25,11 +25,9 @@ #include #include "taglib_export.h" #include "taglib.h" -#include "tutils.h" #include "tbytevector.h" #include "tlist.h" - namespace TagLib { class File; namespace Matroska { @@ -37,7 +35,7 @@ namespace TagLib { { public: using ID = unsigned int; - Element(ID id); + explicit Element(ID id); virtual ~Element(); offset_t size() const; @@ -50,12 +48,12 @@ namespace TagLib { //virtual ByteVector render() = 0; virtual bool render() = 0; void setData(const ByteVector &data); - const ByteVector& data() const; + const ByteVector &data() const; virtual void write(TagLib::File &file); void addSizeListener(Element *element); - void addSizeListeners(const List &elements); + void addSizeListeners(const List &elements); void addOffsetListener(Element *element); - void addOffsetListeners(const List &elements); + void addOffsetListeners(const List &elements); //virtual void updatePosition(Element &caller, offset_t delta) = 0; bool emitSizeChanged(offset_t delta); bool emitOffsetChanged(offset_t delta); @@ -72,6 +70,7 @@ namespace TagLib { inline constexpr Element::ID MkAttachments = 0x1941A469; inline constexpr Element::ID MkSeekHead = 0x114D9B74; inline constexpr Element::ID MkSegment = 0x18538067; + inline constexpr Element::ID MkCues = 0x1C53BB6B; } } } diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index eabc96e4..daa7ccd2 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -28,10 +28,8 @@ #include "ebmlmksegment.h" #include "tlist.h" #include "tdebug.h" -#include "tutils.h" #include -#include #include using namespace TagLib; @@ -39,7 +37,7 @@ using namespace TagLib; class Matroska::File::FilePrivate { public: - FilePrivate() {} + FilePrivate() = default; ~FilePrivate() { delete tag; @@ -49,7 +47,7 @@ public: FilePrivate(const FilePrivate &) = delete; FilePrivate &operator=(const FilePrivate &) = delete; - Matroska::Tag *tag = nullptr; + Tag *tag = nullptr; Attachments *attachments = nullptr; SeekHead *seekHead = nullptr; Segment *segment = nullptr; @@ -70,8 +68,8 @@ bool Matroska::File::isSupported(IOStream *) //////////////////////////////////////////////////////////////////////////////// Matroska::File::File(FileName file, bool readProperties, - Properties::ReadStyle readStyle) -: TagLib::File(file), + Properties::ReadStyle readStyle) : + TagLib::File(file), d(std::make_unique()) { if(!isOpen()) { @@ -83,8 +81,8 @@ Matroska::File::File(FileName file, bool readProperties, } Matroska::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle readStyle) -: TagLib::File(stream), + Properties::ReadStyle readStyle) : + TagLib::File(stream), d(std::make_unique()) { if(!isOpen()) { @@ -97,23 +95,23 @@ Matroska::File::File(IOStream *stream, bool readProperties, Matroska::File::~File() = default; -TagLib::Tag* Matroska::File::tag() const +Tag *Matroska::File::tag() const { return tag(true); } -Matroska::Tag* Matroska::File::tag(bool create) const +Matroska::Tag *Matroska::File::tag(bool create) const { if(d->tag) return d->tag; else { if(create) - d->tag = new Matroska::Tag(); + d->tag = new Tag(); return d->tag; } } -Matroska::Attachments* Matroska::File::attachments(bool create) const +Matroska::Attachments *Matroska::File::attachments(bool create) const { if(d->attachments) return d->attachments; @@ -139,7 +137,7 @@ void Matroska::File::read(bool, Properties::ReadStyle) // Find the Matroska segment in the file std::unique_ptr segment( - static_cast( + static_cast( EBML::findElement(*this, EBML::ElementIDs::MkSegment, fileLength - tell()) ) ); @@ -176,34 +174,34 @@ bool Matroska::File::save() return false; } - List renderList; - List newElements; + List renderList; + List newElements; // List of all possible elements we can write - List elements { + List elements { d->attachments, d->tag }; - /* Build render list. New elements will be added - * to the end of the file. For new elements, - * the order is from least likely to change, - * to most likely to change: - * 1. Bookmarks (todo) - * 2. Attachments - * 3. Tags - */ - for (auto element : elements) { - if (!element) + /* Build render list. New elements will be added + * to the end of the file. For new elements, + * the order is from least likely to change, + * to most likely to change: + * 1. Bookmarks (todo) + * 2. Attachments + * 3. Tags + */ + for(auto element : elements) { + if(!element) continue; - if (element->size()) + if(element->size()) renderList.append(element); else { element->setOffset(length()); newElements.append(element); } } - if (renderList.isEmpty()) + if(renderList.isEmpty()) return true; auto sortAscending = [](const auto a, const auto b) { return a->offset() < b->offset(); }; @@ -211,18 +209,18 @@ bool Matroska::File::save() renderList.append(newElements); // Add our new elements to the Seek Head (if the file has one) - if (d->seekHead) { + if(d->seekHead) { auto segmentDataOffset = d->segment->dataOffset(); - for (auto element : newElements) + for(auto element : newElements) d->seekHead->addEntry(element->id(), element->offset() - segmentDataOffset); d->seekHead->sort(); } // Set up listeners, add seek head and segment length to the end for(auto it = renderList.begin(); it != renderList.end(); ++it) { - for (auto it2 = std::next(it); it2 != renderList.end(); ++it2) + for(auto it2 = std::next(it); it2 != renderList.end(); ++it2) (*it)->addSizeListener(*it2); - if (d->seekHead) + if(d->seekHead) (*it)->addSizeListener(d->seekHead); (*it)->addSizeListener(d->segment); } @@ -235,7 +233,7 @@ bool Matroska::File::save() // Render the elements for(auto element : renderList) { - if (!element->render()) + if(!element->render()) return false; } diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index b368412c..f126fb61 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -26,46 +26,43 @@ #include "tag.h" #include "matroskaproperties.h" +namespace TagLib::Matroska { + class Properties; + class Tag; + class Attachments; + class TAGLIB_EXPORT File : public TagLib::File + { + public: + explicit File(FileName file, bool readProperties = true, + Properties::ReadStyle readStyle = Properties::Average); + explicit File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle readStyle = Properties::Average); + ~File() override; + File(const File &) = delete; + File &operator=(const File &) = delete; + AudioProperties *audioProperties() const override { return nullptr; } + TagLib::Tag *tag() const override; + Attachments *attachments(bool create = false) const; + Tag *tag(bool create) const; + bool save() override; + //PropertyMap properties() const override { return PropertyMap(); } + //void removeUnsupportedProperties(const StringList &properties) override { } -namespace TagLib { - namespace Matroska { - class Properties; - class Tag; - class Attachments; - class TAGLIB_EXPORT File : public TagLib::File - { - public: - File(FileName file, bool readProperties = true, - Properties::ReadStyle readStyle = Properties::Average); - File(IOStream *stream, bool readProperties = true, - Properties::ReadStyle readStyle = Properties::Average); - ~File() override; - File(const File &) = delete; - File &operator=(const File &) = delete; - AudioProperties *audioProperties() const override { return nullptr; } - TagLib::Tag *tag() const override; - Attachments* attachments(bool create = false) const; - Matroska::Tag *tag(bool create) const; - bool save() override; - //PropertyMap properties() const override { return PropertyMap(); } - //void removeUnsupportedProperties(const StringList &properties) override { } + /*! + * Returns whether or not the given \a stream can be opened as a Matroska + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); - /*! - * Returns whether or not the given \a stream can be opened as a Matroska - * file. - * - * \note This method is designed to do a quick check. The result may - * not necessarily be correct. - */ - static bool isSupported(IOStream *stream); - - private: - void read(bool readProperties, Properties::ReadStyle readStyle); - class FilePrivate; - TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE - std::unique_ptr d; - }; - } + private: + void read(bool readProperties, Properties::ReadStyle readStyle); + class FilePrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; } - #endif +#endif diff --git a/taglib/matroska/matroskaproperties.cpp b/taglib/matroska/matroskaproperties.cpp index 3712856e..8827db36 100644 --- a/taglib/matroska/matroskaproperties.cpp +++ b/taglib/matroska/matroskaproperties.cpp @@ -70,7 +70,7 @@ int Matroska::Properties::channels() const // private members //////////////////////////////////////////////////////////////////////////////// -void Matroska::Properties::read(File *file) +void Matroska::Properties::read(File *) { // TODO implement. } diff --git a/taglib/matroska/matroskaproperties.h b/taglib/matroska/matroskaproperties.h index 33340be6..7a4f36e4 100644 --- a/taglib/matroska/matroskaproperties.h +++ b/taglib/matroska/matroskaproperties.h @@ -24,54 +24,49 @@ #include "taglib_export.h" #include "audioproperties.h" +namespace TagLib::Matroska { -namespace TagLib { + class File; - namespace Matroska { + //! An implementation of Matroska audio properties + class TAGLIB_EXPORT Properties : public AudioProperties + { + public: + explicit Properties(File *file, ReadStyle style = Average); + ~Properties() override; - class File; + Properties(const Properties &) = delete; + Properties &operator=(const Properties &) = delete; - //! An implementation of Matroska audio properties - class TAGLIB_EXPORT Properties : public AudioProperties - { - public: + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + int lengthInMilliseconds() const override; - Properties(File *file, ReadStyle style = Average); - ~Properties() override; + /*! + * Returns the average bit rate of the file in kb/s. + */ + int bitrate() const override; - Properties(const Properties &) = delete; - Properties &operator=(const Properties &) = delete; + /*! + * Returns the sample rate in Hz. + */ + int sampleRate() const override; - /*! - * Returns the length of the file in milliseconds. - * - * \see lengthInSeconds() - */ - int lengthInMilliseconds() const override; + /*! + * Returns the number of audio channels. + */ + int channels() const override; - /*! - * Returns the average bit rate of the file in kb/s. - */ - int bitrate() const override; + private: + void read(File *file); - /*! - * Returns the sample rate in Hz. - */ - int sampleRate() const override; - - /*! - * Returns the number of audio channels. - */ - int channels() const override; - - private: - void read(File *file); - - class PropertiesPrivate; - TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE - std::unique_ptr d; - }; - } // namespace Matroska -} // namespace TagLib + class PropertiesPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; +} #endif diff --git a/taglib/matroska/matroskaseekhead.cpp b/taglib/matroska/matroskaseekhead.cpp index 81cc2564..b135b002 100644 --- a/taglib/matroska/matroskaseekhead.cpp +++ b/taglib/matroska/matroskaseekhead.cpp @@ -18,19 +18,16 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ - #include "matroskaseekhead.h" - #include "ebmlmkseekhead.h" - #include "ebmlbinaryelement.h" - #include "ebmluintelement.h" - #include "ebmlmasterelement.h" +#include "matroskaseekhead.h" +#include "ebmlmkseekhead.h" +#include "ebmlbinaryelement.h" +#include "ebmluintelement.h" +#include "ebmlmasterelement.h" +#include "tdebug.h" - #include "tdebug.h" - #include "tfile.h" - #include "tutils.h" +using namespace TagLib; - using namespace TagLib; - -void Matroska::SeekHead::addEntry(Element &element) +void Matroska::SeekHead::addEntry(const Element &element) { entries.append({element.id(), element.offset()}); debug("adding to seekhead"); @@ -48,7 +45,7 @@ ByteVector Matroska::SeekHead::renderInternal() auto beforeSize = size(); EBML::MkSeekHead seekHead; seekHead.setMinRenderSize(beforeSize); - for(const auto& [id, position] : entries) { + for(const auto &[id, position] : entries) { auto seekElement = new EBML::MasterElement(EBML::ElementIDs::MkSeek); auto idElement = new EBML::BinaryElement(EBML::ElementIDs::MkSeekID); idElement->setValue(ByteVector::fromUInt(id, true)); @@ -65,17 +62,17 @@ ByteVector Matroska::SeekHead::renderInternal() bool Matroska::SeekHead::render() { - if (!needsRender) + if(!needsRender) return true; auto beforeSize = size(); auto data = renderInternal(); needsRender = false; auto afterSize = data.size(); - if (afterSize != beforeSize) { + if(afterSize != beforeSize) { return false; - // To do, handle expansion of seek head - if (!emitSizeChanged(afterSize - beforeSize)) + // TODO handle expansion of seek head + if(!emitSizeChanged(afterSize - beforeSize)) return false; } @@ -83,9 +80,9 @@ bool Matroska::SeekHead::render() return true; } -void Matroska::SeekHead::write(TagLib::File &file) +void Matroska::SeekHead::write(File &file) { - if (!data().isEmpty()) + if(!data().isEmpty()) Element::write(file); } @@ -97,19 +94,19 @@ void Matroska::SeekHead::sort() bool Matroska::SeekHead::sizeChanged(Element &caller, offset_t delta) { ID callerID = caller.id(); - if (callerID == ElementIDs::MkSegment) { + if(callerID == ElementIDs::MkSegment) { adjustOffset(delta); return true; } else { offset_t offset = caller.offset(); auto it = entries.begin(); - while (it != entries.end()) { + while(it != entries.end()) { it = std::find_if(it, entries.end(), [offset](const auto a){ return a.second > offset; } ); - if (it != entries.end()) { + if(it != entries.end()) { it->second += delta; needsRender = true; ++it; @@ -117,5 +114,4 @@ bool Matroska::SeekHead::sizeChanged(Element &caller, offset_t delta) } return true; } - return false; } diff --git a/taglib/matroska/matroskaseekhead.h b/taglib/matroska/matroskaseekhead.h index 6c90f4fa..e684a3d9 100644 --- a/taglib/matroska/matroskaseekhead.h +++ b/taglib/matroska/matroskaseekhead.h @@ -24,7 +24,6 @@ #include "matroskaelement.h" #include "tbytevector.h" -#include "tutils.h" #include "tlist.h" namespace TagLib { @@ -33,9 +32,9 @@ namespace TagLib { class SeekHead : public Element { public: - SeekHead() : Element(ElementIDs::MkSeekHead) {}; - virtual ~SeekHead() {}; - void addEntry(Element &element); + SeekHead() : Element(ElementIDs::MkSeekHead) {} + ~SeekHead() override = default; + void addEntry(const Element &element); void addEntry(ID id, offset_t offset); bool render() override; void write(TagLib::File &file) override; diff --git a/taglib/matroska/matroskasegment.cpp b/taglib/matroska/matroskasegment.cpp index 6afcac71..3de08b31 100644 --- a/taglib/matroska/matroskasegment.cpp +++ b/taglib/matroska/matroskasegment.cpp @@ -26,12 +26,12 @@ using namespace TagLib; bool Matroska::Segment::render() { auto data = EBML::renderVINT(dataSize, static_cast(sizeLength)); - if (data.size() != sizeLength) { + if(data.size() != sizeLength) { sizeLength = 8; - if (!emitSizeChanged(sizeLength - data.size())) + if(!emitSizeChanged(sizeLength - data.size())) return false; data = EBML::renderVINT(dataSize, static_cast(sizeLength)); - if (data.size() != sizeLength) + if(data.size() != sizeLength) return false; } setData(data); diff --git a/taglib/matroska/matroskasegment.h b/taglib/matroska/matroskasegment.h index 5f275e6c..96652c4f 100644 --- a/taglib/matroska/matroskasegment.h +++ b/taglib/matroska/matroskasegment.h @@ -23,30 +23,29 @@ #ifndef DO_NOT_DOCUMENT #include "matroskaelement.h" -#include "tutils.h" -namespace TagLib { - namespace Matroska { - class Segment : public Element +namespace TagLib::Matroska { + class Segment : public Element + { + public: + Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset) : + Element(ElementIDs::MkSegment), sizeLength(sizeLength), dataSize(dataSize) { - public: - Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset) - : Element(ElementIDs::MkSegment), sizeLength(sizeLength), dataSize(dataSize) - { - setOffset(lengthOffset); - setSize(sizeLength); - } - virtual ~Segment() = default; - bool render() override; - bool sizeChanged(Element &caller, offset_t delta) override; - offset_t dataOffset() const { return offset() + sizeLength; } - - private: - offset_t sizeLength; - offset_t dataSize; - }; - } + setOffset(lengthOffset); + setSize(sizeLength); + } + + ~Segment() override = default; + bool render() override; + bool sizeChanged(Element &caller, offset_t delta) override; + offset_t dataOffset() const { return offset() + sizeLength; } + + private: + offset_t sizeLength; + offset_t dataSize; + }; } + #endif #endif diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp index 49094fc6..db0b0979 100644 --- a/taglib/matroska/matroskasimpletag.cpp +++ b/taglib/matroska/matroskasimpletag.cpp @@ -27,39 +27,34 @@ using namespace TagLib; class Matroska::SimpleTag::SimpleTagPrivate { - public: - SimpleTagPrivate() = default; - SimpleTag::TargetTypeValue targetTypeValue = TargetTypeValue::None; - String name; - String language; - bool defaultLanguageFlag = true; - +public: + SimpleTagPrivate() = default; + TargetTypeValue targetTypeValue = None; + String name; + String language; + bool defaultLanguageFlag = true; }; class Matroska::SimpleTagString::SimpleTagStringPrivate { - public: - SimpleTagStringPrivate() = default; - String value; - +public: + SimpleTagStringPrivate() = default; + String value; }; class Matroska::SimpleTagBinary::SimpleTagBinaryPrivate { - public: - SimpleTagBinaryPrivate() = default; - ByteVector value; - +public: + SimpleTagBinaryPrivate() = default; + ByteVector value; }; -Matroska::SimpleTag::SimpleTag() -: d(std::make_unique()) +Matroska::SimpleTag::SimpleTag() : + d(std::make_unique()) { - } Matroska::SimpleTag::~SimpleTag() = default; - Matroska::SimpleTag::TargetTypeValue Matroska::SimpleTag::targetTypeValue() const { return d->targetTypeValue; @@ -70,12 +65,12 @@ void Matroska::SimpleTag::setTargetTypeValue(TargetTypeValue targetTypeValue) d->targetTypeValue = targetTypeValue; } -const String& Matroska::SimpleTag::name() const +const String &Matroska::SimpleTag::name() const { return d->name; } -const String& Matroska::SimpleTag::language() const +const String &Matroska::SimpleTag::language() const { return d->language; } @@ -100,15 +95,13 @@ void Matroska::SimpleTag::setName(const String &name) d->name = name; } -Matroska::SimpleTagString::SimpleTagString() -: Matroska::SimpleTag(), +Matroska::SimpleTagString::SimpleTagString() : dd(std::make_unique()) { - } Matroska::SimpleTagString::~SimpleTagString() = default; -const String& Matroska::SimpleTagString::value() const +const String &Matroska::SimpleTagString::value() const { return dd->value; } @@ -118,15 +111,13 @@ void Matroska::SimpleTagString::setValue(const String &value) dd->value = value; } -Matroska::SimpleTagBinary::SimpleTagBinary() -: Matroska::SimpleTag(), +Matroska::SimpleTagBinary::SimpleTagBinary() : dd(std::make_unique()) { - } Matroska::SimpleTagBinary::~SimpleTagBinary() = default; -const ByteVector& Matroska::SimpleTagBinary::value() const +const ByteVector &Matroska::SimpleTagBinary::value() const { return dd->value; } diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index 484decd5..326b25d3 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -42,9 +42,9 @@ namespace TagLib { Edition = 60, Collection = 70 }; - const String& name() const; + const String &name() const; TargetTypeValue targetTypeValue() const; - const String& language() const; + const String &language() const; bool defaultLanguageFlag() const; void setName(const String &name); void setTargetTypeValue(TargetTypeValue targetTypeValue); @@ -66,7 +66,7 @@ namespace TagLib { public: SimpleTagString(); ~SimpleTagString() override; - const String& value() const; + const String &value() const; void setValue(const String &value); private: @@ -80,7 +80,7 @@ namespace TagLib { public: SimpleTagBinary(); ~SimpleTagBinary() override; - const ByteVector& value() const; + const ByteVector &value() const; void setValue(const ByteVector &value); private: diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index cc06e2a1..39abe9c0 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -19,6 +19,9 @@ ***************************************************************************/ #include "matroskatag.h" +#include +#include +#include #include "matroskasimpletag.h" #include "ebmlmasterelement.h" #include "ebmlstringelement.h" @@ -27,35 +30,24 @@ #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); - } - } +namespace TagLib::Matroska::Utils { + std::pair translateKey(const String &key); + String translateTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue); } -class Matroska::Tag::TagPrivate +class Matroska::Tag::TagPrivate { - public: - TagPrivate() = default; - ~TagPrivate() = default; - List tags; - ByteVector data; - +public: + TagPrivate() = default; + ~TagPrivate() = default; + List tags; + ByteVector data; }; -Matroska::Tag::Tag() -: TagLib::Tag(), +Matroska::Tag::Tag() : Element(ElementIDs::MkTags), d(std::make_unique()) { @@ -82,17 +74,17 @@ void Matroska::Tag::clearSimpleTags() d->tags.clear(); } -const Matroska::SimpleTagsList& Matroska::Tag::simpleTagsList() const +const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsList() const { return d->tags; } -Matroska::SimpleTagsList& Matroska::Tag::simpleTagsListPrivate() +Matroska::SimpleTagsList &Matroska::Tag::simpleTagsListPrivate() { return d->tags; } -const Matroska::SimpleTagsList& Matroska::Tag::simpleTagsListPrivate() const +const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsListPrivate() const { return d->tags; } @@ -135,31 +127,31 @@ void Matroska::Tag::setTrack(unsigned int i) String Matroska::Tag::title() const { const auto value = getTag("TITLE"); - return value ? *value : String(); + return value ? *value : String(); } String Matroska::Tag::artist() const { const auto value = getTag("ARTIST"); - return value ? *value : String(); + return value ? *value : String(); } String Matroska::Tag::album() const { const auto value = getTag("ALBUM"); - return value ? *value : String(); + return value ? *value : String(); } String Matroska::Tag::comment() const -{ +{ const auto value = getTag("COMMENT"); - return value ? *value : String(); + return value ? *value : String(); } String Matroska::Tag::genre() const { const auto value = getTag("GENRE"); - return value ? *value : String(); + return value ? *value : String(); } unsigned int Matroska::Tag::year() const @@ -188,7 +180,7 @@ bool Matroska::Tag::isEmpty() const bool Matroska::Tag::render() { EBML::MkTags tags; - List*> targetList; + List *> targetList; targetList.setAutoDelete(true); // Build target-based list @@ -202,7 +194,7 @@ bool Matroska::Tag::render() } ); if(it == targetList.end()) { - auto list = new List(); + auto list = new List(); list->append(tag); targetList.append(list); } @@ -216,7 +208,7 @@ bool Matroska::Tag::render() // Build element auto targets = new EBML::MasterElement(EBML::ElementIDs::MkTagTargets); - if(targetTypeValue != Matroska::SimpleTag::TargetTypeValue::None) { + if(targetTypeValue != SimpleTag::TargetTypeValue::None) { auto element = new EBML::UIntElement(EBML::ElementIDs::MkTagTargetTypeValue); element->setValue(static_cast(targetTypeValue)); targets->appendElement(element); @@ -231,14 +223,14 @@ bool Matroska::Tag::render() t->appendElement(tagName); // Tag Value - Matroska::SimpleTagString *tStr = nullptr; - Matroska::SimpleTagBinary *tBin = nullptr; - if((tStr = dynamic_cast(simpleTag))) { + SimpleTagString *tStr = nullptr; + 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))) { + else if((tBin = dynamic_cast(simpleTag))) { // Todo } @@ -261,8 +253,8 @@ bool Matroska::Tag::render() auto data = tags.render(); auto beforeSize = size(); auto afterSize = data.size(); - if (afterSize != beforeSize) { - if (!emitSizeChanged(afterSize - beforeSize)) + if(afterSize != beforeSize) { + if(!emitSizeChanged(afterSize - beforeSize)) return false; } setData(data); @@ -307,20 +299,20 @@ namespace bool Matroska::Tag::setTag(const String &key, const String &value) { - const auto pair = Matroska::Utils::translateKey(key); + const auto pair = Utils::translateKey(key); // Workaround Clang issue - no lambda capture of structured bindings const String &name = pair.first; auto targetTypeValue = pair.second; if(name.isEmpty()) return false; removeSimpleTags( - [&name, targetTypeValue] (auto t) { - return t->name() == name + [&name, targetTypeValue] (auto t) { + return t->name() == name && t->targetTypeValue() == targetTypeValue; } ); if(!value.isEmpty()) { - auto t = new Matroska::SimpleTagString(); + auto t = new SimpleTagString(); t->setTargetTypeValue(targetTypeValue); t->setName(name); t->setValue(value); @@ -329,15 +321,15 @@ bool Matroska::Tag::setTag(const String &key, const String &value) return true; } -const String* Matroska::Tag::getTag(const String &key) const +const String *Matroska::Tag::getTag(const String &key) const { - const auto pair = Matroska::Utils::translateKey(key); + const auto pair = Utils::translateKey(key); // Workaround Clang issue - no lambda capture of structured bindings const String &name = pair.first; auto targetTypeValue = pair.second; if(name.isEmpty()) return nullptr; - auto tag = dynamic_cast( + auto tag = dynamic_cast( findSimpleTag( [&name, targetTypeValue] (auto t) { return t->name() == name @@ -351,16 +343,16 @@ const String* Matroska::Tag::getTag(const String &key) const std::pair Matroska::Utils::translateKey(const String &key) { auto it = std::find_if(simpleTagsTranslation.cbegin(), - simpleTagsTranslation.cend(), + 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 }; + return { String(), SimpleTag::TargetTypeValue::None }; } -String Matroska::Utils::translateTag(const String &name, Matroska::SimpleTag::TargetTypeValue targetTypeValue) +String Matroska::Utils::translateTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue) { auto it = std::find_if(simpleTagsTranslation.cbegin(), simpleTagsTranslation.cend(), @@ -375,7 +367,7 @@ String Matroska::Utils::translateTag(const String &name, Matroska::SimpleTag::Ta PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) { PropertyMap unsupportedProperties; - for(const auto& [key, value] : propertyMap) { + for(const auto &[key, value] : propertyMap) { if(!setTag(key, value.toString())) unsupportedProperties[key] = value; } @@ -385,10 +377,10 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) PropertyMap Matroska::Tag::properties() const { PropertyMap properties; - Matroska::SimpleTagString *tStr = nullptr; + SimpleTagString *tStr = nullptr; for(auto simpleTag : d->tags) { - if((tStr = dynamic_cast(simpleTag))) { - String key = Matroska::Utils::translateTag(tStr->name(), tStr->targetTypeValue()); + if((tStr = dynamic_cast(simpleTag))) { + String key = Utils::translateTag(tStr->name(), tStr->targetTypeValue()); if(!key.isEmpty() && !properties.contains(key)) properties[key] = tStr->value(); } diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 5424010e..6f4a706a 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -39,7 +39,7 @@ namespace TagLib { } namespace Matroska { - using SimpleTagsList = List; + using SimpleTagsList = List; class TAGLIB_EXPORT Tag : public TagLib::Tag #ifndef DO_NOT_DOCUMENT , private Element @@ -51,7 +51,7 @@ namespace TagLib { void addSimpleTag(SimpleTag *tag); void removeSimpleTag(SimpleTag *tag); void clearSimpleTags(); - const SimpleTagsList& simpleTagsList() const; + const SimpleTagsList &simpleTagsList() const; String title() const override; String artist() const override; String album() const override; @@ -71,7 +71,7 @@ namespace TagLib { PropertyMap properties() const override; PropertyMap setProperties(const PropertyMap &propertyMap) override; template - int removeSimpleTags(T&& p) + int removeSimpleTags(T &&p) { auto &list = simpleTagsListPrivate(); int numRemoved = 0; @@ -87,8 +87,8 @@ namespace TagLib { return numRemoved; } - template - SimpleTagsList findSimpleTags(T&& p) + template + SimpleTagsList findSimpleTags(T &&p) { auto &list = simpleTagsListPrivate(); for(auto it = list.begin(); it != list.end();) { @@ -101,8 +101,8 @@ namespace TagLib { return list; } - template - const Matroska::SimpleTag* findSimpleTag(T&& p) const + template + const SimpleTag *findSimpleTag(T &&p) const { auto &list = simpleTagsListPrivate(); auto it = std::find_if(list.begin(), list.end(), std::forward(p)); @@ -110,20 +110,20 @@ namespace TagLib { } template - Matroska::SimpleTag* findSimpleTag(T&&p) + SimpleTag *findSimpleTag(T &&p) { - return const_cast( - const_cast(this)->findSimpleTag(std::forward(p)) + return const_cast( + const_cast(this)->findSimpleTag(std::forward(p)) ); } private: - friend class Matroska::File; + friend class File; friend class EBML::MkTags; - SimpleTagsList& simpleTagsListPrivate(); - const SimpleTagsList& simpleTagsListPrivate() const; + SimpleTagsList &simpleTagsListPrivate(); + const SimpleTagsList &simpleTagsListPrivate() const; bool setTag(const String &key, const String &value); - const String* getTag(const String &key) const; + const String *getTag(const String &key) const; class TagPrivate; TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; From f7ab162514f668fb1cba22e236473401e64a3c19 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Wed, 13 Aug 2025 17:22:58 +0200 Subject: [PATCH 11/31] Make tags and properties more flexible --- taglib/matroska/ebml/ebmlelement.h | 1 + taglib/matroska/ebml/ebmlmktags.cpp | 6 +- taglib/matroska/matroskaattachments.h | 5 +- taglib/matroska/matroskacues.cpp | 1 + taglib/matroska/matroskafile.cpp | 7 +- taglib/matroska/matroskasegment.h | 1 - taglib/matroska/matroskatag.cpp | 226 ++++++++++++++++---------- taglib/matroska/matroskatag.h | 68 ++------ 8 files changed, 165 insertions(+), 150 deletions(-) diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 55141cc8..cb020303 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -69,6 +69,7 @@ namespace TagLib::EBML { inline constexpr Element::Id MkSimpleTag = 0x67C8; inline constexpr Element::Id MkTagName = 0x45A3; inline constexpr Element::Id MkTagLanguage = 0x447A; + inline constexpr Element::Id MkTagBinary = 0x4485; inline constexpr Element::Id MkTagString = 0x4487; inline constexpr Element::Id MkTagsTagLanguage = 0x447A; inline constexpr Element::Id MkTagsLanguageDefault = 0x4484; diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index 422454a7..d594797e 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -19,6 +19,7 @@ ***************************************************************************/ #include "ebmlmktags.h" +#include "ebmlbinaryelement.h" #include "ebmluintelement.h" #include "ebmlstringelement.h" #include "matroskatag.h" @@ -79,7 +80,8 @@ Matroska::Tag *EBML::MkTags::parse() tagName = &(static_cast(simpleTagChild)->getValue()); else if(id == ElementIDs::MkTagString && !tagValueString) tagValueString = &(static_cast(simpleTagChild)->getValue()); - // TODO implement binary + else if(id == ElementIDs::MkTagBinary && !tagValueBinary) + tagValueBinary = &(static_cast(simpleTagChild)->getValue()); else if(id == ElementIDs::MkTagsTagLanguage && !language) language = &(static_cast(simpleTagChild)->getValue()); else if(id == ElementIDs::MkTagsLanguageDefault) @@ -96,7 +98,7 @@ Matroska::Tag *EBML::MkTags::parse() sTagString->setValue(*tagValueString); sTag = sTagString; } - else if(tagValueBinary) { + else { // tagValueBinary must be non null auto sTagBinary = new Matroska::SimpleTagBinary(); sTagBinary->setTargetTypeValue(targetTypeValue); sTagBinary->setValue(*tagValueBinary); diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index b74ffb54..a49e7b61 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -48,12 +48,15 @@ namespace TagLib { void removeAttachedFile(AttachedFile *file); void clear(); const AttachedFileList &attachedFileList() const; - bool render() override; private: friend class EBML::MkAttachments; friend class File; class AttachmentsPrivate; + + // private Element implementation + bool render() override; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; }; diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp index ac1d2547..06d6e288 100644 --- a/taglib/matroska/matroskacues.cpp +++ b/taglib/matroska/matroskacues.cpp @@ -154,5 +154,6 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) bool Matroska::CueTrack::adjustOffset(offset_t, offset_t) { + // TODO implement return false; } diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index daa7ccd2..b0b02c60 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -28,6 +28,7 @@ #include "ebmlmksegment.h" #include "tlist.h" #include "tdebug.h" +#include "tagutils.h" #include #include @@ -57,10 +58,10 @@ public: // static members //////////////////////////////////////////////////////////////////////////////// -bool Matroska::File::isSupported(IOStream *) +bool Matroska::File::isSupported(IOStream *stream) { - // TODO implement - return false; + const ByteVector id = Utils::readHeader(stream, 4, false); + return id.startsWith("\x1A\x45\xDF\xA3"); } //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/matroska/matroskasegment.h b/taglib/matroska/matroskasegment.h index 96652c4f..dcd1163a 100644 --- a/taglib/matroska/matroskasegment.h +++ b/taglib/matroska/matroskasegment.h @@ -46,6 +46,5 @@ namespace TagLib::Matroska { }; } - #endif #endif diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 39abe9c0..fe65d1e4 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -25,6 +25,7 @@ #include "matroskasimpletag.h" #include "ebmlmasterelement.h" #include "ebmlstringelement.h" +#include "ebmlbinaryelement.h" #include "ebmlmktags.h" #include "ebmluintelement.h" #include "ebmlutils.h" @@ -33,16 +34,62 @@ using namespace TagLib; -namespace TagLib::Matroska::Utils { - std::pair translateKey(const String &key); - String translateTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue); -} - class Matroska::Tag::TagPrivate { public: TagPrivate() = default; ~TagPrivate() = default; + + bool setTag(const String &key, const String &value); + const String *getTag(const String &key) const; + + template + int removeSimpleTags(T &&p) + { + auto &list = tags; + 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 = tags; + 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 SimpleTag *findSimpleTag(T &&p) const + { + auto &list = tags; + auto it = std::find_if(list.begin(), list.end(), std::forward(p)); + return it != list.end() ? *it : nullptr; + } + + template + SimpleTag *findSimpleTag(T &&p) + { + return const_cast( + const_cast(this)->findSimpleTag(std::forward(p)) + ); + } + List tags; ByteVector data; }; @@ -79,84 +126,74 @@ 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); + d->setTag("TITLE", s); } void Matroska::Tag::setArtist(const String &s) { - setTag("ARTIST", s); + d->setTag("ARTIST", s); } void Matroska::Tag::setAlbum(const String &s) { - setTag("ALBUM", s); + d->setTag("ALBUM", s); } void Matroska::Tag::setComment(const String &s) { - setTag("COMMENT", s); + d->setTag("COMMENT", s); } void Matroska::Tag::setGenre(const String &s) { - setTag("GENRE", s); + d->setTag("GENRE", s); } void Matroska::Tag::setYear(unsigned int i) { - setTag("DATE", String::number(i)); + d->setTag("DATE", String::number(i)); } void Matroska::Tag::setTrack(unsigned int i) { - setTag("TRACKNUMBER", String::number(i)); + d->setTag("TRACKNUMBER", String::number(i)); } String Matroska::Tag::title() const { - const auto value = getTag("TITLE"); + const auto value = d->getTag("TITLE"); return value ? *value : String(); } String Matroska::Tag::artist() const { - const auto value = getTag("ARTIST"); + const auto value = d->getTag("ARTIST"); return value ? *value : String(); } String Matroska::Tag::album() const { - const auto value = getTag("ALBUM"); + const auto value = d->getTag("ALBUM"); return value ? *value : String(); } String Matroska::Tag::comment() const { - const auto value = getTag("COMMENT"); + const auto value = d->getTag("COMMENT"); return value ? *value : String(); } String Matroska::Tag::genre() const { - const auto value = getTag("GENRE"); + const auto value = d->getTag("GENRE"); return value ? *value : String(); } unsigned int Matroska::Tag::year() const { - auto value = getTag("DATE"); + auto value = d->getTag("DATE"); if(!value) return 0; auto list = value->split("-"); @@ -165,7 +202,7 @@ unsigned int Matroska::Tag::year() const unsigned int Matroska::Tag::track() const { - auto value = getTag("TRACKNUMBER"); + auto value = d->getTag("TRACKNUMBER"); if(!value) return 0; auto list = value->split("-"); @@ -230,8 +267,10 @@ bool Matroska::Tag::render() tagValue->setValue(tStr->value()); t->appendElement(tagValue); } - else if((tBin = dynamic_cast(simpleTag))) { - // Todo + else if((tBin = dynamic_cast(simpleTag))) { + auto tagValue = new EBML::BinaryElement(EBML::ElementIDs::MkTagBinary); + tagValue->setValue(tBin->value()); + t->appendElement(tagValue); } // Language @@ -264,42 +303,72 @@ bool Matroska::Tag::render() namespace { // PropertyMap key, Tag name, Target type value + // If the key is the same as the name and the target type value is Track, + // no translation is needed because this is the default mapping. + // Therefore, keys like TITLE, ARTIST, GENRE, COMMENT, etc. are omitted. + // For offical tags, see https://www.matroska.org/technical/tagging.html 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("TRACKTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("DISCNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Part), + std::tuple("DISCTOTAL", "TOTAL_PARTS", 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("ALBUMARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album), 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 + std::tuple("DJMIXER", "MIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("REMIXER", "REMIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("INITIALKEY", "INITIAL_KEY", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("RELEASEDATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ENCODINGTIME", "DATE_ENCODED", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("TAGGINGDATE", "DATE_TAGGED", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ENCODEDBY", "ENCODER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ENCODING", "ENCODER_SETTINGS", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("OWNER", "PURCHASE_OWNER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMARTISTID", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("MUSICBRAINZ_ALBUMID", "MUSICBRAINZ_ALBUMID", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("MUSICBRAINZ_RELEASEGROUPID", "MUSICBRAINZ_RELEASEGROUPID", Matroska::SimpleTag::TargetTypeValue::Album), }; + + std::pair 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) }; + if (!key.isEmpty() && !key.startsWith("_")) + return { key, Matroska::SimpleTag::TargetTypeValue::Track }; + return { String(), Matroska::SimpleTag::TargetTypeValue::None }; + } + + String 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() + ? String(std::get<0>(*it), String::UTF8) + : targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track && !name.startsWith("_") + ? name + : String(); + } } -bool Matroska::Tag::setTag(const String &key, const String &value) +bool Matroska::Tag::TagPrivate::setTag(const String &key, const String &value) { - const auto pair = Utils::translateKey(key); + const auto pair = translateKey(key); // Workaround Clang issue - no lambda capture of structured bindings const String &name = pair.first; auto targetTypeValue = pair.second; @@ -316,14 +385,14 @@ bool Matroska::Tag::setTag(const String &key, const String &value) t->setTargetTypeValue(targetTypeValue); t->setName(name); t->setValue(value); - addSimpleTag(t); + tags.append(t); } return true; } -const String *Matroska::Tag::getTag(const String &key) const +const String *Matroska::Tag::TagPrivate::getTag(const String &key) const { - const auto pair = Utils::translateKey(key); + const auto pair = translateKey(key); // Workaround Clang issue - no lambda capture of structured bindings const String &name = pair.first; auto targetTypeValue = pair.second; @@ -340,36 +409,23 @@ const String *Matroska::Tag::getTag(const String &key) const 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(), SimpleTag::TargetTypeValue::None }; -} - -String Matroska::Utils::translateTag(const String &name, 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; + for(const auto &[key, values] : propertyMap) { + for(const auto &value : values) { + if(auto [name, targetTypeValue] = translateKey(key); + !name.isEmpty()) { + auto t = new SimpleTagString(); + t->setTargetTypeValue(targetTypeValue); + t->setName(name); + t->setValue(value); + d->tags.append(t); + } + else { + unsupportedProperties[key] = values; + } + } } return unsupportedProperties; } @@ -378,11 +434,11 @@ PropertyMap Matroska::Tag::properties() const { PropertyMap properties; SimpleTagString *tStr = nullptr; - for(auto simpleTag : d->tags) { + for(auto simpleTag : std::as_const(d->tags)) { if((tStr = dynamic_cast(simpleTag))) { - String key = Utils::translateTag(tStr->name(), tStr->targetTypeValue()); - if(!key.isEmpty() && !properties.contains(key)) - properties[key] = tStr->value(); + String key = translateTag(tStr->name(), tStr->targetTypeValue()); + if(!key.isEmpty()) + properties[key].append(tStr->value()); } } return properties; diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 6f4a706a..5f4c0db2 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -18,12 +18,10 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAS_MATROSKATAG_H -#define HAS_MATROSKATAG_H +#ifndef TAGLIB_MATROSKATAG_H +#define TAGLIB_MATROSKATAG_H #include -#include -#include #include "tag.h" #include "tstring.h" @@ -48,10 +46,6 @@ namespace TagLib { public: Tag(); ~Tag() override; - void addSimpleTag(SimpleTag *tag); - void removeSimpleTag(SimpleTag *tag); - void clearSimpleTags(); - const SimpleTagsList &simpleTagsList() const; String title() const override; String artist() const override; String album() const override; @@ -67,64 +61,22 @@ namespace TagLib { void setYear(unsigned int i) override; void setTrack(unsigned int i) override; bool isEmpty() const override; - bool render() override; 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 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 - SimpleTag *findSimpleTag(T &&p) - { - return const_cast( - const_cast(this)->findSimpleTag(std::forward(p)) - ); - } + void addSimpleTag(SimpleTag *tag); + void removeSimpleTag(SimpleTag *tag); + void clearSimpleTags(); + const SimpleTagsList &simpleTagsList() const; private: friend class File; friend class EBML::MkTags; - SimpleTagsList &simpleTagsListPrivate(); - const SimpleTagsList &simpleTagsListPrivate() const; - bool setTag(const String &key, const String &value); - const String *getTag(const String &key) const; class TagPrivate; + + // private Element implementation + bool render() override; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; }; From cfade6d0825847bd2d1966ea364f758314382edd Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sat, 16 Aug 2025 11:19:25 +0200 Subject: [PATCH 12/31] Implement Matroska audio properties --- taglib/CMakeLists.txt | 6 ++ taglib/matroska/ebml/ebmlelement.cpp | 18 +++++ taglib/matroska/ebml/ebmlelement.h | 10 +++ taglib/matroska/ebml/ebmlfloatelement.cpp | 82 +++++++++++++++++++++++ taglib/matroska/ebml/ebmlfloatelement.h | 64 ++++++++++++++++++ taglib/matroska/ebml/ebmlmkinfo.cpp | 53 +++++++++++++++ taglib/matroska/ebml/ebmlmkinfo.h | 56 ++++++++++++++++ taglib/matroska/ebml/ebmlmkseekhead.cpp | 6 +- taglib/matroska/ebml/ebmlmksegment.cpp | 28 ++++++++ taglib/matroska/ebml/ebmlmksegment.h | 6 ++ taglib/matroska/ebml/ebmlmktracks.cpp | 73 ++++++++++++++++++++ taglib/matroska/ebml/ebmlmktracks.h | 56 ++++++++++++++++ taglib/matroska/matroskafile.cpp | 15 ++++- taglib/matroska/matroskafile.h | 3 +- taglib/matroska/matroskaproperties.cpp | 63 +++++++++++++++-- taglib/matroska/matroskaproperties.h | 40 ++++++++++- 16 files changed, 565 insertions(+), 14 deletions(-) create mode 100644 taglib/matroska/ebml/ebmlfloatelement.cpp create mode 100644 taglib/matroska/ebml/ebmlfloatelement.h create mode 100644 taglib/matroska/ebml/ebmlmkinfo.cpp create mode 100644 taglib/matroska/ebml/ebmlmkinfo.h create mode 100644 taglib/matroska/ebml/ebmlmktracks.cpp create mode 100644 taglib/matroska/ebml/ebmlmktracks.h diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 778b1d9c..68983824 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -245,9 +245,12 @@ if(WITH_MATROSKA) matroska/ebml/ebmlmkcues.h matroska/ebml/ebmlmkseekhead.h matroska/ebml/ebmlmksegment.h + matroska/ebml/ebmlmkinfo.h + matroska/ebml/ebmlmktracks.h matroska/ebml/ebmlmktags.h matroska/ebml/ebmlstringelement.h matroska/ebml/ebmluintelement.h + matroska/ebml/ebmlfloatelement.h matroska/ebml/ebmlutils.h matroska/ebml/ebmlvoidelement.h ) @@ -464,9 +467,12 @@ if(WITH_MATROSKA) matroska/ebml/ebmlmkcues.cpp matroska/ebml/ebmlmkseekhead.cpp matroska/ebml/ebmlmksegment.cpp + matroska/ebml/ebmlmkinfo.cpp + matroska/ebml/ebmlmktracks.cpp matroska/ebml/ebmlmktags.cpp matroska/ebml/ebmlstringelement.cpp matroska/ebml/ebmluintelement.cpp + matroska/ebml/ebmlfloatelement.cpp matroska/ebml/ebmlutils.cpp matroska/ebml/ebmlvoidelement.cpp ) diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index e8bdbea3..25fdc52a 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -22,10 +22,12 @@ #include "ebmlvoidelement.h" #include "ebmlmasterelement.h" #include "ebmlbinaryelement.h" +#include "ebmlfloatelement.h" #include "ebmlmkseekhead.h" #include "ebmlmksegment.h" #include "ebmlmktags.h" #include "ebmlmkattachments.h" +#include "ebmlmktracks.h" #include "ebmlstringelement.h" #include "ebmluintelement.h" #include "ebmlutils.h" @@ -60,6 +62,12 @@ EBML::Element *EBML::Element::factory(File &file) case ElementIDs::MkSegment: return new MkSegment(sizeLength, dataSize, offset); + case ElementIDs::MkInfo: + return new MkInfo(sizeLength, dataSize, offset); + + case ElementIDs::MkTracks: + return new MkTracks(sizeLength, dataSize, offset); + case ElementIDs::MkTags: return new MkTags(sizeLength, dataSize, offset); @@ -71,6 +79,8 @@ EBML::Element *EBML::Element::factory(File &file) case ElementIDs::MkSimpleTag: case ElementIDs::MkAttachedFile: case ElementIDs::MkSeek: + case ElementIDs::MkTrackEntry: + case ElementIDs::MkAudio: return new MasterElement(id, sizeLength, dataSize, offset); case ElementIDs::MkTagName: @@ -81,17 +91,25 @@ EBML::Element *EBML::Element::factory(File &file) case ElementIDs::MkTagLanguage: case ElementIDs::MkAttachedFileMediaType: + case ElementIDs::MkCodecID: return new Latin1StringElement(id, sizeLength, dataSize); case ElementIDs::MkTagTargetTypeValue: case ElementIDs::MkAttachedFileUID: case ElementIDs::MkSeekPosition: + case ElementIDs::MkTimestampScale: + case ElementIDs::MkBitDepth: + case ElementIDs::MkChannels: return new UIntElement(id, sizeLength, dataSize); case ElementIDs::MkAttachedFileData: case ElementIDs::MkSeekID: return new BinaryElement(id, sizeLength, dataSize); + case ElementIDs::MkDuration: + case ElementIDs::MkSamplingFrequency: + return new FloatElement(id, sizeLength, dataSize); + case ElementIDs::MkSeekHead: return new MkSeekHead(sizeLength, dataSize, offset); diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index cb020303..5cc0e06e 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -98,6 +98,16 @@ namespace TagLib::EBML { inline constexpr Element::Id MkCueCodecState = 0xEA; inline constexpr Element::Id MkCueReference = 0xDB; inline constexpr Element::Id MkCueRefTime = 0x96; + inline constexpr Element::Id MkInfo = 0x1549A966; + inline constexpr Element::Id MkTimestampScale = 0x2AD7B1; + inline constexpr Element::Id MkDuration = 0x4489; + inline constexpr Element::Id MkTracks = 0x1654AE6B; + inline constexpr Element::Id MkTrackEntry = 0xAE; + inline constexpr Element::Id MkCodecID = 0x86; + inline constexpr Element::Id MkAudio = 0xE1; + inline constexpr Element::Id MkSamplingFrequency = 0xB5; + inline constexpr Element::Id MkBitDepth = 0x6264; + inline constexpr Element::Id MkChannels = 0x9F; } } diff --git a/taglib/matroska/ebml/ebmlfloatelement.cpp b/taglib/matroska/ebml/ebmlfloatelement.cpp new file mode 100644 index 00000000..4752d862 --- /dev/null +++ b/taglib/matroska/ebml/ebmlfloatelement.cpp @@ -0,0 +1,82 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlfloatelement.h" +#include "ebmlutils.h" +#include "tbytevector.h" +#include "tfile.h" +#include "tdebug.h" + +using namespace TagLib; + +double EBML::FloatElement::getValueAsDouble(double defaultValue) const +{ + if(std::holds_alternative(value)) { + return std::get(value); + } + if(std::holds_alternative(value)) { + return std::get(value); + } + return defaultValue; +} + +bool EBML::FloatElement::read(File &file) +{ + ByteVector buffer = file.readBlock(dataSize); + if(buffer.size() != dataSize) { + debug("Failed to read EBML Float element"); + return false; + } + + if(dataSize == 0) { + value = std::monostate(); + } + else if(dataSize == 4) { + value = buffer.toFloat32BE(0); + } + else if(dataSize == 8) { + value = buffer.toFloat64BE(0); + } + else { + debug("Invalid size for EBML Float element"); + return false; + } + return true; +} + +ByteVector EBML::FloatElement::render() +{ + ByteVector data; + if(std::holds_alternative(value)) { + data = ByteVector::fromFloat64BE(std::get(value)); + } + else if(std::holds_alternative(value)) { + data = ByteVector::fromFloat32BE(std::get(value)); + } + ByteVector buffer = renderId(); + buffer.append(renderVINT(data.size(), 0)); + buffer.append(data); + return buffer; +} diff --git a/taglib/matroska/ebml/ebmlfloatelement.h b/taglib/matroska/ebml/ebmlfloatelement.h new file mode 100644 index 00000000..a51e444c --- /dev/null +++ b/taglib/matroska/ebml/ebmlfloatelement.h @@ -0,0 +1,64 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLFLOATELEMENT_H +#define TAGLIB_EBMLFLOATELEMENT_H +#ifndef DO_NOT_DOCUMENT + +#include +#include "ebmlelement.h" + +namespace TagLib { + class File; + + namespace EBML { + class FloatElement : public Element + { + public: + using FloatVariantType = std::variant; + + FloatElement(Id id, int sizeLength, offset_t dataSize) : + Element(id, sizeLength, dataSize) + { + } + + explicit FloatElement(Id id) : + FloatElement(id, 0, 0) + { + } + FloatVariantType getValue() const { return value; } + double getValueAsDouble(double defaultValue = 0.0) const; + void setValue(FloatVariantType val) { value = val; } + bool read(File &file) override; + ByteVector render() override; + + private: + FloatVariantType value; + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlmkinfo.cpp b/taglib/matroska/ebml/ebmlmkinfo.cpp new file mode 100644 index 00000000..feba7c01 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkinfo.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmkinfo.h" +#include "ebmlstringelement.h" +#include "ebmluintelement.h" +#include "ebmlfloatelement.h" +#include "matroskaproperties.h" + +using namespace TagLib; + +void EBML::MkInfo::parse(Matroska::Properties *properties) +{ + if(!properties) + return; + + unsigned long long timestampScale = 1000000; + double duration = 0.0; + for(auto element : elements) { + Id id = element->getId(); + if (id == ElementIDs::MkTimestampScale) { + timestampScale = static_cast(element)->getValue(); + } + else if (id == ElementIDs::MkDuration) { + duration = static_cast(element)->getValueAsDouble(); + } + } + + properties->setLengthInMilliseconds( + static_cast(duration * static_cast(timestampScale) / 1000000.0)); +} diff --git a/taglib/matroska/ebml/ebmlmkinfo.h b/taglib/matroska/ebml/ebmlmkinfo.h new file mode 100644 index 00000000..479c7056 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkinfo.h @@ -0,0 +1,56 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLMKINFO_H +#define TAGLIB_EBMLMKINFO_H +#ifndef DO_NOT_DOCUMENT + +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + +namespace TagLib { + namespace Matroska { + class Properties; + } + namespace EBML { + class MkInfo : public MasterElement + { + public: + MkInfo(int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(ElementIDs::MkInfo, sizeLength, dataSize, offset) + { + } + MkInfo() : + MasterElement(ElementIDs::MkInfo, 0, 0, 0) + { + } + void parse(Matroska::Properties * properties); + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlmkseekhead.cpp b/taglib/matroska/ebml/ebmlmkseekhead.cpp index 144d94e8..477d30f2 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.cpp +++ b/taglib/matroska/ebml/ebmlmkseekhead.cpp @@ -25,9 +25,9 @@ using namespace TagLib; -Matroska::SeekHead *EBML::MkSeekHead::parse() +std::unique_ptr EBML::MkSeekHead::parse() { - auto seekHead = new Matroska::SeekHead(); + auto seekHead = std::make_unique(); seekHead->setOffset(offset); seekHead->setSize(getSize() + padding); @@ -50,7 +50,7 @@ Matroska::SeekHead *EBML::MkSeekHead::parse() if(entryId && offset) seekHead->addEntry(entryId, offset); else { - delete seekHead; + seekHead.reset(); return nullptr; } } diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index de77a64c..0de3d5e8 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -22,6 +22,8 @@ #include "ebmlmktags.h" #include "ebmlmkattachments.h" #include "ebmlmkseekhead.h" +#include "ebmlmkinfo.h" +#include "ebmlmktracks.h" #include "ebmlutils.h" #include "matroskafile.h" #include "matroskatag.h" @@ -36,6 +38,8 @@ EBML::MkSegment::~MkSegment() delete tags; delete attachments; delete seekHead; + delete info; + delete tracks; } bool EBML::MkSegment::read(File &file) @@ -52,6 +56,16 @@ bool EBML::MkSegment::read(File &file) if(!seekHead->read(file)) return false; } + else if(id == ElementIDs::MkInfo) { + info = static_cast(element); + if(!info->read(file)) + return false; + } + else if(id == ElementIDs::MkTracks) { + tracks = static_cast(element); + if(!tracks->read(file)) + return false; + } else if(id == ElementIDs::MkTags) { tags = static_cast(element); if(!tags->read(file)) @@ -95,3 +109,17 @@ Matroska::Segment *EBML::MkSegment::parseSegment() { return new Matroska::Segment(sizeLength, dataSize, offset + idSize(id)); } + +void EBML::MkSegment::parseInfo(Matroska::Properties *properties) +{ + if(info) { + info->parse(properties); + } +} + +void EBML::MkSegment::parseTracks(Matroska::Properties *properties) +{ + if (tracks) { + tracks->parse(properties); + } +} diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index 5ea0960c..15ab7db3 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -23,6 +23,8 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlmasterelement.h" +#include "ebmlmkinfo.h" +#include "ebmlmktracks.h" #include "taglib.h" namespace TagLib { @@ -49,11 +51,15 @@ namespace TagLib { Matroska::Attachments *parseAttachments(); Matroska::SeekHead *parseSeekHead(); Matroska::Segment *parseSegment(); + void parseInfo(Matroska::Properties *properties); + void parseTracks(Matroska::Properties *properties); private: MkTags *tags = nullptr; MkAttachments *attachments = nullptr; MkSeekHead *seekHead = nullptr; + MkInfo *info = nullptr; + MkTracks *tracks = nullptr; }; } } diff --git a/taglib/matroska/ebml/ebmlmktracks.cpp b/taglib/matroska/ebml/ebmlmktracks.cpp new file mode 100644 index 00000000..552b07c3 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmktracks.cpp @@ -0,0 +1,73 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmktracks.h" +#include "ebmlstringelement.h" +#include "ebmluintelement.h" +#include "ebmlfloatelement.h" +#include "matroskaproperties.h" + +using namespace TagLib; + +void EBML::MkTracks::parse(Matroska::Properties *properties) +{ + if(!properties) + return; + + for(auto element : elements) { + if(element->getId() != ElementIDs::MkTrackEntry) + continue; + + String codecId; + double samplingFrequency = 0.0; + unsigned long long bitDepth = 0; + unsigned long long channels = 0; + auto trackEntry = static_cast(element); + for(auto trackEntryChild : *trackEntry) { + Id trackEntryChildId = trackEntryChild->getId(); + if(trackEntryChildId == ElementIDs::MkCodecID) + codecId = static_cast(trackEntryChild)->getValue(); + else if(trackEntryChildId == ElementIDs::MkAudio) { + auto audio = static_cast(trackEntryChild); + for(auto audioChild : *audio) { + Id audioChildId = audioChild->getId(); + if(audioChildId == ElementIDs::MkSamplingFrequency) + samplingFrequency = static_cast(audioChild)->getValueAsDouble(); + else if(audioChildId == ElementIDs::MkBitDepth) + bitDepth = static_cast(audioChild)->getValue(); + else if(audioChildId == ElementIDs::MkChannels) + channels = static_cast(audioChild)->getValue(); + } + } + } + if(bitDepth || channels) { + properties->setSampleRate(static_cast(samplingFrequency)); + properties->setBitsPerSample(static_cast(bitDepth)); + properties->setChannels(static_cast(channels)); + properties->setCodecName(codecId); + return; + } + } +} diff --git a/taglib/matroska/ebml/ebmlmktracks.h b/taglib/matroska/ebml/ebmlmktracks.h new file mode 100644 index 00000000..53754fb7 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmktracks.h @@ -0,0 +1,56 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLMKTRACKS_H +#define TAGLIB_EBMLMKTRACKS_H +#ifndef DO_NOT_DOCUMENT + +#include "ebmlmasterelement.h" +#include "ebmlutils.h" +#include "taglib.h" + +namespace TagLib { + namespace Matroska { + class Properties; + } + namespace EBML { + class MkTracks : public MasterElement + { + public: + MkTracks(int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(ElementIDs::MkTracks, sizeLength, dataSize, offset) + { + } + MkTracks() : + MasterElement(ElementIDs::MkTracks, 0, 0, 0) + { + } + void parse(Matroska::Properties *properties); + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index b0b02c60..7e9e19f5 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -44,6 +44,7 @@ public: delete tag; delete attachments; delete seekHead; + delete segment; } FilePrivate(const FilePrivate &) = delete; @@ -52,6 +53,7 @@ public: Attachments *attachments = nullptr; SeekHead *seekHead = nullptr; Segment *segment = nullptr; + std::unique_ptr properties; }; //////////////////////////////////////////////////////////////////////////////// @@ -96,6 +98,11 @@ Matroska::File::File(IOStream *stream, bool readProperties, Matroska::File::~File() = default; +AudioProperties *Matroska::File::audioProperties() const +{ + return d->properties.get(); +} + Tag *Matroska::File::tag() const { return tag(true); @@ -123,7 +130,7 @@ Matroska::Attachments *Matroska::File::attachments(bool create) const } } -void Matroska::File::read(bool, Properties::ReadStyle) +void Matroska::File::read(bool readProperties, Properties::ReadStyle) { offset_t fileLength = length(); @@ -162,6 +169,12 @@ void Matroska::File::read(bool, Properties::ReadStyle) d->attachments = segment->parseAttachments(); setValid(true); + + if(readProperties) { + d->properties = std::make_unique(this); + segment->parseInfo(d->properties.get()); + segment->parseTracks(d->properties.get()); + } } bool Matroska::File::save() diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index f126fb61..cb9088b9 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -40,7 +40,7 @@ namespace TagLib::Matroska { ~File() override; File(const File &) = delete; File &operator=(const File &) = delete; - AudioProperties *audioProperties() const override { return nullptr; } + AudioProperties *audioProperties() const override; TagLib::Tag *tag() const override; Attachments *attachments(bool create = false) const; Tag *tag(bool create) const; @@ -60,6 +60,7 @@ namespace TagLib::Matroska { private: void read(bool readProperties, Properties::ReadStyle readStyle); class FilePrivate; + friend class Properties; TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; }; diff --git a/taglib/matroska/matroskaproperties.cpp b/taglib/matroska/matroskaproperties.cpp index 8827db36..9bb9d0fc 100644 --- a/taglib/matroska/matroskaproperties.cpp +++ b/taglib/matroska/matroskaproperties.cpp @@ -25,23 +25,36 @@ #include "matroskaproperties.h" +#include "matroskafile.h" + using namespace TagLib; class Matroska::Properties::PropertiesPrivate { public: + explicit PropertiesPrivate(File *file) : file(file) {} + ~PropertiesPrivate() = default; + + PropertiesPrivate(const PropertiesPrivate &) = delete; + PropertiesPrivate &operator=(const PropertiesPrivate &) = delete; + + File *file; + String codecName; int length { 0 }; - int bitrate { 0 }; + int bitrate { -1 }; int sampleRate { 0 }; - int bitsPerSample { 0 }; int channels { 0 }; + int bitsPerSample { 0 }; }; +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + Matroska::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style), - d(std::make_unique()) + d(std::make_unique(file)) { - read(file); } Matroska::Properties::~Properties() = default; @@ -53,6 +66,9 @@ int Matroska::Properties::lengthInMilliseconds() const int Matroska::Properties::bitrate() const { + if (d->bitrate == -1) { + d->bitrate = d->length != 0 ? static_cast(d->file->length() * 8 / d->length) : 0; + } return d->bitrate; } @@ -66,11 +82,46 @@ int Matroska::Properties::channels() const return d->channels; } +int Matroska::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +String Matroska::Properties::codecName() const +{ + return d->codecName; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void Matroska::Properties::read(File *) +void Matroska::Properties::setLengthInMilliseconds(int length) { - // TODO implement. + d->length = length; +} + +void Matroska::Properties::setBitrate(int bitrate) +{ + d->bitrate = bitrate; +} + +void Matroska::Properties::setSampleRate(int sampleRate) +{ + d->sampleRate = sampleRate; +} + +void Matroska::Properties::setChannels(int channels) +{ + d->channels = channels; +} + +void Matroska::Properties::setBitsPerSample(int bitsPerSample) +{ + d->bitsPerSample = bitsPerSample; +} + +void Matroska::Properties::setCodecName(const String &codecName) +{ + d->codecName = codecName; } diff --git a/taglib/matroska/matroskaproperties.h b/taglib/matroska/matroskaproperties.h index 7a4f36e4..805d3dbe 100644 --- a/taglib/matroska/matroskaproperties.h +++ b/taglib/matroska/matroskaproperties.h @@ -1,3 +1,8 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + /*************************************************************************** * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License version * @@ -24,15 +29,25 @@ #include "taglib_export.h" #include "audioproperties.h" +namespace TagLib::EBML { + class MkTracks; + class MkInfo; +} namespace TagLib::Matroska { - class File; //! An implementation of Matroska audio properties class TAGLIB_EXPORT Properties : public AudioProperties { public: + /*! + * Creates an instance of Matroska::Properties. + */ explicit Properties(File *file, ReadStyle style = Average); + + /*! + * Destroys this Matroska::Properties instance. + */ ~Properties() override; Properties(const Properties &) = delete; @@ -60,10 +75,29 @@ namespace TagLib::Matroska { */ int channels() const override; - private: - void read(File *file); + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + /*! + * Returns the concrete codec name, for example "A_MPEG/L3" + * used in the file if available, otherwise an empty string. + */ + String codecName() const; + + private: class PropertiesPrivate; + friend class EBML::MkInfo; + friend class EBML::MkTracks; + + void setLengthInMilliseconds(int length); + void setBitrate(int bitrate); + void setSampleRate(int sampleRate); + void setChannels(int channels); + void setBitsPerSample(int bitsPerSample); + void setCodecName(const String &codecName); + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; }; From 8d02484804e0dd7f3c76c8b3b32e85448c1a14d8 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Tue, 19 Aug 2025 16:33:40 +0200 Subject: [PATCH 13/31] Replace raw pointers by smart pointers Also improve type safety and consistency. --- taglib/matroska/ebml/ebmlbinaryelement.h | 4 + taglib/matroska/ebml/ebmlelement.cpp | 127 ++++++------- taglib/matroska/ebml/ebmlelement.h | 210 +++++++++++++++------ taglib/matroska/ebml/ebmlfloatelement.h | 4 + taglib/matroska/ebml/ebmlmasterelement.cpp | 13 +- taglib/matroska/ebml/ebmlmasterelement.h | 14 +- taglib/matroska/ebml/ebmlmkattachments.cpp | 32 ++-- taglib/matroska/ebml/ebmlmkattachments.h | 10 +- taglib/matroska/ebml/ebmlmkcues.cpp | 62 +++--- taglib/matroska/ebml/ebmlmkcues.h | 4 +- taglib/matroska/ebml/ebmlmkinfo.cpp | 10 +- taglib/matroska/ebml/ebmlmkinfo.h | 8 +- taglib/matroska/ebml/ebmlmkseekhead.cpp | 16 +- taglib/matroska/ebml/ebmlmkseekhead.h | 10 +- taglib/matroska/ebml/ebmlmksegment.cpp | 44 ++--- taglib/matroska/ebml/ebmlmksegment.h | 30 +-- taglib/matroska/ebml/ebmlmktags.cpp | 54 +++--- taglib/matroska/ebml/ebmlmktags.h | 10 +- taglib/matroska/ebml/ebmlmktracks.cpp | 30 +-- taglib/matroska/ebml/ebmlmktracks.h | 8 +- taglib/matroska/ebml/ebmlstringelement.h | 4 + taglib/matroska/ebml/ebmluintelement.h | 4 + taglib/matroska/ebml/ebmlutils.cpp | 9 +- taglib/matroska/ebml/ebmlutils.h | 11 +- taglib/matroska/ebml/ebmlvoidelement.h | 7 +- taglib/matroska/matroskaattachments.cpp | 28 +-- taglib/matroska/matroskacues.cpp | 58 +++--- taglib/matroska/matroskacues.h | 16 +- taglib/matroska/matroskaelement.h | 7 - taglib/matroska/matroskafile.cpp | 55 +++--- taglib/matroska/matroskaseekhead.cpp | 19 +- taglib/matroska/matroskaseekhead.h | 2 +- taglib/matroska/matroskasegment.cpp | 8 + taglib/matroska/matroskasegment.h | 8 +- taglib/matroska/matroskatag.cpp | 38 ++-- 35 files changed, 552 insertions(+), 422 deletions(-) diff --git a/taglib/matroska/ebml/ebmlbinaryelement.h b/taglib/matroska/ebml/ebmlbinaryelement.h index b8dd686e..bcf9fd70 100644 --- a/taglib/matroska/ebml/ebmlbinaryelement.h +++ b/taglib/matroska/ebml/ebmlbinaryelement.h @@ -34,6 +34,10 @@ namespace TagLib { Element(id, sizeLength, dataSize) { } + BinaryElement(Id id, int sizeLength, offset_t dataSize, offset_t) : + Element(id, sizeLength, dataSize) + { + } explicit BinaryElement(Id id) : Element(id, 0, 0) diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 25fdc52a..7c947e85 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -39,12 +39,15 @@ using namespace TagLib; -EBML::Element *EBML::Element::factory(File &file) +#define RETURN_ELEMENT_FOR_CASE(eid) \ + case (eid): return make_unique_element(id, sizeLength, dataSize, offset) + +std::unique_ptr EBML::Element::factory(File &file) { // Get the element ID offset_t offset = file.tell(); - Id id = readId(file); - if(!id) { + unsigned int uintId = readId(file); + if(!uintId) { debug("Failed to parse EMBL ElementID"); return nullptr; } @@ -55,73 +58,62 @@ EBML::Element *EBML::Element::factory(File &file) return nullptr; // Return the subclass + // The enum switch without default will give us a warning if an ID is missing + auto id = static_cast(uintId); switch(id) { - case ElementIDs::EBMLHeader: - return new Element(id, sizeLength, dataSize); - - case ElementIDs::MkSegment: - return new MkSegment(sizeLength, dataSize, offset); - - case ElementIDs::MkInfo: - return new MkInfo(sizeLength, dataSize, offset); - - case ElementIDs::MkTracks: - return new MkTracks(sizeLength, dataSize, offset); - - case ElementIDs::MkTags: - return new MkTags(sizeLength, dataSize, offset); - - case ElementIDs::MkAttachments: - return new MkAttachments(sizeLength, dataSize, offset); - - case ElementIDs::MkTag: - case ElementIDs::MkTagTargets: - case ElementIDs::MkSimpleTag: - case ElementIDs::MkAttachedFile: - case ElementIDs::MkSeek: - case ElementIDs::MkTrackEntry: - case ElementIDs::MkAudio: - return new MasterElement(id, sizeLength, dataSize, offset); - - case ElementIDs::MkTagName: - case ElementIDs::MkTagString: - case ElementIDs::MkAttachedFileName: - case ElementIDs::MkAttachedFileDescription: - return new UTF8StringElement(id, sizeLength, dataSize); - - case ElementIDs::MkTagLanguage: - case ElementIDs::MkAttachedFileMediaType: - case ElementIDs::MkCodecID: - return new Latin1StringElement(id, sizeLength, dataSize); - - case ElementIDs::MkTagTargetTypeValue: - case ElementIDs::MkAttachedFileUID: - case ElementIDs::MkSeekPosition: - case ElementIDs::MkTimestampScale: - case ElementIDs::MkBitDepth: - case ElementIDs::MkChannels: - return new UIntElement(id, sizeLength, dataSize); - - case ElementIDs::MkAttachedFileData: - case ElementIDs::MkSeekID: - return new BinaryElement(id, sizeLength, dataSize); - - case ElementIDs::MkDuration: - case ElementIDs::MkSamplingFrequency: - return new FloatElement(id, sizeLength, dataSize); - - case ElementIDs::MkSeekHead: - return new MkSeekHead(sizeLength, dataSize, offset); - - case ElementIDs::VoidElement: - return new VoidElement(sizeLength, dataSize); - - default: - return new Element(id, sizeLength, dataSize); + RETURN_ELEMENT_FOR_CASE(Id::EBMLHeader); + RETURN_ELEMENT_FOR_CASE(Id::MkSegment); + RETURN_ELEMENT_FOR_CASE(Id::MkInfo); + RETURN_ELEMENT_FOR_CASE(Id::MkTracks); + RETURN_ELEMENT_FOR_CASE(Id::MkTags); + RETURN_ELEMENT_FOR_CASE(Id::MkAttachments); + RETURN_ELEMENT_FOR_CASE(Id::MkTag); + RETURN_ELEMENT_FOR_CASE(Id::MkTagTargets); + RETURN_ELEMENT_FOR_CASE(Id::MkSimpleTag); + RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFile); + RETURN_ELEMENT_FOR_CASE(Id::MkSeek); + RETURN_ELEMENT_FOR_CASE(Id::MkTrackEntry); + RETURN_ELEMENT_FOR_CASE(Id::MkAudio); + RETURN_ELEMENT_FOR_CASE(Id::MkTagName); + RETURN_ELEMENT_FOR_CASE(Id::MkTagString); + RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileName); + RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileDescription); + RETURN_ELEMENT_FOR_CASE(Id::MkTagLanguage); + RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileMediaType); + RETURN_ELEMENT_FOR_CASE(Id::MkCodecID); + RETURN_ELEMENT_FOR_CASE(Id::MkTagTargetTypeValue); + RETURN_ELEMENT_FOR_CASE(Id::MkTagsLanguageDefault); + RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileUID); + RETURN_ELEMENT_FOR_CASE(Id::MkSeekPosition); + RETURN_ELEMENT_FOR_CASE(Id::MkTimestampScale); + RETURN_ELEMENT_FOR_CASE(Id::MkBitDepth); + RETURN_ELEMENT_FOR_CASE(Id::MkChannels); + RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileData); + RETURN_ELEMENT_FOR_CASE(Id::MkSeekID); + RETURN_ELEMENT_FOR_CASE(Id::MkDuration); + RETURN_ELEMENT_FOR_CASE(Id::MkSamplingFrequency); + RETURN_ELEMENT_FOR_CASE(Id::MkSeekHead); + RETURN_ELEMENT_FOR_CASE(Id::VoidElement); + RETURN_ELEMENT_FOR_CASE(Id::MkCluster); + RETURN_ELEMENT_FOR_CASE(Id::MkCodecState); + RETURN_ELEMENT_FOR_CASE(Id::MkTagBinary); + RETURN_ELEMENT_FOR_CASE(Id::MkCues); + RETURN_ELEMENT_FOR_CASE(Id::MkCuePoint); + RETURN_ELEMENT_FOR_CASE(Id::MkCueTime); + RETURN_ELEMENT_FOR_CASE(Id::MkCueTrackPositions); + RETURN_ELEMENT_FOR_CASE(Id::MkCueTrack); + RETURN_ELEMENT_FOR_CASE(Id::MkCueClusterPosition); + RETURN_ELEMENT_FOR_CASE(Id::MkCueRelativePosition); + RETURN_ELEMENT_FOR_CASE(Id::MkCueDuration); + RETURN_ELEMENT_FOR_CASE(Id::MkCueBlockNumber); + RETURN_ELEMENT_FOR_CASE(Id::MkCueCodecState); + RETURN_ELEMENT_FOR_CASE(Id::MkCueReference); + RETURN_ELEMENT_FOR_CASE(Id::MkCueRefTime); } + return std::make_unique(id, sizeLength, dataSize); } -EBML::Element::Id EBML::Element::readId(File &file) +unsigned int EBML::Element::readId(File &file) { auto buffer = file.readBlock(1); if(buffer.size() != 1) { @@ -161,6 +153,7 @@ ByteVector EBML::Element::renderId() const { int numBytes = idSize(id); static const auto byteOrder = Utils::systemByteOrder(); - uint32_t data = byteOrder == Utils::LittleEndian ? Utils::byteSwap(id) : id; + auto uintId = static_cast(id); + uint32_t data = byteOrder == Utils::LittleEndian ? Utils::byteSwap(uintId) : uintId; return ByteVector(reinterpret_cast(&data) + (4 - numBytes), numBytes); } diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 5cc0e06e..8f6a3fe8 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -22,6 +22,7 @@ #define TAGLIB_EBMLELEMENT_H #ifndef DO_NOT_DOCUMENT +#include #include "tfile.h" #include "tutils.h" #include "taglib.h" @@ -30,11 +31,67 @@ namespace TagLib::EBML { class Element { public: - using Id = unsigned int; + enum class Id : unsigned int + { + EBMLHeader = 0x1A45DFA3, + VoidElement = 0xEC, + MkSegment = 0x18538067, + MkTags = 0x1254C367, + MkTag = 0x7373, + MkTagTargets = 0x63C0, + MkTagTargetTypeValue = 0x68CA, + MkSimpleTag = 0x67C8, + MkTagName = 0x45A3, + MkTagLanguage = 0x447A, + MkTagBinary = 0x4485, + MkTagString = 0x4487, + MkTagsTagLanguage = 0x447A, + MkTagsLanguageDefault = 0x4484, + MkAttachments = 0x1941A469, + MkAttachedFile = 0x61A7, + MkAttachedFileDescription = 0x467E, + MkAttachedFileName = 0x466E, + MkAttachedFileMediaType = 0x4660, + MkAttachedFileData = 0x465C, + MkAttachedFileUID = 0x46AE, + MkSeekHead = 0x114D9B74, + MkSeek = 0x4DBB, + MkSeekID = 0x53AB, + MkSeekPosition = 0x53AC, + MkCluster = 0x1F43B675, + MkCodecState = 0xA4, + MkCues = 0x1C53BB6B, + MkCuePoint = 0xBB, + MkCueTime = 0xB3, + MkCueTrackPositions = 0xB7, + MkCueTrack = 0xF7, + MkCueClusterPosition = 0xF1, + MkCueRelativePosition = 0xF0, + MkCueDuration = 0xB2, + MkCueBlockNumber = 0x5378, + MkCueCodecState = 0xEA, + MkCueReference = 0xDB, + MkCueRefTime = 0x96, + MkInfo = 0x1549A966, + MkTimestampScale = 0x2AD7B1, + MkDuration = 0x4489, + MkTracks = 0x1654AE6B, + MkTrackEntry = 0xAE, + MkCodecID = 0x86, + MkAudio = 0xE1, + MkSamplingFrequency = 0xB5, + MkBitDepth = 0x6264, + MkChannels = 0x9F, + }; + Element(Id id, int sizeLength, offset_t dataSize) : id(id), sizeLength(sizeLength), dataSize(dataSize) { } + Element(Id id, int sizeLength, offset_t dataSize, offset_t) : + id(id), sizeLength(sizeLength), dataSize(dataSize) + { + } virtual ~Element() = default; virtual bool read(File &file) { @@ -49,8 +106,8 @@ namespace TagLib::EBML { int64_t getDataSize() const { return dataSize; } ByteVector renderId() const; virtual ByteVector render(); - static Element *factory(File &file); - static Id readId(File &file); + static std::unique_ptr factory(File &file); + static unsigned int readId(File &file); protected: Id id; @@ -58,57 +115,104 @@ namespace TagLib::EBML { offset_t dataSize; }; - namespace ElementIDs { - inline constexpr Element::Id EBMLHeader = 0x1A45DFA3; - inline constexpr Element::Id VoidElement = 0xEC; - 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 MkTagBinary = 0x4485; - inline constexpr Element::Id MkTagString = 0x4487; - inline constexpr Element::Id MkTagsTagLanguage = 0x447A; - inline constexpr Element::Id MkTagsLanguageDefault = 0x4484; - inline constexpr Element::Id MkAttachments = 0x1941A469; - inline constexpr Element::Id MkAttachedFile = 0x61A7; - inline constexpr Element::Id MkAttachedFileDescription = 0x467E; - inline constexpr Element::Id MkAttachedFileName = 0x466E; - inline constexpr Element::Id MkAttachedFileMediaType = 0x4660; - inline constexpr Element::Id MkAttachedFileData = 0x465C; - inline constexpr Element::Id MkAttachedFileUID = 0x46AE; - inline constexpr Element::Id MkSeekHead = 0x114D9B74; - inline constexpr Element::Id MkSeek = 0x4DBB; - inline constexpr Element::Id MkSeekID = 0x53AB; - inline constexpr Element::Id MkSeekPosition = 0x53AC; - inline constexpr Element::Id MkCluster = 0x1F43B675; - inline constexpr Element::Id MkCodecState = 0xA4; - inline constexpr Element::Id MkCues = 0x1C53BB6B; - inline constexpr Element::Id MkCuePoint = 0xBB; - inline constexpr Element::Id MkCueTime = 0xB3; - inline constexpr Element::Id MkCueTrackPositions = 0xB7; - inline constexpr Element::Id MkCueTrack = 0xF7; - inline constexpr Element::Id MkCueClusterPosition = 0xF1; - inline constexpr Element::Id MkCueRelativePosition = 0xF0; - inline constexpr Element::Id MkCueDuration = 0xB2; - inline constexpr Element::Id MkCueBlockNumber = 0x5378; - inline constexpr Element::Id MkCueCodecState = 0xEA; - inline constexpr Element::Id MkCueReference = 0xDB; - inline constexpr Element::Id MkCueRefTime = 0x96; - inline constexpr Element::Id MkInfo = 0x1549A966; - inline constexpr Element::Id MkTimestampScale = 0x2AD7B1; - inline constexpr Element::Id MkDuration = 0x4489; - inline constexpr Element::Id MkTracks = 0x1654AE6B; - inline constexpr Element::Id MkTrackEntry = 0xAE; - inline constexpr Element::Id MkCodecID = 0x86; - inline constexpr Element::Id MkAudio = 0xE1; - inline constexpr Element::Id MkSamplingFrequency = 0xB5; - inline constexpr Element::Id MkBitDepth = 0x6264; - inline constexpr Element::Id MkChannels = 0x9F; + // Template specializations to ensure that elements for the different IDs + // are consistently created and cast. They provide a mapping between IDs + // and Element subclasses. The switch in factory() makes sure that the + // template for all IDs are instantiated, i.e. that every ID has its Element + // subclass mapped. + class MasterElement; + class UIntElement; + class BinaryElement; + class FloatElement; + class MkSegment; + class MkInfo; + class MkTracks; + class MkTags; + class MkAttachments; + class MkSeekHead; + class VoidElement; + + template + class StringElement; + using UTF8StringElement = StringElement; + using Latin1StringElement = StringElement; + + template + struct GetElementTypeById; + + template <> struct GetElementTypeById { using type = Element; }; + template <> struct GetElementTypeById { using type = MkSegment; }; + template <> struct GetElementTypeById { using type = MkInfo; }; + template <> struct GetElementTypeById { using type = MkTracks; }; + template <> struct GetElementTypeById { using type = MkTags; }; + template <> struct GetElementTypeById { using type = MkAttachments; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = BinaryElement; }; + template <> struct GetElementTypeById { using type = BinaryElement; }; + template <> struct GetElementTypeById { using type = BinaryElement; }; + template <> struct GetElementTypeById { using type = BinaryElement; }; + template <> struct GetElementTypeById { using type = FloatElement; }; + template <> struct GetElementTypeById { using type = FloatElement; }; + template <> struct GetElementTypeById { using type = MkSeekHead; }; + template <> struct GetElementTypeById { using type = VoidElement; }; + + template ::type> + const T *element_cast(const std::unique_ptr &ptr) + { + return static_cast(ptr.get()); } + + template ::type> + std::unique_ptr element_cast(std::unique_ptr &&ptr) + { + return std::unique_ptr(static_cast(ptr.release())); + } + + template ::type> + std::unique_ptr make_unique_element(Element::Id id, int sizeLength, offset_t dataSize, offset_t offset) + { + return std::make_unique(id, sizeLength, dataSize, offset); + } + + template ::type> + std::unique_ptr make_unique_element() + { + return std::make_unique(ID, 0, 0, 0); + } + } #endif diff --git a/taglib/matroska/ebml/ebmlfloatelement.h b/taglib/matroska/ebml/ebmlfloatelement.h index a51e444c..2c9ef59c 100644 --- a/taglib/matroska/ebml/ebmlfloatelement.h +++ b/taglib/matroska/ebml/ebmlfloatelement.h @@ -43,6 +43,10 @@ namespace TagLib { Element(id, sizeLength, dataSize) { } + FloatElement(Id id, int sizeLength, offset_t dataSize, offset_t) : + Element(id, sizeLength, dataSize) + { + } explicit FloatElement(Id id) : FloatElement(id, 0, 0) diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp index cfaea47e..fd0447dd 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.cpp +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -25,20 +25,21 @@ using namespace TagLib; -EBML::MasterElement::~MasterElement() +EBML::MasterElement::~MasterElement() = default; + +void EBML::MasterElement::appendElement(std::unique_ptr&& element) { - for(auto element : elements) - delete element; + elements.push_back(std::move(element)); } bool EBML::MasterElement::read(File &file) { offset_t maxOffset = file.tell() + dataSize; - Element *element = nullptr; + std::unique_ptr element; while((element = findNextElement(file, maxOffset))) { if(!element->read(file)) return false; - elements.append(element); + elements.push_back(std::move(element)); } return file.tell() == maxOffset; } @@ -47,7 +48,7 @@ ByteVector EBML::MasterElement::render() { ByteVector buffer = renderId(); ByteVector data; - for(auto element : elements) + for(const auto &element : elements) data.append(element->render()); dataSize = data.size(); buffer.append(renderVINT(dataSize, 0)); diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h index cdccee75..c7f7a846 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.h +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -44,11 +44,13 @@ namespace TagLib::EBML { offset_t getOffset() const { return offset; } 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(); } - List::ConstIterator cend() const { return elements.cend(); } + void appendElement(std::unique_ptr &&element); + std::list>::iterator begin() { return elements.begin(); } + std::list>::iterator end() { return elements.end(); } + std::list>::const_iterator begin() const { return elements.begin(); } + std::list>::const_iterator end() const { return elements.end(); } + std::list>::const_iterator cbegin() const { return elements.cbegin(); } + std::list>::const_iterator cend() const { return elements.cend(); } offset_t getPadding() const { return padding; } void setPadding(offset_t padding) { this->padding = padding; } offset_t getMinRenderSize() const { return minRenderSize; } @@ -58,7 +60,7 @@ namespace TagLib::EBML { offset_t offset; offset_t padding = 0; offset_t minRenderSize = 0; - List elements; + std::list> elements; }; } diff --git a/taglib/matroska/ebml/ebmlmkattachments.cpp b/taglib/matroska/ebml/ebmlmkattachments.cpp index 35c35700..ed8ab2b9 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.cpp +++ b/taglib/matroska/ebml/ebmlmkattachments.cpp @@ -27,14 +27,14 @@ using namespace TagLib; -Matroska::Attachments *EBML::MkAttachments::parse() +std::unique_ptr EBML::MkAttachments::parse() { - auto attachments = new Matroska::Attachments(); + auto attachments = std::make_unique(); attachments->setOffset(offset); attachments->setSize(getSize()); - for(auto element : elements) { - if(element->getId() != ElementIDs::MkAttachedFile) + for(const auto &element : elements) { + if(element->getId() != Id::MkAttachedFile) continue; const String *filename = nullptr; @@ -42,19 +42,19 @@ Matroska::Attachments *EBML::MkAttachments::parse() const String *mediaType = nullptr; const ByteVector *data = nullptr; Matroska::AttachedFile::UID uid = 0; - auto attachedFile = static_cast(element); - for(auto attachedFileChild : *attachedFile) { + auto attachedFile = element_cast(element); + for(const auto &attachedFileChild : *attachedFile) { Id id = attachedFileChild->getId(); - if(id == ElementIDs::MkAttachedFileName) - filename = &(static_cast(attachedFileChild)->getValue()); - else if(id == ElementIDs::MkAttachedFileData) - data = &(static_cast(attachedFileChild)->getValue()); - else if(id == ElementIDs::MkAttachedFileDescription) - description = &(static_cast(attachedFileChild)->getValue()); - else if(id == ElementIDs::MkAttachedFileMediaType) - mediaType = &(static_cast(attachedFileChild)->getValue()); - else if(id == ElementIDs::MkAttachedFileUID) - uid = static_cast(attachedFileChild)->getValue(); + if(id == Id::MkAttachedFileName) + filename = &(element_cast(attachedFileChild)->getValue()); + else if(id == Id::MkAttachedFileData) + data = &(element_cast(attachedFileChild)->getValue()); + else if(id == Id::MkAttachedFileDescription) + description = &(element_cast(attachedFileChild)->getValue()); + else if(id == Id::MkAttachedFileMediaType) + mediaType = &(element_cast(attachedFileChild)->getValue()); + else if(id == Id::MkAttachedFileUID) + uid = element_cast(attachedFileChild)->getValue(); } if(!(filename && data)) continue; diff --git a/taglib/matroska/ebml/ebmlmkattachments.h b/taglib/matroska/ebml/ebmlmkattachments.h index 228493d5..02f27648 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.h +++ b/taglib/matroska/ebml/ebmlmkattachments.h @@ -35,14 +35,18 @@ namespace TagLib { { public: MkAttachments(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(ElementIDs::MkAttachments, sizeLength, dataSize, offset) + MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) + { + } + MkAttachments(Id, int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) { } MkAttachments() : - MasterElement(ElementIDs::MkAttachments, 0, 0, 0) + MasterElement(Id::MkAttachments, 0, 0, 0) { } - Matroska::Attachments *parse(); + std::unique_ptr parse(); }; } } diff --git a/taglib/matroska/ebml/ebmlmkcues.cpp b/taglib/matroska/ebml/ebmlmkcues.cpp index c0d1bc10..a97f7dd2 100644 --- a/taglib/matroska/ebml/ebmlmkcues.cpp +++ b/taglib/matroska/ebml/ebmlmkcues.cpp @@ -29,48 +29,48 @@ Matroska::Cues *EBML::MkCues::parse() auto cues = new Matroska::Cues(); cues->setOffset(offset); cues->setSize(getSize()); - cues->setID(id); + cues->setID(static_cast(id)); - for(auto cuesChild : elements) { - if(cuesChild->getId() != ElementIDs::MkCuePoint) + for(const auto &cuesChild : elements) { + if(cuesChild->getId() != Id::MkCuePoint) continue; - auto cuePointElement = static_cast(cuesChild); - auto cuePoint = new Matroska::CuePoint(); + auto cuePointElement = element_cast(cuesChild); + auto cuePoint = std::make_unique(); - for(auto cuePointChild : *cuePointElement) { + for(const auto &cuePointChild : *cuePointElement) { Id id = cuePointChild->getId(); - if(id == ElementIDs::MkCueTime) - cuePoint->setTime(static_cast(cuePointChild)->getValue()); - else if(id == ElementIDs::MkCueTrackPositions) { - auto cueTrack = new Matroska::CueTrack(); - auto cueTrackElement = static_cast(cuePointChild); - for(auto cueTrackChild : *cueTrackElement) { + if(id == Id::MkCueTime) + cuePoint->setTime(element_cast(cuePointChild)->getValue()); + else if(id == Id::MkCueTrackPositions) { + auto cueTrack = std::make_unique(); + auto cueTrackElement = element_cast(cuePointChild); + for(const auto &cueTrackChild : *cueTrackElement) { Id trackId = cueTrackChild->getId(); - if(trackId == ElementIDs::MkCueTrack) - cueTrack->setTrackNumber(static_cast(cueTrackChild)->getValue()); - else if(trackId == ElementIDs::MkCueClusterPosition) - cueTrack->setClusterPosition(static_cast(cueTrackChild)->getValue()); - else if(trackId == ElementIDs::MkCueRelativePosition) - cueTrack->setRelativePosition(static_cast(cueTrackChild)->getValue()); - else if(trackId == ElementIDs::MkCueDuration) - cueTrack->setDuration(static_cast(cueTrackChild)->getValue()); - else if(trackId == ElementIDs::MkCueBlockNumber) - cueTrack->setBlockNumber(static_cast(cueTrackChild)->getValue()); - else if(trackId == ElementIDs::MkCueCodecState) - cueTrack->setCodecState(static_cast(cueTrackChild)->getValue()); - else if(trackId == ElementIDs::MkCueReference) { - auto cueReference = static_cast(cueTrackChild); - for(auto cueReferenceChild : *cueReference) { - if(cueReferenceChild->getId() != ElementIDs::MkCueReference) + if(trackId == Id::MkCueTrack) + cueTrack->setTrackNumber(element_cast(cueTrackChild)->getValue()); + else if(trackId == Id::MkCueClusterPosition) + cueTrack->setClusterPosition(element_cast(cueTrackChild)->getValue()); + else if(trackId == Id::MkCueRelativePosition) + cueTrack->setRelativePosition(element_cast(cueTrackChild)->getValue()); + else if(trackId == Id::MkCueDuration) + cueTrack->setDuration(element_cast(cueTrackChild)->getValue()); + else if(trackId == Id::MkCueBlockNumber) + cueTrack->setBlockNumber(element_cast(cueTrackChild)->getValue()); + else if(trackId == Id::MkCueCodecState) + cueTrack->setCodecState(element_cast(cueTrackChild)->getValue()); + else if(trackId == Id::MkCueReference) { + auto cueReference = element_cast(cueTrackChild); + for(const auto &cueReferenceChild : *cueReference) { + if(cueReferenceChild->getId() != Id::MkCueRefTime) continue; - cueTrack->addReferenceTime(static_cast(cueReferenceChild)->getValue()); + cueTrack->addReferenceTime(element_cast(cueReferenceChild)->getValue()); } } } - cuePoint->addCueTrack(cueTrack); + cuePoint->addCueTrack(std::move(cueTrack)); } } - cues->addCuePoint(cuePoint); + cues->addCuePoint(std::move(cuePoint)); } return cues; } diff --git a/taglib/matroska/ebml/ebmlmkcues.h b/taglib/matroska/ebml/ebmlmkcues.h index 2d31826e..79530513 100644 --- a/taglib/matroska/ebml/ebmlmkcues.h +++ b/taglib/matroska/ebml/ebmlmkcues.h @@ -36,11 +36,11 @@ namespace TagLib { { public: MkCues(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(ElementIDs::MkCues, sizeLength, dataSize, offset) + MasterElement(Id::MkCues, sizeLength, dataSize, offset) { } MkCues() : - MasterElement(ElementIDs::MkCues, 0, 0, 0) + MasterElement(Id::MkCues, 0, 0, 0) { } diff --git a/taglib/matroska/ebml/ebmlmkinfo.cpp b/taglib/matroska/ebml/ebmlmkinfo.cpp index feba7c01..c6c48f3c 100644 --- a/taglib/matroska/ebml/ebmlmkinfo.cpp +++ b/taglib/matroska/ebml/ebmlmkinfo.cpp @@ -38,13 +38,13 @@ void EBML::MkInfo::parse(Matroska::Properties *properties) unsigned long long timestampScale = 1000000; double duration = 0.0; - for(auto element : elements) { + for(const auto &element : elements) { Id id = element->getId(); - if (id == ElementIDs::MkTimestampScale) { - timestampScale = static_cast(element)->getValue(); + if (id == Id::MkTimestampScale) { + timestampScale = element_cast(element)->getValue(); } - else if (id == ElementIDs::MkDuration) { - duration = static_cast(element)->getValueAsDouble(); + else if (id == Id::MkDuration) { + duration = element_cast(element)->getValueAsDouble(); } } diff --git a/taglib/matroska/ebml/ebmlmkinfo.h b/taglib/matroska/ebml/ebmlmkinfo.h index 479c7056..672ea217 100644 --- a/taglib/matroska/ebml/ebmlmkinfo.h +++ b/taglib/matroska/ebml/ebmlmkinfo.h @@ -40,11 +40,15 @@ namespace TagLib { { public: MkInfo(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(ElementIDs::MkInfo, sizeLength, dataSize, offset) + MasterElement(Id::MkInfo, sizeLength, dataSize, offset) + { + } + MkInfo(Id, int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkInfo, sizeLength, dataSize, offset) { } MkInfo() : - MasterElement(ElementIDs::MkInfo, 0, 0, 0) + MasterElement(Id::MkInfo, 0, 0, 0) { } void parse(Matroska::Properties * properties); diff --git a/taglib/matroska/ebml/ebmlmkseekhead.cpp b/taglib/matroska/ebml/ebmlmkseekhead.cpp index 477d30f2..20ae34bf 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.cpp +++ b/taglib/matroska/ebml/ebmlmkseekhead.cpp @@ -31,21 +31,21 @@ std::unique_ptr EBML::MkSeekHead::parse() seekHead->setOffset(offset); seekHead->setSize(getSize() + padding); - for(auto element : elements) { - if(element->getId() != ElementIDs::MkSeek) + for(const auto &element : elements) { + if(element->getId() != Id::MkSeek) continue; - auto seekElement = static_cast(element); + auto seekElement = element_cast(element); Matroska::Element::ID entryId = 0; offset_t offset = 0; - for(auto seekElementChild : *seekElement) { + for(const auto &seekElementChild : *seekElement) { Id id = seekElementChild->getId(); - if(id == ElementIDs::MkSeekID && !entryId) { - auto data = static_cast(seekElementChild)->getValue(); + if(id == Id::MkSeekID && !entryId) { + auto data = element_cast(seekElementChild)->getValue(); if(data.size() == 4) entryId = data.toUInt(true); } - else if(id == ElementIDs::MkSeekPosition && !offset) - offset = static_cast(seekElementChild)->getValue(); + else if(id == Id::MkSeekPosition && !offset) + offset = element_cast(seekElementChild)->getValue(); } if(entryId && offset) seekHead->addEntry(entryId, offset); diff --git a/taglib/matroska/ebml/ebmlmkseekhead.h b/taglib/matroska/ebml/ebmlmkseekhead.h index bf6b8edf..de405c6a 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.h +++ b/taglib/matroska/ebml/ebmlmkseekhead.h @@ -34,15 +34,19 @@ namespace TagLib { { public: MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(ElementIDs::MkSeekHead, sizeLength, dataSize, offset) + MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) + { + } + MkSeekHead(Id, int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) { } MkSeekHead() : - MasterElement(ElementIDs::MkSeekHead, 0, 0, 0) + MasterElement(Id::MkSeekHead, 0, 0, 0) { } - Matroska::SeekHead *parse(); + std::unique_ptr parse(); }; } } diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index 0de3d5e8..887ca5d9 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -33,81 +33,73 @@ using namespace TagLib; -EBML::MkSegment::~MkSegment() -{ - delete tags; - delete attachments; - delete seekHead; - delete info; - delete tracks; -} +EBML::MkSegment::~MkSegment() = default; bool EBML::MkSegment::read(File &file) { offset_t maxOffset = file.tell() + dataSize; - Element *element = nullptr; + std::unique_ptr element; int i = 0; int seekHeadIndex = -1; while((element = findNextElement(file, maxOffset))) { Id id = element->getId(); - if(id == ElementIDs::MkSeekHead) { + if(id == Id::MkSeekHead) { seekHeadIndex = i; - seekHead = static_cast(element); + seekHead = element_cast(std::move(element)); if(!seekHead->read(file)) return false; } - else if(id == ElementIDs::MkInfo) { - info = static_cast(element); + else if(id == Id::MkInfo) { + info = element_cast(std::move(element)); if(!info->read(file)) return false; } - else if(id == ElementIDs::MkTracks) { - tracks = static_cast(element); + else if(id == Id::MkTracks) { + tracks = element_cast(std::move(element)); if(!tracks->read(file)) return false; } - else if(id == ElementIDs::MkTags) { - tags = static_cast(element); + else if(id == Id::MkTags) { + tags = element_cast(std::move(element)); if(!tags->read(file)) return false; } - else if(id == ElementIDs::MkAttachments) { - attachments = static_cast(element); + else if(id == Id::MkAttachments) { + attachments = element_cast(std::move(element)); if(!attachments->read(file)) return false; } else { - if(id == ElementIDs::VoidElement + if(id == Id::VoidElement && seekHead && seekHeadIndex == i - 1) seekHead->setPadding(element->getSize()); element->skipData(file); - delete element; } i++; } return true; } -Matroska::Tag *EBML::MkSegment::parseTag() +std::unique_ptr EBML::MkSegment::parseTag() { return tags ? tags->parse() : nullptr; } -Matroska::Attachments *EBML::MkSegment::parseAttachments() +std::unique_ptr EBML::MkSegment::parseAttachments() { return attachments ? attachments->parse() : nullptr; } -Matroska::SeekHead *EBML::MkSegment::parseSeekHead() +std::unique_ptr EBML::MkSegment::parseSeekHead() { return seekHead ? seekHead->parse() : nullptr; } -Matroska::Segment *EBML::MkSegment::parseSegment() +std::unique_ptr EBML::MkSegment::parseSegment() { - return new Matroska::Segment(sizeLength, dataSize, offset + idSize(id)); + return std::make_unique(sizeLength, dataSize, offset + idSize(id)); } void EBML::MkSegment::parseInfo(Matroska::Properties *properties) diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index 15ab7db3..e913f36c 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -23,6 +23,9 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlmasterelement.h" +#include "ebmlmktags.h" +#include "ebmlmkattachments.h" +#include "ebmlmkseekhead.h" #include "ebmlmkinfo.h" #include "ebmlmktracks.h" #include "taglib.h" @@ -35,31 +38,32 @@ namespace TagLib { class Segment; } namespace EBML { - class MkTags; - class MkAttachments; - class MkSeekHead; class MkSegment : public MasterElement { public: MkSegment(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(ElementIDs::MkSegment, sizeLength, dataSize, offset) + MasterElement(Id::MkSegment, sizeLength, dataSize, offset) + { + } + MkSegment(Id, int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkSegment, sizeLength, dataSize, offset) { } ~MkSegment() override; bool read(File &file) override; - Matroska::Tag *parseTag(); - Matroska::Attachments *parseAttachments(); - Matroska::SeekHead *parseSeekHead(); - Matroska::Segment *parseSegment(); + std::unique_ptr parseTag(); + std::unique_ptr parseAttachments(); + std::unique_ptr parseSeekHead(); + std::unique_ptr parseSegment(); void parseInfo(Matroska::Properties *properties); void parseTracks(Matroska::Properties *properties); private: - MkTags *tags = nullptr; - MkAttachments *attachments = nullptr; - MkSeekHead *seekHead = nullptr; - MkInfo *info = nullptr; - MkTracks *tracks = nullptr; + std::unique_ptr tags; + std::unique_ptr attachments; + std::unique_ptr seekHead; + std::unique_ptr info; + std::unique_ptr tracks; }; } } diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index d594797e..a7dd5709 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -28,39 +28,39 @@ using namespace TagLib; -Matroska::Tag *EBML::MkTags::parse() +std::unique_ptr EBML::MkTags::parse() { - auto mTag = new Matroska::Tag(); + auto mTag = std::make_unique(); mTag->setOffset(offset); mTag->setSize(getSize()); - mTag->setID(id); + mTag->setID(static_cast(id)); // Loop through each element - for(auto tagsChild : elements) { - if(tagsChild->getId() != ElementIDs::MkTag) + for(const auto &tagsChild : elements) { + if(tagsChild->getId() != Id::MkTag) continue; - auto tag = static_cast(tagsChild); - List simpleTags; - MasterElement *targets = nullptr; + auto tag = element_cast(tagsChild); + List simpleTags; + const MasterElement *targets = nullptr; // Identify the element and the elements - for(auto tagChild : *tag) { + for(const auto &tagChild : *tag) { Id tagChildId = tagChild->getId(); - if(!targets && tagChildId == ElementIDs::MkTagTargets) - targets = static_cast(tagChild); - else if(tagChildId == ElementIDs::MkSimpleTag) - simpleTags.append(static_cast(tagChild)); + if(!targets && tagChildId == Id::MkTagTargets) + targets = element_cast(tagChild); + else if(tagChildId == Id::MkSimpleTag) + simpleTags.append(element_cast(tagChild)); } // Parse the element Matroska::SimpleTag::TargetTypeValue targetTypeValue = Matroska::SimpleTag::TargetTypeValue::None; if(targets) { - for(auto targetsChild : *targets) { + for(const auto &targetsChild : *targets) { Id id = targetsChild->getId(); - if(id == ElementIDs::MkTagTargetTypeValue + if(id == Id::MkTagTargetTypeValue && targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) { targetTypeValue = static_cast( - static_cast(targetsChild)->getValue() + element_cast(targetsChild)->getValue() ); } } @@ -74,18 +74,18 @@ Matroska::Tag *EBML::MkTags::parse() const String *language = nullptr; bool defaultLanguageFlag = true; - for(auto simpleTagChild : *simpleTag) { + for(const auto &simpleTagChild : *simpleTag) { Id id = simpleTagChild->getId(); - if(id == ElementIDs::MkTagName && !tagName) - tagName = &(static_cast(simpleTagChild)->getValue()); - else if(id == ElementIDs::MkTagString && !tagValueString) - tagValueString = &(static_cast(simpleTagChild)->getValue()); - else if(id == ElementIDs::MkTagBinary && !tagValueBinary) - tagValueBinary = &(static_cast(simpleTagChild)->getValue()); - else if(id == ElementIDs::MkTagsTagLanguage && !language) - language = &(static_cast(simpleTagChild)->getValue()); - else if(id == ElementIDs::MkTagsLanguageDefault) - defaultLanguageFlag = static_cast(simpleTagChild)->getValue() ? true : false; + if(id == Id::MkTagName && !tagName) + tagName = &(element_cast(simpleTagChild)->getValue()); + else if(id == Id::MkTagString && !tagValueString) + tagValueString = &(element_cast(simpleTagChild)->getValue()); + else if(id == Id::MkTagBinary && !tagValueBinary) + tagValueBinary = &(element_cast(simpleTagChild)->getValue()); + else if(id == Id::MkTagsTagLanguage && !language) + language = &(element_cast(simpleTagChild)->getValue()); + else if(id == Id::MkTagsLanguageDefault) + defaultLanguageFlag = element_cast(simpleTagChild)->getValue() ? true : false; } if(!tagName || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) continue; diff --git a/taglib/matroska/ebml/ebmlmktags.h b/taglib/matroska/ebml/ebmlmktags.h index 1f00e1e2..dea4e2eb 100644 --- a/taglib/matroska/ebml/ebmlmktags.h +++ b/taglib/matroska/ebml/ebmlmktags.h @@ -36,15 +36,19 @@ namespace TagLib { { public: MkTags(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(ElementIDs::MkTags, sizeLength, dataSize, offset) + MasterElement(Id::MkTags, sizeLength, dataSize, offset) + { + } + MkTags(Id, int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkTags, sizeLength, dataSize, offset) { } MkTags() : - MasterElement(ElementIDs::MkTags, 0, 0, 0) + MasterElement(Id::MkTags, 0, 0, 0) { } - Matroska::Tag *parse(); + std::unique_ptr parse(); }; } } diff --git a/taglib/matroska/ebml/ebmlmktracks.cpp b/taglib/matroska/ebml/ebmlmktracks.cpp index 552b07c3..febdaad2 100644 --- a/taglib/matroska/ebml/ebmlmktracks.cpp +++ b/taglib/matroska/ebml/ebmlmktracks.cpp @@ -36,29 +36,29 @@ void EBML::MkTracks::parse(Matroska::Properties *properties) if(!properties) return; - for(auto element : elements) { - if(element->getId() != ElementIDs::MkTrackEntry) + for(const auto &element : elements) { + if(element->getId() != Id::MkTrackEntry) continue; String codecId; double samplingFrequency = 0.0; unsigned long long bitDepth = 0; unsigned long long channels = 0; - auto trackEntry = static_cast(element); - for(auto trackEntryChild : *trackEntry) { + auto trackEntry = element_cast(element); + for(const auto &trackEntryChild : *trackEntry) { Id trackEntryChildId = trackEntryChild->getId(); - if(trackEntryChildId == ElementIDs::MkCodecID) - codecId = static_cast(trackEntryChild)->getValue(); - else if(trackEntryChildId == ElementIDs::MkAudio) { - auto audio = static_cast(trackEntryChild); - for(auto audioChild : *audio) { + if(trackEntryChildId == Id::MkCodecID) + codecId = element_cast(trackEntryChild)->getValue(); + else if(trackEntryChildId == Id::MkAudio) { + auto audio = element_cast(trackEntryChild); + for(const auto &audioChild : *audio) { Id audioChildId = audioChild->getId(); - if(audioChildId == ElementIDs::MkSamplingFrequency) - samplingFrequency = static_cast(audioChild)->getValueAsDouble(); - else if(audioChildId == ElementIDs::MkBitDepth) - bitDepth = static_cast(audioChild)->getValue(); - else if(audioChildId == ElementIDs::MkChannels) - channels = static_cast(audioChild)->getValue(); + if(audioChildId == Id::MkSamplingFrequency) + samplingFrequency = element_cast(audioChild)->getValueAsDouble(); + else if(audioChildId == Id::MkBitDepth) + bitDepth = element_cast(audioChild)->getValue(); + else if(audioChildId == Id::MkChannels) + channels = element_cast(audioChild)->getValue(); } } } diff --git a/taglib/matroska/ebml/ebmlmktracks.h b/taglib/matroska/ebml/ebmlmktracks.h index 53754fb7..1f8ebb2f 100644 --- a/taglib/matroska/ebml/ebmlmktracks.h +++ b/taglib/matroska/ebml/ebmlmktracks.h @@ -40,11 +40,15 @@ namespace TagLib { { public: MkTracks(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(ElementIDs::MkTracks, sizeLength, dataSize, offset) + MasterElement(Id::MkTracks, sizeLength, dataSize, offset) + { + } + MkTracks(Id, int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkTracks, sizeLength, dataSize, offset) { } MkTracks() : - MasterElement(ElementIDs::MkTracks, 0, 0, 0) + MasterElement(Id::MkTracks, 0, 0, 0) { } void parse(Matroska::Properties *properties); diff --git a/taglib/matroska/ebml/ebmlstringelement.h b/taglib/matroska/ebml/ebmlstringelement.h index afeb1ef3..258eb553 100644 --- a/taglib/matroska/ebml/ebmlstringelement.h +++ b/taglib/matroska/ebml/ebmlstringelement.h @@ -37,6 +37,10 @@ namespace TagLib { Element(id, sizeLength, dataSize) { } + StringElement(Id id, int sizeLength, offset_t dataSize, offset_t) : + Element(id, sizeLength, dataSize) + { + } explicit StringElement(Id id) : Element(id, 0, 0) diff --git a/taglib/matroska/ebml/ebmluintelement.h b/taglib/matroska/ebml/ebmluintelement.h index 581a19ce..efd6f32f 100644 --- a/taglib/matroska/ebml/ebmluintelement.h +++ b/taglib/matroska/ebml/ebmluintelement.h @@ -35,6 +35,10 @@ namespace TagLib { Element(id, sizeLength, dataSize) { } + UIntElement(Id id, int sizeLength, offset_t dataSize, offset_t) : + Element(id, sizeLength, dataSize) + { + } explicit UIntElement(Id id) : UIntElement(id, 0, 0) diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index 2793fb23..149109a1 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -30,21 +30,20 @@ using namespace TagLib; -EBML::Element *EBML::findElement(File &file, Element::Id id, offset_t maxOffset) +std::unique_ptr EBML::findElement(File &file, Element::Id id, offset_t maxOffset) { - Element *element = nullptr; + std::unique_ptr element; while(file.tell() < maxOffset) { element = Element::factory(file); if(!element || element->getId() == id) return element; element->skipData(file); - delete element; - element = nullptr; + element.reset(); } return element; } -EBML::Element *EBML::findNextElement(File &file, offset_t maxOffset) +std::unique_ptr EBML::findNextElement(File &file, offset_t maxOffset) { return file.tell() < maxOffset ? Element::factory(file) : nullptr; } diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h index 9644cfba..c418d632 100644 --- a/taglib/matroska/ebml/ebmlutils.h +++ b/taglib/matroska/ebml/ebmlutils.h @@ -59,8 +59,8 @@ namespace TagLib { std::pair readVINT(File &file); template std::pair parseVINT(const ByteVector &buffer); - Element *findElement(File &file, Element::Id id, offset_t maxOffset); - Element *findNextElement(File &file, offset_t maxOffset); + std::unique_ptr findElement(File &file, Element::Id id, offset_t maxOffset); + std::unique_ptr findNextElement(File &file, offset_t maxOffset); ByteVector renderVINT(uint64_t number, int minSizeLength); unsigned long long randomUID(); @@ -82,11 +82,12 @@ namespace TagLib { constexpr int idSize(Element::Id id) { - if(id <= 0xFF) + auto uintId = static_cast(id); + if(uintId <= 0xFF) return 1; - else if(id <= 0xFFFF) + else if(uintId <= 0xFFFF) return 2; - else if(id <= 0xFFFFFF) + else if(uintId <= 0xFFFFFF) return 3; else return 4; diff --git a/taglib/matroska/ebml/ebmlvoidelement.h b/taglib/matroska/ebml/ebmlvoidelement.h index 8e928e04..9b6864ac 100644 --- a/taglib/matroska/ebml/ebmlvoidelement.h +++ b/taglib/matroska/ebml/ebmlvoidelement.h @@ -33,10 +33,13 @@ namespace TagLib { { public: VoidElement(int sizeLength, offset_t dataSize) : - Element(ElementIDs::VoidElement, sizeLength, dataSize) + Element(Id::VoidElement, sizeLength, dataSize) + {} + VoidElement(Id, int sizeLength, offset_t dataSize, offset_t) : + Element(Id::VoidElement, sizeLength, dataSize) {} VoidElement() : - Element(ElementIDs::VoidElement, 0, 0) + Element(Id::VoidElement, 0, 0) {} ByteVector render() override; offset_t getTargetSize() const; diff --git a/taglib/matroska/matroskaattachments.cpp b/taglib/matroska/matroskaattachments.cpp index 1dab3c1d..8fb34700 100644 --- a/taglib/matroska/matroskaattachments.cpp +++ b/taglib/matroska/matroskaattachments.cpp @@ -1,5 +1,5 @@ -#include #include "matroskaattachments.h" +#include #include "matroskaattachedfile.h" #include "ebmlmkattachments.h" #include "ebmlmasterelement.h" @@ -22,7 +22,7 @@ public: }; Matroska::Attachments::Attachments() : - Element(ElementIDs::MkAttachments), + Element(static_cast(EBML::Element::Id::MkAttachments)), d(std::make_unique()) { d->files.setAutoDelete(true); @@ -57,40 +57,40 @@ bool Matroska::Attachments::render() { EBML::MkAttachments attachments; for(const auto attachedFile : d->files) { - auto attachedFileElement = new EBML::MasterElement(EBML::ElementIDs::MkAttachedFile); + auto attachedFileElement = EBML::make_unique_element(); // Filename - auto fileNameElement = new EBML::UTF8StringElement(EBML::ElementIDs::MkAttachedFileName); + auto fileNameElement = EBML::make_unique_element(); fileNameElement->setValue(attachedFile->fileName()); - attachedFileElement->appendElement(fileNameElement); + attachedFileElement->appendElement(std::move(fileNameElement)); // Media/MIME type - auto mediaTypeElement = new EBML::Latin1StringElement(EBML::ElementIDs::MkAttachedFileMediaType); + auto mediaTypeElement = EBML::make_unique_element(); mediaTypeElement->setValue(attachedFile->mediaType()); - attachedFileElement->appendElement(mediaTypeElement); + attachedFileElement->appendElement(std::move(mediaTypeElement)); // Description const String &description = attachedFile->description(); if(!description.isEmpty()) { - auto descriptionElement = new EBML::UTF8StringElement(EBML::ElementIDs::MkAttachedFileDescription); + auto descriptionElement = EBML::make_unique_element(); descriptionElement->setValue(description); - attachedFileElement->appendElement(descriptionElement); + attachedFileElement->appendElement(std::move(descriptionElement)); } // Data - auto dataElement = new EBML::BinaryElement(EBML::ElementIDs::MkAttachedFileData); + auto dataElement = EBML::make_unique_element(); dataElement->setValue(attachedFile->data()); - attachedFileElement->appendElement(dataElement); + attachedFileElement->appendElement(std::move(dataElement)); // UID - auto uidElement = new EBML::UIntElement(EBML::ElementIDs::MkAttachedFileUID); + auto uidElement = EBML::make_unique_element(); AttachedFile::UID uid = attachedFile->uid(); if(!uid) uid = EBML::randomUID(); uidElement->setValue(uid); - attachedFileElement->appendElement(uidElement); + attachedFileElement->appendElement(std::move(uidElement)); - attachments.appendElement(attachedFileElement); + attachments.appendElement(std::move(attachedFileElement)); } auto beforeSize = size(); diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp index 06d6e288..a064ae51 100644 --- a/taglib/matroska/matroskacues.cpp +++ b/taglib/matroska/matroskacues.cpp @@ -30,48 +30,47 @@ using namespace TagLib; Matroska::Cues::Cues() : - Element(ElementIDs::MkCues) + Element(static_cast(EBML::Element::Id::MkCues)) { - cuePoints.setAutoDelete(true); } ByteVector Matroska::Cues::renderInternal() { EBML::MkCues cues; - for(auto &cuePoint : cuePoints) { - auto cuePointElement = new EBML::MasterElement(EBML::ElementIDs::MkCuePoint); - auto timestamp = new EBML::UIntElement(EBML::ElementIDs::MkCueTime); + for(const auto &cuePoint : cuePoints) { + auto cuePointElement = EBML::make_unique_element(); + auto timestamp = EBML::make_unique_element(); timestamp->setValue(cuePoint->getTime()); - cuePointElement->appendElement(timestamp); + cuePointElement->appendElement(std::move(timestamp)); - auto trackList = cuePoint->cueTrackList(); - for(auto &cueTrack : trackList) { - auto cueTrackElement = new EBML::MasterElement(EBML::ElementIDs::MkCueTrackPositions); + const auto &trackList = cuePoint->cueTrackList(); + for(const auto &cueTrack : trackList) { + auto cueTrackElement = EBML::make_unique_element(); // Track number - auto trackNumber = new EBML::UIntElement(EBML::ElementIDs::MkCueTrack); + auto trackNumber = EBML::make_unique_element(); trackNumber->setValue(cueTrack->getTrackNumber()); - cueTrackElement->appendElement(trackNumber); + cueTrackElement->appendElement(std::move(trackNumber)); // Cluster position - auto clusterPosition = new EBML::UIntElement(EBML::ElementIDs::MkCueClusterPosition); + auto clusterPosition = EBML::make_unique_element(); clusterPosition->setValue(cueTrack->getClusterPosition()); - cueTrackElement->appendElement(clusterPosition); + cueTrackElement->appendElement(std::move(clusterPosition)); // Todo - other elements // Reference times auto referenceTimes = cueTrack->referenceTimes(); if(!referenceTimes.isEmpty()) { - auto cueReference = new EBML::MasterElement(EBML::ElementIDs::MkCueReference); + auto cueReference = EBML::make_unique_element(); for(auto reference : referenceTimes) { - auto refTime = new EBML::UIntElement(EBML::ElementIDs::MkCueRefTime); + auto refTime = EBML::make_unique_element(); refTime->setValue(reference); - cueReference->appendElement(refTime); + cueReference->appendElement(std::move(refTime)); } - cueTrackElement->appendElement(cueReference); + cueTrackElement->appendElement(std::move(cueReference)); } - cuePointElement->appendElement(cueTrackElement); + cuePointElement->appendElement(std::move(cueTrackElement)); } } return cues.render(); @@ -90,38 +89,47 @@ bool Matroska::Cues::render() bool Matroska::Cues::sizeChanged(Element &caller, offset_t delta) { offset_t offset = caller.offset(); - for(auto cuePoint : cuePoints) + for(auto &cuePoint : cuePoints) needsRender |= cuePoint->adjustOffset(offset, delta); return true; } bool Matroska::Cues::isValid(TagLib::File &file, offset_t segmentDataOffset) const { - for(const auto cuePoint : cuePoints) { + for(const auto &cuePoint : cuePoints) { if(!cuePoint->isValid(file, segmentDataOffset)) return false; } return true; } +void Matroska::Cues::addCuePoint(std::unique_ptr &&cuePoint) +{ + cuePoints.push_back(std::move(cuePoint)); +} + Matroska::CuePoint::CuePoint() { - cueTracks.setAutoDelete(true); } bool Matroska::CuePoint::isValid(TagLib::File &file, offset_t segmentDataOffset) const { - for(const auto track : cueTracks) { + for(const auto &track : cueTracks) { if(!track->isValid(file, segmentDataOffset)) return false; } return true; } +void Matroska::CuePoint::addCueTrack(std::unique_ptr &&cueTrack) +{ + cueTracks.push_back(std::move(cueTrack)); +} + bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta) { bool ret = false; - for(auto cueTrack : cueTracks) + for(auto &cueTrack : cueTracks) ret |= cueTrack->adjustOffset(offset, delta); return ret; @@ -138,13 +146,13 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) return false; } file.seek(segmentDataOffset + clusterPosition); - if(EBML::Element::readId(file) != EBML::ElementIDs::MkCluster) { + if(EBML::Element::readId(file) != static_cast(EBML::Element::Id::MkCluster)) { debug("No cluster found at position"); return false; } if(codecState) { file.seek(segmentDataOffset + codecState); - if(EBML::Element::readId(file) != EBML::ElementIDs::MkCodecState) { + if(EBML::Element::readId(file) != static_cast(EBML::Element::Id::MkCodecState)) { debug("No codec state found at position"); return false; } diff --git a/taglib/matroska/matroskacues.h b/taglib/matroska/matroskacues.h index 109c90ce..61539413 100644 --- a/taglib/matroska/matroskacues.h +++ b/taglib/matroska/matroskacues.h @@ -37,12 +37,12 @@ namespace TagLib { class Cues : public Element { public: - using CuePointList = List; + using CuePointList = std::list>; Cues(); ~Cues() override = default; bool isValid(File &file, offset_t segmentDataOffset) const; - void addCuePoint(CuePoint *cuePoint) { cuePoints.append(cuePoint); } - CuePointList cuePointList() { return cuePoints; } + void addCuePoint(std::unique_ptr &&cuePoint); + const CuePointList &cuePointList() { return cuePoints; } bool sizeChanged(Element &caller, offset_t delta) override; bool render() override; @@ -51,19 +51,19 @@ namespace TagLib { ByteVector renderInternal(); bool needsRender = false; - List cuePoints; + CuePointList cuePoints; }; class CuePoint { public: - using CueTrackList = List; + using CueTrackList = std::list>; using Time = unsigned long long; CuePoint(); ~CuePoint() = default; bool isValid(File &file, offset_t segmentDataOffset) const; - void addCueTrack(CueTrack *cueTrack) { cueTracks.append(cueTrack); } - CueTrackList cueTrackList() const { return cueTracks; } + void addCueTrack(std::unique_ptr &&cueTrack); + const CueTrackList &cueTrackList() const { return cueTracks; } void setTime(Time time) { this->time = time; } Time getTime() const { return time; } bool adjustOffset(offset_t offset, offset_t delta); @@ -93,7 +93,7 @@ namespace TagLib { void setDuration(unsigned long long duration) { this->duration = duration; } unsigned long long getDuration() const { return duration; } void addReferenceTime(unsigned long long refTime) { refTimes.append(refTime); } - ReferenceTimeList referenceTimes() const { return refTimes; } + const ReferenceTimeList &referenceTimes() const { return refTimes; } bool adjustOffset(offset_t offset, offset_t delta); private: diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index ed11584d..c5c3aec0 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -65,13 +65,6 @@ namespace TagLib { TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr e; }; - namespace ElementIDs { - inline constexpr Element::ID MkTags = 0x1254C367; - inline constexpr Element::ID MkAttachments = 0x1941A469; - inline constexpr Element::ID MkSeekHead = 0x114D9B74; - inline constexpr Element::ID MkSegment = 0x18538067; - inline constexpr Element::ID MkCues = 0x1C53BB6B; - } } } diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index 7e9e19f5..9651a1a2 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -39,20 +39,15 @@ class Matroska::File::FilePrivate { public: FilePrivate() = default; - ~FilePrivate() - { - delete tag; - delete attachments; - delete seekHead; - delete segment; - } + ~FilePrivate() = default; FilePrivate(const FilePrivate &) = delete; FilePrivate &operator=(const FilePrivate &) = delete; - Tag *tag = nullptr; - Attachments *attachments = nullptr; - SeekHead *seekHead = nullptr; - Segment *segment = nullptr; + + std::unique_ptr tag; + std::unique_ptr attachments; + std::unique_ptr seekHead; + std::unique_ptr segment; std::unique_ptr properties; }; @@ -110,24 +105,16 @@ Tag *Matroska::File::tag() const Matroska::Tag *Matroska::File::tag(bool create) const { - if(d->tag) - return d->tag; - else { - if(create) - d->tag = new Tag(); - return d->tag; - } + if(!d->tag && create) + d->tag = std::make_unique(); + return d->tag.get(); } Matroska::Attachments *Matroska::File::attachments(bool create) const { - if(d->attachments) - return d->attachments; - else { - if(create) - d->attachments = new Attachments(); - return d->attachments; - } + if(!d->attachments && create) + d->attachments = std::make_unique(); + return d->attachments.get(); } void Matroska::File::read(bool readProperties, Properties::ReadStyle) @@ -136,7 +123,7 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle) // Find the EBML Header std::unique_ptr head(EBML::Element::factory(*this)); - if(!head || head->getId() != EBML::ElementIDs::EBMLHeader) { + if(!head || head->getId() != EBML::Element::Id::EBMLHeader) { debug("Failed to find EBML head"); setValid(false); return; @@ -145,8 +132,8 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle) // Find the Matroska segment in the file std::unique_ptr segment( - static_cast( - EBML::findElement(*this, EBML::ElementIDs::MkSegment, fileLength - tell()) + EBML::element_cast( + EBML::findElement(*this, EBML::Element::Id::MkSegment, fileLength - tell()) ) ); if(!segment) { @@ -193,8 +180,8 @@ bool Matroska::File::save() // List of all possible elements we can write List elements { - d->attachments, - d->tag + d->attachments.get(), + d->tag.get() }; /* Build render list. New elements will be added @@ -235,15 +222,15 @@ bool Matroska::File::save() for(auto it2 = std::next(it); it2 != renderList.end(); ++it2) (*it)->addSizeListener(*it2); if(d->seekHead) - (*it)->addSizeListener(d->seekHead); - (*it)->addSizeListener(d->segment); + (*it)->addSizeListener(d->seekHead.get()); + (*it)->addSizeListener(d->segment.get()); } if(d->seekHead) { d->seekHead->addSizeListeners(renderList); - renderList.append(d->seekHead); + renderList.append(d->seekHead.get()); } d->segment->addSizeListeners(renderList); - renderList.append(d->segment); + renderList.append(d->segment.get()); // Render the elements for(auto element : renderList) { diff --git a/taglib/matroska/matroskaseekhead.cpp b/taglib/matroska/matroskaseekhead.cpp index b135b002..09a3070d 100644 --- a/taglib/matroska/matroskaseekhead.cpp +++ b/taglib/matroska/matroskaseekhead.cpp @@ -27,6 +27,11 @@ using namespace TagLib; +Matroska::SeekHead::SeekHead() : + Element(static_cast(EBML::Element::Id::MkSeekHead)) +{ +} + void Matroska::SeekHead::addEntry(const Element &element) { entries.append({element.id(), element.offset()}); @@ -46,16 +51,16 @@ ByteVector Matroska::SeekHead::renderInternal() EBML::MkSeekHead seekHead; seekHead.setMinRenderSize(beforeSize); for(const auto &[id, position] : entries) { - auto seekElement = new EBML::MasterElement(EBML::ElementIDs::MkSeek); - auto idElement = new EBML::BinaryElement(EBML::ElementIDs::MkSeekID); + auto seekElement = EBML::make_unique_element(); + auto idElement = EBML::make_unique_element(); idElement->setValue(ByteVector::fromUInt(id, true)); - seekElement->appendElement(idElement); + seekElement->appendElement(std::move(idElement)); - auto positionElement = new EBML::UIntElement(EBML::ElementIDs::MkSeekPosition); + auto positionElement = EBML::make_unique_element(); positionElement->setValue(static_cast(position)); - seekElement->appendElement(positionElement); + seekElement->appendElement(std::move(positionElement)); - seekHead.appendElement(seekElement); + seekHead.appendElement(std::move(seekElement)); } return seekHead.render(); } @@ -94,7 +99,7 @@ void Matroska::SeekHead::sort() bool Matroska::SeekHead::sizeChanged(Element &caller, offset_t delta) { ID callerID = caller.id(); - if(callerID == ElementIDs::MkSegment) { + if(callerID == static_cast(EBML::Element::Id::MkSegment)) { adjustOffset(delta); return true; } diff --git a/taglib/matroska/matroskaseekhead.h b/taglib/matroska/matroskaseekhead.h index e684a3d9..4f4b09da 100644 --- a/taglib/matroska/matroskaseekhead.h +++ b/taglib/matroska/matroskaseekhead.h @@ -32,7 +32,7 @@ namespace TagLib { class SeekHead : public Element { public: - SeekHead() : Element(ElementIDs::MkSeekHead) {} + SeekHead(); ~SeekHead() override = default; void addEntry(const Element &element); void addEntry(ID id, offset_t offset); diff --git a/taglib/matroska/matroskasegment.cpp b/taglib/matroska/matroskasegment.cpp index 3de08b31..ddcffa90 100644 --- a/taglib/matroska/matroskasegment.cpp +++ b/taglib/matroska/matroskasegment.cpp @@ -23,6 +23,14 @@ using namespace TagLib; +Matroska::Segment::Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset) : + Element(static_cast(EBML::Element::Id::MkSegment)), + sizeLength(sizeLength), dataSize(dataSize) +{ + setOffset(lengthOffset); + setSize(sizeLength); +} + bool Matroska::Segment::render() { auto data = EBML::renderVINT(dataSize, static_cast(sizeLength)); diff --git a/taglib/matroska/matroskasegment.h b/taglib/matroska/matroskasegment.h index dcd1163a..0c9b2e0a 100644 --- a/taglib/matroska/matroskasegment.h +++ b/taglib/matroska/matroskasegment.h @@ -28,13 +28,7 @@ namespace TagLib::Matroska { class Segment : public Element { public: - Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset) : - Element(ElementIDs::MkSegment), sizeLength(sizeLength), dataSize(dataSize) - { - setOffset(lengthOffset); - setSize(sizeLength); - } - + Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset); ~Segment() override = default; bool render() override; bool sizeChanged(Element &caller, offset_t delta) override; diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index fe65d1e4..d7982b8a 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -95,7 +95,7 @@ public: }; Matroska::Tag::Tag() : - Element(ElementIDs::MkTags), + Element(static_cast(EBML::Element::Id::MkTags)), d(std::make_unique()) { d->tags.setAutoDelete(true); @@ -241,52 +241,52 @@ bool Matroska::Tag::render() for(auto list : targetList) { auto frontTag = list->front(); auto targetTypeValue = frontTag->targetTypeValue(); - auto tag = new EBML::MasterElement(EBML::ElementIDs::MkTag); + auto tag = EBML::make_unique_element(); // Build element - auto targets = new EBML::MasterElement(EBML::ElementIDs::MkTagTargets); + auto targets = EBML::make_unique_element(); if(targetTypeValue != SimpleTag::TargetTypeValue::None) { - auto element = new EBML::UIntElement(EBML::ElementIDs::MkTagTargetTypeValue); + auto element = EBML::make_unique_element(); element->setValue(static_cast(targetTypeValue)); - targets->appendElement(element); + targets->appendElement(std::move(element)); } - tag->appendElement(targets); + tag->appendElement(std::move(targets)); // Build element for(auto simpleTag : *list) { - auto t = new EBML::MasterElement(EBML::ElementIDs::MkSimpleTag); - auto tagName = new EBML::UTF8StringElement(EBML::ElementIDs::MkTagName); + auto t = EBML::make_unique_element(); + auto tagName = EBML::make_unique_element(); tagName->setValue(simpleTag->name()); - t->appendElement(tagName); + t->appendElement(std::move(tagName)); // Tag Value SimpleTagString *tStr = nullptr; SimpleTagBinary *tBin = nullptr; if((tStr = dynamic_cast(simpleTag))) { - auto tagValue = new EBML::UTF8StringElement(EBML::ElementIDs::MkTagString); + auto tagValue = EBML::make_unique_element(); tagValue->setValue(tStr->value()); - t->appendElement(tagValue); + t->appendElement(std::move(tagValue)); } else if((tBin = dynamic_cast(simpleTag))) { - auto tagValue = new EBML::BinaryElement(EBML::ElementIDs::MkTagBinary); + auto tagValue = EBML::make_unique_element(); tagValue->setValue(tBin->value()); - t->appendElement(tagValue); + t->appendElement(std::move(tagValue)); } // Language - auto language = new EBML::Latin1StringElement(EBML::ElementIDs::MkTagsTagLanguage); + auto language = EBML::make_unique_element(); const String &lang = simpleTag->language(); language->setValue(!lang.isEmpty() ? lang : "und"); - t->appendElement(language); + t->appendElement(std::move(language)); // Default language flag - auto dlf = new EBML::UIntElement(EBML::ElementIDs::MkTagsLanguageDefault); + auto dlf = EBML::make_unique_element(); dlf->setValue(simpleTag->defaultLanguageFlag() ? 1 : 0); - t->appendElement(dlf); + t->appendElement(std::move(dlf)); - tag->appendElement(t); + tag->appendElement(std::move(t)); } - tags.appendElement(tag); + tags.appendElement(std::move(tag)); } auto data = tags.render(); From 98bc68d16efe2538f8ea27b09550a45e9030efb2 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Tue, 19 Aug 2025 16:47:04 +0200 Subject: [PATCH 14/31] Implement complex properties for Matroska --- taglib/matroska/matroskaattachedfile.h | 4 +- taglib/matroska/matroskaattachments.cpp | 5 + taglib/matroska/matroskaattachments.h | 5 +- taglib/matroska/matroskacues.h | 4 +- taglib/matroska/matroskaelement.h | 4 +- taglib/matroska/matroskafile.cpp | 129 +++++++++++++++++++++++- taglib/matroska/matroskafile.h | 107 ++++++++++++++++++-- taglib/matroska/matroskasimpletag.h | 4 +- taglib/matroska/matroskatag.cpp | 67 ++++++++++++ taglib/matroska/matroskatag.h | 24 +++++ 10 files changed, 336 insertions(+), 17 deletions(-) diff --git a/taglib/matroska/matroskaattachedfile.h b/taglib/matroska/matroskaattachedfile.h index 4eeaaa78..c28e889e 100644 --- a/taglib/matroska/matroskaattachedfile.h +++ b/taglib/matroska/matroskaattachedfile.h @@ -18,8 +18,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAS_MATROSKAATTACHEDFILE_H -#define HAS_MATROSKAATTACHEDFILE_H +#ifndef TAGLIB_MATROSKAATTACHEDFILE_H +#define TAGLIB_MATROSKAATTACHEDFILE_H #include "taglib_export.h" diff --git a/taglib/matroska/matroskaattachments.cpp b/taglib/matroska/matroskaattachments.cpp index 8fb34700..b04ace77 100644 --- a/taglib/matroska/matroskaattachments.cpp +++ b/taglib/matroska/matroskaattachments.cpp @@ -53,6 +53,11 @@ const Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFi return d->files; } +Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFiles() +{ + return d->files; +} + bool Matroska::Attachments::render() { EBML::MkAttachments attachments; diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index a49e7b61..4489cabb 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -18,8 +18,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAS_MATROSKAATTACHMENTS_H -#define HAS_MATROSKAATTACHMENTS_H +#ifndef TAGLIB_MATROSKAATTACHMENTS_H +#define TAGLIB_MATROSKAATTACHMENTS_H #include #include "taglib_export.h" @@ -56,6 +56,7 @@ namespace TagLib { // private Element implementation bool render() override; + AttachedFileList &attachedFiles(); TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; diff --git a/taglib/matroska/matroskacues.h b/taglib/matroska/matroskacues.h index 61539413..192599f3 100644 --- a/taglib/matroska/matroskacues.h +++ b/taglib/matroska/matroskacues.h @@ -18,8 +18,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAS_MATROSKACUES_H -#define HAS_MATROSKACUES_H +#ifndef TAGLIB_MATROSKACUES_H +#define TAGLIB_MATROSKACUES_H #ifndef DO_NOT_DOCUMENT #include "tlist.h" diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index c5c3aec0..9c1f19c3 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -18,8 +18,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAS_MATROSKAELEMENT_H -#define HAS_MATROSKAELEMENT_H +#ifndef TAGLIB_MATROSKAELEMENT_H +#define TAGLIB_MATROSKAELEMENT_H #ifndef DO_NOT_DOCUMENT #include diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index 9651a1a2..ac9c7fca 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -21,6 +21,7 @@ #include "matroskafile.h" #include "matroskatag.h" #include "matroskaattachments.h" +#include "matroskaattachedfile.h" #include "matroskaseekhead.h" #include "matroskasegment.h" #include "ebmlutils.h" @@ -29,9 +30,9 @@ #include "tlist.h" #include "tdebug.h" #include "tagutils.h" +#include "tpropertymap.h" #include -#include using namespace TagLib; @@ -110,6 +111,132 @@ Matroska::Tag *Matroska::File::tag(bool create) const return d->tag.get(); } +PropertyMap Matroska::File::properties() const +{ + return d->tag ? d->tag->properties() : PropertyMap(); +} + +void Matroska::File::removeUnsupportedProperties(const StringList &properties) +{ + if(d->tag) { + d->tag->removeUnsupportedProperties(properties); + } +} + +PropertyMap Matroska::File::setProperties(const PropertyMap &properties) +{ + if(!d->tag) { + d->tag = std::make_unique(); + } + return d->tag->setProperties(properties); +} + +namespace { + + String keyForAttachedFile(const Matroska::AttachedFile *attachedFile) + { + if(!attachedFile) { + return {}; + } + 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()); + } + + unsigned long long stringToULongLong(const String &str, bool *ok) + { + 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; + } + +} + +StringList Matroska::File::complexPropertyKeys() const +{ + StringList keys = TagLib::File::complexPropertyKeys(); + if(d->attachments) { + const auto &attachedFiles = d->attachments->attachedFileList(); + for(const auto &attachedFile : attachedFiles) { + String key = keyForAttachedFile(attachedFile); + if(!key.isEmpty() && !keys.contains(key)) { + keys.append(key); + } + } + } + return keys; +} + +List Matroska::File::complexProperties(const String &key) const +{ + List props = TagLib::File::complexProperties(key); + if(d->attachments) { + const auto &attachedFiles = d->attachments->attachedFileList(); + for(const auto &attachedFile : attachedFiles) { + if(keyForAttachedFile(attachedFile) == key) { + VariantMap property; + property.insert("data", attachedFile->data()); + property.insert("mimeType", attachedFile->mediaType()); + property.insert("description", attachedFile->description()); + property.insert("fileName", attachedFile->fileName()); + property.insert("uid", attachedFile->uid()); + props.append(property); + } + } + } + return props; +} + +bool Matroska::File::setComplexProperties(const String &key, const List &value) +{ + if(TagLib::File::setComplexProperties(key, value)) { + return true; + } + + attachments(true)->clear(); + for(const auto &property : value) { + auto mimeType = property.value("mimeType").value(); + auto data = property.value("data").value(); + auto fileName = property.value("fileName").value(); + auto uid = property.value("uid").value(); + bool ok; + unsigned long long uidKey; + if(key.upper() == "PICTURE" && !mimeType.startsWith("image/")) { + mimeType = data.startsWith("\x89PNG\x0d\x0a\x1a\x0a") + ? "image/png" : "image/jpeg"; + } + else if(mimeType.isEmpty() && key.find("/") != -1) { + mimeType = key; + } + else if(fileName.isEmpty() && key.find(".") != -1) { + fileName = key; + } + else if(!uid && ((uidKey = stringToULongLong(key, &ok))) && ok) { + uid = uidKey; + } + auto attachedFile = new 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; +} + Matroska::Attachments *Matroska::File::attachments(bool create) const { if(!d->attachments && create) diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index cb9088b9..7ac23fa3 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -18,8 +18,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAS_MATROSKAFILE_H -#define HAS_MATROSKAFILE_H +#ifndef TAGLIB_MATROSKAFILE_H +#define TAGLIB_MATROSKAFILE_H #include "taglib_export.h" #include "tfile.h" @@ -30,23 +30,118 @@ namespace TagLib::Matroska { class Properties; class Tag; class Attachments; + + /*! + * Implementation of TagLib::File for Matroska. + */ class TAGLIB_EXPORT File : public TagLib::File { public: + /*! + * Constructs a Matroska file from \a file. If \a readProperties is \c true the + * file's audio properties will also be read. + * + * The \a readStyle parameter is currently unused. + */ explicit File(FileName file, bool readProperties = true, Properties::ReadStyle readStyle = Properties::Average); + + /*! + * Constructs a Matroska file from \a stream. If \a readProperties is \c true the + * file's audio properties will also be read. + * + * The \a readStyle parameter is currently unused. + */ explicit File(IOStream *stream, bool readProperties = true, Properties::ReadStyle readStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ ~File() override; + File(const File &) = delete; File &operator=(const File &) = delete; - AudioProperties *audioProperties() const override; + + /*! + * Returns a pointer to the tag of the file. + * + * It will create a tag if one does not exist and returns a valid pointer. + * + * \note The tag is still owned by the Matroska::File and should not + * be deleted by the user. It will be deleted when the file (object) is + * destroyed. + */ TagLib::Tag *tag() const override; - Attachments *attachments(bool create = false) const; + + /*! + * Returns a pointer to the Matroska tag of the file. + * + * If \a create is \c false this may return a null pointer if there is no tag. + * If \a create is \c true it will create a tag if one does not exist and + * returns a valid pointer. + * + * \note The tag is still owned by the Matroska::File and should not + * be deleted by the user. It will be deleted when the file (object) + * destroyed. + */ Tag *tag(bool create) const; + + /*! + * Implements the reading part of the unified property interface. + */ + PropertyMap properties() const override; + + void removeUnsupportedProperties(const StringList &properties) override; + + /*! + * Implements the writing part of the unified tag dictionary interface. + */ + PropertyMap setProperties(const PropertyMap &) override; + + /*! + * Returns the keys for attached files, "PICTURE" for images, the media + * type, file name or UID for other attached files. + * The names of the binary simple tags are included too. + */ + StringList complexPropertyKeys() const override; + + /*! + * Get the pictures stored in the attachments as complex properties + * for \a key "PICTURE". Other attached files can be retrieved, by + * media type, file name or UID. + * The attached files are returned as maps with keys "data", "mimeType", + * "description", "fileName, "uid". + * Binary simple tags can be retrieved as maps with keys "data", "name", + * "targetTypeValue", "language", "defaultLanguage". + */ + List complexProperties(const String &key) const override; + + /*! + * Set attached files as complex properties \a value, e.g. pictures for + * \a key "PICTURE" with the maps in \a value having keys "data", "mimeType", + * "description", "fileName, "uid". For other attached files, the mime type, + * file name or UID can be used as the \a key. + * Maps with keys "name" (with the same value as \a key) and "data" are + * stored as binary simple tags with additional keys "targetTypeValue", + * "language", "defaultLanguage". + */ + bool setComplexProperties(const String &key, const List &value) override; + + /*! + * Returns the Matroska::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + AudioProperties *audioProperties() const override; + + /*! + * Save the file. + * + * This returns \c true if the save was successful. + */ bool save() override; - //PropertyMap properties() const override { return PropertyMap(); } - //void removeUnsupportedProperties(const StringList &properties) override { } + + Attachments *attachments(bool create = false) const; /*! * Returns whether or not the given \a stream can be opened as a Matroska diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index 326b25d3..5026f8fb 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -18,8 +18,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAS_MATROSKASIMPLETAG_H -#define HAS_MATROSKASIMPLETAG_H +#ifndef TAGLIB_MATROSKASIMPLETAG_H +#define TAGLIB_MATROSKASIMPLETAG_H #include #include "tag.h" diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index d7982b8a..b70a45c8 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -430,6 +430,73 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) return unsupportedProperties; } +void Matroska::Tag::removeUnsupportedProperties(const StringList& properties) +{ + d->removeSimpleTags( + [&properties](const SimpleTag* t) { + return properties.contains(t->name()); + } + ); +} + +StringList Matroska::Tag::complexPropertyKeys() const +{ + StringList keys; + for(const SimpleTag *t : std::as_const(d->tags)) { + if(auto tBinary = dynamic_cast(t)) { + keys.append(tBinary->name()); + } + } + return keys; +} + +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(auto tBinary = dynamic_cast(t)) { + VariantMap property; + property.insert("data", tBinary->value()); + property.insert("name", tBinary->name()); + property.insert("targetTypeValue", tBinary->targetTypeValue()); + property.insert("language", tBinary->language()); + property.insert("defaultLanguage", tBinary->defaultLanguageFlag()); + props.append(property); + } + } + } + return TagLib::Tag::complexProperties(key); +} + +bool Matroska::Tag::setComplexProperties(const String& key, const List& value) +{ + if(key.upper() == "PICTURE") { + // Pictures are handled at the file level + return false; + } + d->removeSimpleTags( + [&key](const SimpleTag* t) { + return t->name() == key && dynamic_cast(t) != nullptr; + } + ); + bool result = false; + for(const auto &property : value) { + if(property.value("name").value() == key && property.contains("data")) { + auto *t = new SimpleTagBinary; + t->setTargetTypeValue(static_cast( + property.value("targetTypeValue", 0).value())); + t->setName(key); + t->setValue(property.value("data").value()); + t->setLanguage(property.value("language").value()); + t->setDefaultLanguageFlag(property.value("defaultLanguage", true).value()); + d->tags.append(t); + result = true; + } + } + return result; +} + PropertyMap Matroska::Tag::properties() const { PropertyMap properties; diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 5f4c0db2..5c90a986 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -63,6 +63,30 @@ namespace TagLib { bool isEmpty() const override; PropertyMap properties() const override; PropertyMap setProperties(const PropertyMap &propertyMap) override; + void removeUnsupportedProperties(const StringList& properties) override; + + /*! + * Returns the names of the binary simple tags. + */ + StringList complexPropertyKeys() const override; + + /*! + * Get the binary simple tags as maps with keys "data", "name", + * "targetTypeValue", "language", "defaultLanguage". + * The attached files such as pictures with key "PICTURE" are available + * with Matroska::File::complexProperties(). + */ + List complexProperties(const String& key) const override; + + /*! + * Set the binary simple tags as maps with keys "data", "name", + * "targetTypeValue", "language", "defaultLanguage". + * The attached files such as pictures with key "PICTURE" can be set + * with Matroska::File::setComplexProperties(). + * + * Returns \c true if \c key can be stored as binary simple tags. + */ + bool setComplexProperties(const String& key, const List& value) override; void addSimpleTag(SimpleTag *tag); void removeSimpleTag(SimpleTag *tag); From 3566b00596110d85060ee33e96286ec9b3924131 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Fri, 22 Aug 2025 13:27:07 +0200 Subject: [PATCH 15/31] Clean up attachments Avoid use of raw pointers. --- examples/matroskareader.cpp | 12 ++-- examples/matroskawriter.cpp | 10 ++-- taglib/matroska/ebml/ebmlmkattachments.cpp | 12 ++-- taglib/matroska/matroskaattachedfile.cpp | 50 +++++++++++++++- taglib/matroska/matroskaattachedfile.h | 70 ++++++++++++++++++++++ taglib/matroska/matroskaattachments.cpp | 54 +++++++++++++---- taglib/matroska/matroskaattachments.h | 20 +++++-- taglib/matroska/matroskafile.cpp | 39 ++++++------ 8 files changed, 210 insertions(+), 57 deletions(-) diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp index 070fc75d..93a7a3c4 100644 --- a/examples/matroskareader.cpp +++ b/examples/matroskareader.cpp @@ -56,17 +56,17 @@ int main(int argc, char *argv[]) if(attachments) { const TagLib::Matroska::Attachments::AttachedFileList &list = attachments->attachedFileList(); printf("Found %u attachment(s)\n", list.size()); - for(auto attachedFile : list) { - PRINT_PRETTY("Filename", attachedFile->fileName().toCString(true)); - const TagLib::String &description = attachedFile->description(); + for(const auto &attachedFile : list) { + PRINT_PRETTY("Filename", attachedFile.fileName().toCString(true)); + const TagLib::String &description = attachedFile.description(); PRINT_PRETTY("Description", !description.isEmpty() ? description.toCString(true) : "None"); - const TagLib::String &mediaType = attachedFile->mediaType(); + const TagLib::String &mediaType = attachedFile.mediaType(); PRINT_PRETTY("Media Type", !mediaType.isEmpty() ? mediaType.toCString(false) : "None"); PRINT_PRETTY("Data Size", - TagLib::Utils::formatString("%u byte(s)",attachedFile->data().size()).toCString(false) + TagLib::Utils::formatString("%u byte(s)",attachedFile.data().size()).toCString(false) ); PRINT_PRETTY("UID", - TagLib::Utils::formatString("%llu",attachedFile->uid()).toCString(false) + TagLib::Utils::formatString("%llu",attachedFile.uid()).toCString(false) ); } } diff --git a/examples/matroskawriter.cpp b/examples/matroskawriter.cpp index 6ae11953..a5f762fc 100644 --- a/examples/matroskawriter.cpp +++ b/examples/matroskawriter.cpp @@ -41,11 +41,11 @@ int main(int argc, char *argv[]) TagLib::FileStream image(argv[2]); auto data = image.readBlock(image.length()); auto attachments = file.attachments(true); - auto attachedFile = new TagLib::Matroska::AttachedFile(); - attachedFile->setFileName("cover.jpg"); - attachedFile->setMediaType("image/jpeg"); - attachedFile->setData(data); - //attachedFile->setUID(5081000385627515072ull); + TagLib::Matroska::AttachedFile attachedFile; + attachedFile.setFileName("cover.jpg"); + attachedFile.setMediaType("image/jpeg"); + attachedFile.setData(data); + //attachedFile.setUID(5081000385627515072ull); attachments->addAttachedFile(attachedFile); file.save(); diff --git a/taglib/matroska/ebml/ebmlmkattachments.cpp b/taglib/matroska/ebml/ebmlmkattachments.cpp index ed8ab2b9..bc58b952 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.cpp +++ b/taglib/matroska/ebml/ebmlmkattachments.cpp @@ -59,15 +59,15 @@ std::unique_ptr EBML::MkAttachments::parse() if(!(filename && data)) continue; - auto file = new Matroska::AttachedFile(); - file->setFileName(*filename); - file->setData(*data); + Matroska::AttachedFile file; + file.setFileName(*filename); + file.setData(*data); if(description) - file->setDescription(*description); + file.setDescription(*description); if(mediaType) - file->setMediaType(*mediaType); + file.setMediaType(*mediaType); if(uid) - file->setUID(uid); + file.setUID(uid); attachments->addAttachedFile(file); } diff --git a/taglib/matroska/matroskaattachedfile.cpp b/taglib/matroska/matroskaattachedfile.cpp index 01c99d31..06c6f4f3 100644 --- a/taglib/matroska/matroskaattachedfile.cpp +++ b/taglib/matroska/matroskaattachedfile.cpp @@ -1,4 +1,23 @@ -#include +/*************************************************************************** +* This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + #include "matroskaattachedfile.h" #include "tstring.h" #include "tbytevector.h" @@ -10,8 +29,6 @@ class Matroska::AttachedFile::AttachedFilePrivate public: AttachedFilePrivate() = default; ~AttachedFilePrivate() = default; - AttachedFilePrivate(const AttachedFilePrivate &) = delete; - AttachedFilePrivate &operator=(const AttachedFilePrivate &) = delete; String fileName; String description; String mediaType; @@ -19,12 +36,39 @@ public: UID uid = 0; }; +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + Matroska::AttachedFile::AttachedFile() : d(std::make_unique()) { } + +Matroska::AttachedFile::AttachedFile(const AttachedFile &other) : + d(std::make_unique(*other.d)) +{ +} + +Matroska::AttachedFile::AttachedFile(AttachedFile&& other) noexcept = default; + Matroska::AttachedFile::~AttachedFile() = default; +Matroska::AttachedFile &Matroska::AttachedFile::operator=(AttachedFile &&other) = default; + +Matroska::AttachedFile &Matroska::AttachedFile::operator=(const AttachedFile &other) +{ + AttachedFile(other).swap(*this); + return *this; +} + +void Matroska::AttachedFile::swap(AttachedFile &other) noexcept +{ + using std::swap; + + swap(d, other.d); +} + void Matroska::AttachedFile::setFileName(const String &fileName) { d->fileName = fileName; diff --git a/taglib/matroska/matroskaattachedfile.h b/taglib/matroska/matroskaattachedfile.h index c28e889e..14cb5475 100644 --- a/taglib/matroska/matroskaattachedfile.h +++ b/taglib/matroska/matroskaattachedfile.h @@ -21,28 +21,98 @@ #ifndef TAGLIB_MATROSKAATTACHEDFILE_H #define TAGLIB_MATROSKAATTACHEDFILE_H +#include #include "taglib_export.h" namespace TagLib { class String; class ByteVector; namespace Matroska { + //! Attached file embedded into a Matroska file. class TAGLIB_EXPORT AttachedFile { public: using UID = unsigned long long; AttachedFile(); + + /*! + * Construct an attached file as a copy of \a other. + */ + AttachedFile(const AttachedFile &other); + + /*! + * Construct an attached file moving from \a other. + */ + AttachedFile(AttachedFile &&other) noexcept; + + /*! + * Destroys this attached file. + */ ~AttachedFile(); + /*! + * Copies the contents of \a other into this object. + */ + AttachedFile &operator=(const AttachedFile &other); + + /*! + * Moves the contents of \a other into this object. + */ + AttachedFile &operator=(AttachedFile &&other); + + /*! + * Exchanges the content of the object with the content of \a other. + */ + void swap(AttachedFile &other) noexcept; + + /*! + * Set the \a fileName of the attached file. + */ void setFileName(const String &fileName); + + /*! + * Returns the filename of the attached file. + */ const String &fileName() const; + + /*! + * Set a human-friendly \a description for the attached file. + */ void setDescription(const String &description); + + /*! + * Returns the human-friendly description for the attached file. + */ const String &description() const; + + /*! + * Set the \a mediaType of the attached file. + */ void setMediaType(const String &mediaType); + + /*! + * Returns the media type of the attached file. + */ const String &mediaType() const; + + /*! + * Set the data of the attached file. + */ void setData(const ByteVector &data); + + /*! + * Returns the data of the attached file. + */ const ByteVector &data() const; + + /*! + * Set the \a uid representing the file, as random as possible. + */ void setUID(UID uid); + + /*! + * Returns the UID of the attached file. + */ UID uid() const; private: diff --git a/taglib/matroska/matroskaattachments.cpp b/taglib/matroska/matroskaattachments.cpp index b04ace77..b11eafb2 100644 --- a/taglib/matroska/matroskaattachments.cpp +++ b/taglib/matroska/matroskaattachments.cpp @@ -1,3 +1,23 @@ +/*************************************************************************** +* This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + #include "matroskaattachments.h" #include #include "matroskaattachedfile.h" @@ -18,27 +38,33 @@ public: ~AttachmentsPrivate() = default; AttachmentsPrivate(const AttachmentsPrivate &) = delete; AttachmentsPrivate &operator=(const AttachmentsPrivate &) = delete; - List files; + AttachedFileList files; }; +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + Matroska::Attachments::Attachments() : Element(static_cast(EBML::Element::Id::MkAttachments)), d(std::make_unique()) { - d->files.setAutoDelete(true); } + Matroska::Attachments::~Attachments() = default; -void Matroska::Attachments::addAttachedFile(AttachedFile *file) +void Matroska::Attachments::addAttachedFile(const AttachedFile& file) { d->files.append(file); } -void Matroska::Attachments::removeAttachedFile(AttachedFile *file) +void Matroska::Attachments::removeAttachedFile(unsigned long long uid) { - auto it = d->files.find(file); + auto it = std::find_if(d->files.begin(), d->files.end(), + [uid](const AttachedFile& file) { + return file.uid() == uid; + }); if(it != d->files.end()) { - delete *it; d->files.erase(it); } } @@ -53,6 +79,10 @@ const Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFi return d->files; } +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFiles() { return d->files; @@ -61,21 +91,21 @@ Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFiles() bool Matroska::Attachments::render() { EBML::MkAttachments attachments; - for(const auto attachedFile : d->files) { + for(const auto &attachedFile : std::as_const(d->files)) { auto attachedFileElement = EBML::make_unique_element(); // Filename auto fileNameElement = EBML::make_unique_element(); - fileNameElement->setValue(attachedFile->fileName()); + fileNameElement->setValue(attachedFile.fileName()); attachedFileElement->appendElement(std::move(fileNameElement)); // Media/MIME type auto mediaTypeElement = EBML::make_unique_element(); - mediaTypeElement->setValue(attachedFile->mediaType()); + mediaTypeElement->setValue(attachedFile.mediaType()); attachedFileElement->appendElement(std::move(mediaTypeElement)); // Description - const String &description = attachedFile->description(); + const String &description = attachedFile.description(); if(!description.isEmpty()) { auto descriptionElement = EBML::make_unique_element(); descriptionElement->setValue(description); @@ -84,12 +114,12 @@ bool Matroska::Attachments::render() // Data auto dataElement = EBML::make_unique_element(); - dataElement->setValue(attachedFile->data()); + dataElement->setValue(attachedFile.data()); attachedFileElement->appendElement(std::move(dataElement)); // UID auto uidElement = EBML::make_unique_element(); - AttachedFile::UID uid = attachedFile->uid(); + AttachedFile::UID uid = attachedFile.uid(); if(!uid) uid = EBML::randomUID(); uidElement->setValue(uid); diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index 4489cabb..b5ae7d1e 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -34,19 +34,31 @@ namespace TagLib { namespace Matroska { class AttachedFile; class File; + + //! Collection of attached files. class TAGLIB_EXPORT Attachments #ifndef DO_NOT_DOCUMENT : private Element #endif { public: - using AttachedFileList = List; + using AttachedFileList = List; + //! Construct attachments. Attachments(); - virtual ~Attachments(); - void addAttachedFile(AttachedFile *file); - void removeAttachedFile(AttachedFile *file); + //! Destroy attachements. + ~Attachments(); + + //! Add an attached file. + void addAttachedFile(const AttachedFile &file); + + //! Remove an attached file. + void removeAttachedFile(unsigned long long uid); + + //! Remove an attached file. void clear(); + + //! Get list of all attached files. const AttachedFileList &attachedFileList() const; private: diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index ac9c7fca..84ccd507 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -133,21 +133,18 @@ PropertyMap Matroska::File::setProperties(const PropertyMap &properties) namespace { - String keyForAttachedFile(const Matroska::AttachedFile *attachedFile) + String keyForAttachedFile(const Matroska::AttachedFile &attachedFile) { - if(!attachedFile) { - return {}; - } - if(attachedFile->mediaType().startsWith("image/")) { + if(attachedFile.mediaType().startsWith("image/")) { return "PICTURE"; } - if(!attachedFile->mediaType().isEmpty()) { - return attachedFile->mediaType(); + if(!attachedFile.mediaType().isEmpty()) { + return attachedFile.mediaType(); } - if(!attachedFile->fileName().isEmpty()) { - return attachedFile->fileName(); + if(!attachedFile.fileName().isEmpty()) { + return attachedFile.fileName(); } - return String::fromLongLong(attachedFile->uid()); + return String::fromLongLong(attachedFile.uid()); } unsigned long long stringToULongLong(const String &str, bool *ok) @@ -187,11 +184,11 @@ List Matroska::File::complexProperties(const String &key) const for(const auto &attachedFile : attachedFiles) { if(keyForAttachedFile(attachedFile) == key) { VariantMap property; - property.insert("data", attachedFile->data()); - property.insert("mimeType", attachedFile->mediaType()); - property.insert("description", attachedFile->description()); - property.insert("fileName", attachedFile->fileName()); - property.insert("uid", attachedFile->uid()); + property.insert("data", attachedFile.data()); + property.insert("mimeType", attachedFile.mediaType()); + property.insert("description", attachedFile.description()); + property.insert("fileName", attachedFile.fileName()); + property.insert("uid", attachedFile.uid()); props.append(property); } } @@ -226,12 +223,12 @@ bool Matroska::File::setComplexProperties(const String &key, const ListsetData(data); - attachedFile->setMediaType(mimeType); - attachedFile->setDescription(property.value("description").value()); - attachedFile->setFileName(fileName); - attachedFile->setUID(uid); + 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; From d47d28f0f8a296b177f6c6239cf08ce796c45b92 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Fri, 22 Aug 2025 08:55:51 +0200 Subject: [PATCH 16/31] Fix and simplify Matroska simple tag Avoid use of raw pointers, fix property interface. --- examples/matroskareader.cpp | 18 +- examples/matroskawriter.cpp | 17 +- taglib/matroska/ebml/ebmlmktags.cpp | 38 +--- taglib/matroska/matroskasimpletag.cpp | 126 +++++------ taglib/matroska/matroskasimpletag.h | 136 ++++++++---- taglib/matroska/matroskatag.cpp | 305 +++++++++++++------------- taglib/matroska/matroskatag.h | 8 +- 7 files changed, 336 insertions(+), 312 deletions(-) diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp index 93a7a3c4..453d583a 100644 --- a/examples/matroskareader.cpp +++ b/examples/matroskareader.cpp @@ -30,23 +30,21 @@ int main(int argc, char *argv[]) const TagLib::Matroska::SimpleTagsList &list = tag->simpleTagsList(); printf("Found %u tag(s):\n", list.size()); - for(TagLib::Matroska::SimpleTag *t : list) { - PRINT_PRETTY("Tag Name", t->name().toCString(true)); + for(const TagLib::Matroska::SimpleTag &t : list) { + PRINT_PRETTY("Tag Name", t.name().toCString(true)); - TagLib::Matroska::SimpleTagString *tString = nullptr; - TagLib::Matroska::SimpleTagBinary *tBinary = nullptr; - if((tString = dynamic_cast(t))) - PRINT_PRETTY("Tag Value", tString->value().toCString(true)); - else if((tBinary = dynamic_cast(t))) + if(t.type() == TagLib::Matroska::SimpleTag::StringType) + PRINT_PRETTY("Tag Value", t.toString().toCString(true)); + else if(t.type() == TagLib::Matroska::SimpleTag::BinaryType) PRINT_PRETTY("Tag Value", - TagLib::Utils::formatString("Binary with size %i", tBinary->value().size()).toCString(false) + TagLib::Utils::formatString("Binary with size %i", t.toByteVector().size()).toCString(false) ); - auto targetTypeValue = static_cast(t->targetTypeValue()); + auto targetTypeValue = static_cast(t.targetTypeValue()); PRINT_PRETTY("Target Type Value", targetTypeValue == 0 ? "None" : TagLib::Utils::formatString("%i", targetTypeValue).toCString(false) ); - const TagLib::String &language = t->language(); + const TagLib::String &language = t.language(); PRINT_PRETTY("Language", !language.isEmpty() ? language.toCString(false) : "Not set"); printf("\n"); diff --git a/examples/matroskawriter.cpp b/examples/matroskawriter.cpp index a5f762fc..70ad8a80 100644 --- a/examples/matroskawriter.cpp +++ b/examples/matroskawriter.cpp @@ -22,18 +22,13 @@ int main(int argc, char *argv[]) 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"); - simpleTag->setLanguage("en"); - tag->addSimpleTag(simpleTag); + tag->addSimpleTag(TagLib::Matroska::SimpleTag( + "Test Name 1", TagLib::String("Test Value 1"), + TagLib::Matroska::SimpleTag::TargetTypeValue::Track, "en")); - 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); + tag->addSimpleTag(TagLib::Matroska::SimpleTag( + "Test Name 2", TagLib::String("Test Value 2"), + TagLib::Matroska::SimpleTag::TargetTypeValue::Album)); tag->setTitle("Test title"); tag->setArtist("Test artist"); tag->setYear(1969); diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index a7dd5709..bbfec2c6 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -68,47 +68,33 @@ std::unique_ptr EBML::MkTags::parse() // Parse each for(auto simpleTag : simpleTags) { - const String *tagName = nullptr; const String *tagValueString = nullptr; const ByteVector *tagValueBinary = nullptr; - const String *language = nullptr; + String tagName; + String language; bool defaultLanguageFlag = true; for(const auto &simpleTagChild : *simpleTag) { Id id = simpleTagChild->getId(); - if(id == Id::MkTagName && !tagName) - tagName = &(element_cast(simpleTagChild)->getValue()); + if(id == Id::MkTagName && tagName.isEmpty()) + tagName = element_cast(simpleTagChild)->getValue(); else if(id == Id::MkTagString && !tagValueString) tagValueString = &(element_cast(simpleTagChild)->getValue()); else if(id == Id::MkTagBinary && !tagValueBinary) tagValueBinary = &(element_cast(simpleTagChild)->getValue()); - else if(id == Id::MkTagsTagLanguage && !language) - language = &(element_cast(simpleTagChild)->getValue()); + else if(id == Id::MkTagsTagLanguage && language.isEmpty()) + language = element_cast(simpleTagChild)->getValue(); else if(id == Id::MkTagsLanguageDefault) defaultLanguageFlag = element_cast(simpleTagChild)->getValue() ? true : false; } - if(!tagName || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) + if(tagName.isEmpty() || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary)) continue; - // Create a Simple Tag object and add it to the Tag object - Matroska::SimpleTag *sTag = nullptr; - if(tagValueString) { - auto sTagString = new Matroska::SimpleTagString(); - sTagString->setTargetTypeValue(targetTypeValue); - sTagString->setValue(*tagValueString); - sTag = sTagString; - } - else { // tagValueBinary must be non null - auto sTagBinary = new Matroska::SimpleTagBinary(); - sTagBinary->setTargetTypeValue(targetTypeValue); - sTagBinary->setValue(*tagValueBinary); - sTag = sTagBinary; - } - sTag->setName(*tagName); - if(language) - sTag->setLanguage(*language); - sTag->setDefaultLanguageFlag(defaultLanguageFlag); - mTag->addSimpleTag(sTag); + mTag->addSimpleTag(tagValueString + ? Matroska::SimpleTag(tagName, *tagValueString, + targetTypeValue, language, defaultLanguageFlag) + : Matroska::SimpleTag(tagName, *tagValueBinary, + targetTypeValue, language, defaultLanguageFlag)); } } return mTag; diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp index db0b0979..43fb0fc5 100644 --- a/taglib/matroska/matroskasimpletag.cpp +++ b/taglib/matroska/matroskasimpletag.cpp @@ -19,6 +19,7 @@ ***************************************************************************/ #include "matroskasimpletag.h" +#include #include "matroskatag.h" #include "tstring.h" #include "tbytevector.h" @@ -28,43 +29,68 @@ using namespace TagLib; class Matroska::SimpleTag::SimpleTagPrivate { public: - SimpleTagPrivate() = default; - TargetTypeValue targetTypeValue = None; - String name; - String language; - bool defaultLanguageFlag = true; + explicit SimpleTagPrivate(const String &name, const String& value, + TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) : + value(value), name(name), language(language), + targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {} + explicit SimpleTagPrivate(const String &name, const ByteVector& value, + TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) : + value(value), name(name), language(language), + targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {} + const std::variant value; + const String name; + const String language; + const TargetTypeValue targetTypeValue; + const bool defaultLanguageFlag; }; -class Matroska::SimpleTagString::SimpleTagStringPrivate -{ -public: - SimpleTagStringPrivate() = default; - String value; -}; +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// -class Matroska::SimpleTagBinary::SimpleTagBinaryPrivate -{ -public: - SimpleTagBinaryPrivate() = default; - ByteVector value; -}; - -Matroska::SimpleTag::SimpleTag() : - d(std::make_unique()) +Matroska::SimpleTag::SimpleTag(const String &name, const String &value, + TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) : + d(std::make_unique(name, value, targetTypeValue, + language, defaultLanguage)) { } + +Matroska::SimpleTag::SimpleTag(const String &name, const ByteVector &value, + TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) : + d(std::make_unique(name, value, targetTypeValue, + language, defaultLanguage)) +{ +} + +Matroska::SimpleTag::SimpleTag(const SimpleTag &other) : + d(std::make_unique(*other.d)) +{ +} + +Matroska::SimpleTag::SimpleTag(SimpleTag&& other) noexcept = default; + Matroska::SimpleTag::~SimpleTag() = default; +Matroska::SimpleTag &Matroska::SimpleTag::operator=(SimpleTag &&other) = default; + +Matroska::SimpleTag &Matroska::SimpleTag::operator=(const SimpleTag &other) +{ + SimpleTag(other).swap(*this); + return *this; +} + +void Matroska::SimpleTag::swap(SimpleTag &other) noexcept +{ + using std::swap; + + swap(d, other.d); +} + Matroska::SimpleTag::TargetTypeValue Matroska::SimpleTag::targetTypeValue() const { return d->targetTypeValue; } -void Matroska::SimpleTag::setTargetTypeValue(TargetTypeValue targetTypeValue) -{ - d->targetTypeValue = targetTypeValue; -} - const String &Matroska::SimpleTag::name() const { return d->name; @@ -75,54 +101,28 @@ const String &Matroska::SimpleTag::language() const return d->language; } -void Matroska::SimpleTag::setLanguage(const String &language) -{ - d->language = language; -} - bool Matroska::SimpleTag::defaultLanguageFlag() const { return d->defaultLanguageFlag; } -void Matroska::SimpleTag::setDefaultLanguageFlag(bool flag) +Matroska::SimpleTag::ValueType Matroska::SimpleTag::type() const { - d->defaultLanguageFlag = flag; + return std::holds_alternative(d->value) ? BinaryType : StringType; } -void Matroska::SimpleTag::setName(const String &name) +String Matroska::SimpleTag::toString() const { - d->name = name; + if(std::holds_alternative(d->value)) { + return std::get(d->value); + } + return {}; } -Matroska::SimpleTagString::SimpleTagString() : - dd(std::make_unique()) +ByteVector Matroska::SimpleTag::toByteVector() const { -} -Matroska::SimpleTagString::~SimpleTagString() = default; - -const String &Matroska::SimpleTagString::value() const -{ - return dd->value; -} - -void Matroska::SimpleTagString::setValue(const String &value) -{ - dd->value = value; -} - -Matroska::SimpleTagBinary::SimpleTagBinary() : - dd(std::make_unique()) -{ -} -Matroska::SimpleTagBinary::~SimpleTagBinary() = default; - -const ByteVector &Matroska::SimpleTagBinary::value() const -{ - return dd->value; -} - -void Matroska::SimpleTagBinary::setValue(const ByteVector &value) -{ - dd->value = value; + if(std::holds_alternative(d->value)) { + return std::get(d->value); + } + return {}; } diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index 5026f8fb..def0fea8 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -23,70 +23,116 @@ #include #include "tag.h" -//#include "matroskatag.h" namespace TagLib { class String; class ByteVector; namespace Matroska { + //! Attribute of Matroska metadata. class TAGLIB_EXPORT SimpleTag { public: + //! Specifies the level of other elements the tag value applies to. enum TargetTypeValue { - None = 0, - Shot = 10, - Subtrack = 20, - Track = 30, - Part = 40, - Album = 50, - Edition = 60, - Collection = 70 + None = 0, //!< Empty or omitted, everything in the segment + Shot = 10, //!< Shot + Subtrack = 20, //!< Subtrack / movement / scene + Track = 30, //!< Track / song / chapter + Part = 40, //!< Part / session + Album = 50, //!< Album / opera / concert / movie / episode + Edition = 60, //!< Edition / issue / volume / opus / season / sequel + Collection = 70 //!< Collection }; + + //! The types the value can have. + enum ValueType { + StringType = 0, //!< Item contains text information coded in UTF-8 + BinaryType = 1 //!< Item contains binary information + }; + + /*! + * Construct a string simple tag. + */ + SimpleTag(const String &name, const String &value, + TargetTypeValue targetTypeValue = None, + const String &language = String(), bool defaultLanguage = true); + + /*! + * Construct a binary simple tag. + */ + SimpleTag(const String &name, const ByteVector &value, + TargetTypeValue targetTypeValue = None, + const String &language = String(), bool defaultLanguage = true); + + /*! + * Construct a simple tag as a copy of \a other. + */ + SimpleTag(const SimpleTag &other); + + /*! + * Construct a simple tag moving from \a other. + */ + SimpleTag(SimpleTag &&other) noexcept; + + /*! + * Destroys this simple tag. + */ + ~SimpleTag(); + + /*! + * Copies the contents of \a other into this item. + */ + SimpleTag &operator=(const SimpleTag &other); + + /*! + * Moves the contents of \a other into this item. + */ + SimpleTag &operator=(SimpleTag &&other); + + /*! + * Exchanges the content of the simple tag with the content of \a other. + */ + void swap(SimpleTag &other) noexcept; + + /*! + * Returns the name of the simple tag. + */ const String &name() const; + + /*! + * Returns the logical level of the target. + */ TargetTypeValue targetTypeValue() const; + + /*! + * Returns the language of the tag. + */ const String &language() const; + + /*! + * Returns if this is the default/original language to use for the tag. + */ bool defaultLanguageFlag() const; - void setName(const String &name); - void setTargetTypeValue(TargetTypeValue targetTypeValue); - void setLanguage(const String &language); - void setDefaultLanguageFlag(bool flag); - virtual ~SimpleTag(); + + /*! + * Returns the type of the value. + */ + ValueType type() const; + + /*! + * Returns the StringType value. + */ + String toString() const; + + /*! + * Returns the BinaryType value. + */ + ByteVector toByteVector() const; private: class SimpleTagPrivate; TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; - - protected: - SimpleTag(); - }; - - class TAGLIB_EXPORT SimpleTagString : public SimpleTag - { - public: - SimpleTagString(); - ~SimpleTagString() override; - const String &value() const; - void setValue(const String &value); - - private: - class SimpleTagStringPrivate; - TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE - std::unique_ptr dd; - }; - - class TAGLIB_EXPORT SimpleTagBinary : public SimpleTag - { - public: - SimpleTagBinary(); - ~SimpleTagBinary() override; - const ByteVector &value() const; - void setValue(const ByteVector &value); - - private: - class SimpleTagBinaryPrivate; - TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE - std::unique_ptr dd; }; } diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index b70a45c8..bbc94bc2 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -41,7 +41,7 @@ public: ~TagPrivate() = default; bool setTag(const String &key, const String &value); - const String *getTag(const String &key) const; + String getTag(const String &key) const; template int removeSimpleTags(T &&p) @@ -51,8 +51,6 @@ public: 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++; } @@ -74,23 +72,7 @@ public: return list; } - template - const SimpleTag *findSimpleTag(T &&p) const - { - auto &list = tags; - auto it = std::find_if(list.begin(), list.end(), std::forward(p)); - return it != list.end() ? *it : nullptr; - } - - template - SimpleTag *findSimpleTag(T &&p) - { - return const_cast( - const_cast(this)->findSimpleTag(std::forward(p)) - ); - } - - List tags; + SimpleTagsList tags; ByteVector data; }; @@ -98,20 +80,24 @@ Matroska::Tag::Tag() : Element(static_cast(EBML::Element::Id::MkTags)), d(std::make_unique()) { - d->tags.setAutoDelete(true); } + Matroska::Tag::~Tag() = default; -void Matroska::Tag::addSimpleTag(SimpleTag *tag) +void Matroska::Tag::addSimpleTag(const SimpleTag &tag) { d->tags.append(tag); } -void Matroska::Tag::removeSimpleTag(SimpleTag *tag) +void Matroska::Tag::removeSimpleTag(const String &name, + SimpleTag::TargetTypeValue targetTypeValue) { - auto it = d->tags.find(tag); + auto it = std::find_if(d->tags.begin(), d->tags.end(), + [&name, targetTypeValue](const SimpleTag &t) { + return t.name() == name && t.targetTypeValue() == targetTypeValue; + } + ); if(it != d->tags.end()) { - delete *it; d->tags.erase(it); } } @@ -163,49 +149,44 @@ void Matroska::Tag::setTrack(unsigned int i) String Matroska::Tag::title() const { - const auto value = d->getTag("TITLE"); - return value ? *value : String(); + return d->getTag("TITLE"); } String Matroska::Tag::artist() const { - const auto value = d->getTag("ARTIST"); - return value ? *value : String(); + return d->getTag("ARTIST"); } String Matroska::Tag::album() const { - const auto value = d->getTag("ALBUM"); - return value ? *value : String(); + return d->getTag("ALBUM"); } String Matroska::Tag::comment() const { - const auto value = d->getTag("COMMENT"); - return value ? *value : String(); + return d->getTag("COMMENT"); } String Matroska::Tag::genre() const { - const auto value = d->getTag("GENRE"); - return value ? *value : String(); + return d->getTag("GENRE"); } unsigned int Matroska::Tag::year() const { auto value = d->getTag("DATE"); - if(!value) + if(value.isEmpty()) return 0; - auto list = value->split("-"); + auto list = value.split("-"); return static_cast(list.front().toInt()); } unsigned int Matroska::Tag::track() const { auto value = d->getTag("TRACKNUMBER"); - if(!value) + if(value.isEmpty()) return 0; - auto list = value->split("-"); + auto list = value.split("-"); return static_cast(list.front().toInt()); } @@ -217,30 +198,29 @@ bool Matroska::Tag::isEmpty() const bool Matroska::Tag::render() { EBML::MkTags tags; - List *> targetList; - targetList.setAutoDelete(true); + List targetList; // Build target-based list - for(auto tag : d->tags) { - auto targetTypeValue = tag->targetTypeValue(); + for(const auto &tag : std::as_const(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; + [&](const auto &list) { + const auto &simpleTag = list.front(); + return simpleTag.targetTypeValue() == targetTypeValue; } ); if(it == targetList.end()) { - auto list = new List(); - list->append(tag); + SimpleTagsList list; + list.append(tag); targetList.append(list); } else - (*it)->append(tag); + it->append(tag); } - for(auto list : targetList) { - auto frontTag = list->front(); - auto targetTypeValue = frontTag->targetTypeValue(); + for(const auto &list : targetList) { + const auto &frontTag = list.front(); + auto targetTypeValue = frontTag.targetTypeValue(); auto tag = EBML::make_unique_element(); // Build element @@ -253,35 +233,33 @@ bool Matroska::Tag::render() tag->appendElement(std::move(targets)); // Build element - for(auto simpleTag : *list) { + for(const auto &simpleTag : list) { auto t = EBML::make_unique_element(); auto tagName = EBML::make_unique_element(); - tagName->setValue(simpleTag->name()); + tagName->setValue(simpleTag.name()); t->appendElement(std::move(tagName)); // Tag Value - SimpleTagString *tStr = nullptr; - SimpleTagBinary *tBin = nullptr; - if((tStr = dynamic_cast(simpleTag))) { + if(simpleTag.type() == SimpleTag::StringType) { auto tagValue = EBML::make_unique_element(); - tagValue->setValue(tStr->value()); + tagValue->setValue(simpleTag.toString()); t->appendElement(std::move(tagValue)); } - else if((tBin = dynamic_cast(simpleTag))) { + else if(simpleTag.type() == SimpleTag::BinaryType) { auto tagValue = EBML::make_unique_element(); - tagValue->setValue(tBin->value()); + tagValue->setValue(simpleTag.toByteVector()); t->appendElement(std::move(tagValue)); } // Language auto language = EBML::make_unique_element(); - const String &lang = simpleTag->language(); + const String &lang = simpleTag.language(); language->setValue(!lang.isEmpty() ? lang : "und"); t->appendElement(std::move(language)); // Default language flag auto dlf = EBML::make_unique_element(); - dlf->setValue(simpleTag->defaultLanguageFlag() ? 1 : 0); + dlf->setValue(simpleTag.defaultLanguageFlag() ? 1 : 0); t->appendElement(std::move(dlf)); tag->appendElement(std::move(t)); @@ -302,51 +280,62 @@ bool Matroska::Tag::render() namespace { - // PropertyMap key, Tag name, Target type value + // PropertyMap key, Tag name, Target type value, strict // If the key is the same as the name and the target type value is Track, // no translation is needed because this is the default mapping. - // Therefore, keys like TITLE, ARTIST, GENRE, COMMENT, etc. are omitted. + // Therefore, keys like TITLE, ARTIST, GENRE, COMMENT, etc. are omitted + // unless they shall have priority over higher level tags with the same name + // when no target type value is given. The strict boolean marks + // entries which shall not be mapped without correct target type value. // For offical tags, see https://www.matroska.org/technical/tagging.html constexpr std::array simpleTagsTranslation { - std::tuple("ALBUM", "TITLE", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("ALBUMARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("TRACKNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("TRACKTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("DISCNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Part), - std::tuple("DISCTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Part), - std::tuple("DATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("TITLE", "TITLE", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("ALBUM", "TITLE", Matroska::SimpleTag::TargetTypeValue::Album, true), + std::tuple("ARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("ALBUMARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Album, true), + std::tuple("TRACKNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("DISCNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Album, true), + std::tuple("TRACKTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("DISCTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Album, true), + std::tuple("DATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album, false), // Todo - original date - std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("ALBUMARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("ENCODEDBY", "ENCODED_BY", 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("DJMIXER", "MIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("REMIXER", "REMIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("INITIALKEY", "INITIAL_KEY", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("RELEASEDATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("ENCODINGTIME", "DATE_ENCODED", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("TAGGINGDATE", "DATE_TAGGED", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("ENCODEDBY", "ENCODER", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("ENCODING", "ENCODER_SETTINGS", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("OWNER", "PURCHASE_OWNER", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMARTISTID", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("MUSICBRAINZ_ALBUMID", "MUSICBRAINZ_ALBUMID", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("MUSICBRAINZ_RELEASEGROUPID", "MUSICBRAINZ_RELEASEGROUPID", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("TITLESORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album, true), + std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("ALBUMARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album, true), + std::tuple("ENCODEDBY", "ENCODED_BY", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("MEDIA", "ORIGINAL_MEDIA_TYPE", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("LABEL", "LABEL_CODE", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("CATALOGNUMBER", "CATALOG_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("DJMIXER", "MIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("REMIXER", "REMIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("INITIALKEY", "INITIAL_KEY", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("RELEASEDATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("ENCODINGTIME", "DATE_ENCODED", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("TAGGINGDATE", "DATE_TAGGED", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("ENCODEDBY", "ENCODER", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("ENCODING", "ENCODER_SETTINGS", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("OWNER", "PURCHASE_OWNER", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_GAIN", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("REPLAYGAIN_ALBUM_GAIN", "REPLAYGAIN_GAIN", Matroska::SimpleTag::TargetTypeValue::Album, true), + std::tuple("REPLAYGAIN_TRACK_PEAK", "REPLAYGAIN_PEAK", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("REPLAYGAIN_ALBUM_PEAK", "REPLAYGAIN_PEAK", Matroska::SimpleTag::TargetTypeValue::Album, true), + std::tuple("MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMARTISTID", Matroska::SimpleTag::TargetTypeValue::Album, false), + std::tuple("MUSICBRAINZ_ALBUMID", "MUSICBRAINZ_ALBUMID", Matroska::SimpleTag::TargetTypeValue::Album, false), + std::tuple("MUSICBRAINZ_RELEASEGROUPID", "MUSICBRAINZ_RELEASEGROUPID", Matroska::SimpleTag::TargetTypeValue::Album, false), }; - std::pair translateKey(const String &key) + std::tuple 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) }; + return { std::get<1>(*it), std::get<2>(*it), std::get<3>(*it) }; if (!key.isEmpty() && !key.startsWith("_")) - return { key, Matroska::SimpleTag::TargetTypeValue::Track }; - return { String(), Matroska::SimpleTag::TargetTypeValue::None }; + return { key, Matroska::SimpleTag::TargetTypeValue::Track, false }; + return { String(), Matroska::SimpleTag::TargetTypeValue::None, false }; } String translateTag(const String &name, Matroska::SimpleTag::TargetTypeValue targetTypeValue) @@ -355,12 +344,16 @@ namespace simpleTagsTranslation.cend(), [&name, targetTypeValue](const auto &t) { return name == std::get<1>(t) - && targetTypeValue == std::get<2>(t); + && (targetTypeValue == std::get<2>(t) || + (targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None + && !std::get<3>(t))); } ); return it != simpleTagsTranslation.end() ? String(std::get<0>(*it), String::UTF8) - : targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track && !name.startsWith("_") + : (targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track || + targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) && + !name.startsWith("_") ? name : String(); } @@ -368,59 +361,65 @@ namespace bool Matroska::Tag::TagPrivate::setTag(const String &key, const String &value) { - const auto pair = translateKey(key); + const auto tpl = translateKey(key); // Workaround Clang issue - no lambda capture of structured bindings - const String &name = pair.first; - auto targetTypeValue = pair.second; + const String &name = std::get<0>(tpl); + auto targetTypeValue = std::get<1>(tpl); if(name.isEmpty()) return false; removeSimpleTags( - [&name, targetTypeValue] (auto t) { - return t->name() == name - && t->targetTypeValue() == targetTypeValue; + [&name, targetTypeValue] (const auto &t) { + return t.name() == name + && t.targetTypeValue() == targetTypeValue; } ); if(!value.isEmpty()) { - auto t = new SimpleTagString(); - t->setTargetTypeValue(targetTypeValue); - t->setName(name); - t->setValue(value); - tags.append(t); + tags.append(SimpleTag(name, value, targetTypeValue)); } return true; } -const String *Matroska::Tag::TagPrivate::getTag(const String &key) const +String Matroska::Tag::TagPrivate::getTag(const String &key) const { - const auto pair = translateKey(key); + const auto tpl = translateKey(key); // Workaround Clang issue - no lambda capture of structured bindings - const String &name = pair.first; - auto targetTypeValue = pair.second; + const String &name = std::get<0>(tpl); + auto targetTypeValue = std::get<1>(tpl); + bool strict = std::get<2>(tpl); if(name.isEmpty()) - return nullptr; - auto tag = dynamic_cast( - findSimpleTag( - [&name, targetTypeValue] (auto t) { - return t->name() == name - && t->targetTypeValue() == targetTypeValue; - } - ) + return {}; + auto it = std::find_if(tags.begin(), tags.end(), + [&name, targetTypeValue, strict] (const SimpleTag &t) { + return t.name() == name + && t.type() == SimpleTag::StringType + && (t.targetTypeValue() == targetTypeValue || + (t.targetTypeValue() == SimpleTag::TargetTypeValue::None && !strict)); + } ); - return tag ? &tag->value() : nullptr; + return it != tags.end() ? it->toString() : String(); } PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) { + // Remove all simple tags which would be returned in properties() + for(auto it = d->tags.begin(); it != d->tags.end();) { + String key; + if(it->type() == SimpleTag::StringType && + !(key = translateTag(it->name(), it->targetTypeValue())).isEmpty()) { + it = d->tags.erase(it); + } + else { + ++it; + } + } + + // Add the new properties PropertyMap unsupportedProperties; for(const auto &[key, values] : propertyMap) { for(const auto &value : values) { - if(auto [name, targetTypeValue] = translateKey(key); + if(auto [name, targetTypeValue, _] = translateKey(key); !name.isEmpty()) { - auto t = new SimpleTagString(); - t->setTargetTypeValue(targetTypeValue); - t->setName(name); - t->setValue(value); - d->tags.append(t); + d->tags.append(SimpleTag(name, value, targetTypeValue)); } else { unsupportedProperties[key] = values; @@ -433,8 +432,8 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) void Matroska::Tag::removeUnsupportedProperties(const StringList& properties) { d->removeSimpleTags( - [&properties](const SimpleTag* t) { - return properties.contains(t->name()); + [&properties](const SimpleTag &t) { + return properties.contains(t.name()); } ); } @@ -442,9 +441,9 @@ void Matroska::Tag::removeUnsupportedProperties(const StringList& properties) StringList Matroska::Tag::complexPropertyKeys() const { StringList keys; - for(const SimpleTag *t : std::as_const(d->tags)) { - if(auto tBinary = dynamic_cast(t)) { - keys.append(tBinary->name()); + for(const SimpleTag &t : std::as_const(d->tags)) { + if(t.type() == SimpleTag::BinaryType) { + keys.append(t.name()); } } return keys; @@ -454,19 +453,19 @@ 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(auto tBinary = dynamic_cast(t)) { + for(const SimpleTag &t : std::as_const(d->tags)) { + if(t.type() == SimpleTag::BinaryType) { VariantMap property; - property.insert("data", tBinary->value()); - property.insert("name", tBinary->name()); - property.insert("targetTypeValue", tBinary->targetTypeValue()); - property.insert("language", tBinary->language()); - property.insert("defaultLanguage", tBinary->defaultLanguageFlag()); + property.insert("data", t.toByteVector()); + property.insert("name", t.name()); + property.insert("targetTypeValue", t.targetTypeValue()); + property.insert("language", t.language()); + property.insert("defaultLanguage", t.defaultLanguageFlag()); props.append(property); } } } - return TagLib::Tag::complexProperties(key); + return props; } bool Matroska::Tag::setComplexProperties(const String& key, const List& value) @@ -476,21 +475,20 @@ bool Matroska::Tag::setComplexProperties(const String& key, const ListremoveSimpleTags( - [&key](const SimpleTag* t) { - return t->name() == key && dynamic_cast(t) != nullptr; + [&key](const SimpleTag &t) { + return t.name() == key && t.type() == SimpleTag::BinaryType; } ); bool result = false; for(const auto &property : value) { if(property.value("name").value() == key && property.contains("data")) { - auto *t = new SimpleTagBinary; - t->setTargetTypeValue(static_cast( - property.value("targetTypeValue", 0).value())); - t->setName(key); - t->setValue(property.value("data").value()); - t->setLanguage(property.value("language").value()); - t->setDefaultLanguageFlag(property.value("defaultLanguage", true).value()); - d->tags.append(t); + 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())); result = true; } } @@ -500,12 +498,13 @@ bool Matroska::Tag::setComplexProperties(const String& key, const Listtags)) { - if((tStr = dynamic_cast(simpleTag))) { - String key = translateTag(tStr->name(), tStr->targetTypeValue()); + 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(tStr->value()); + properties[key].append(simpleTag.toString()); + else + properties.addUnsupportedData(simpleTag.name()); } } return properties; diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 5c90a986..8be38424 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -37,7 +37,7 @@ namespace TagLib { } namespace Matroska { - using SimpleTagsList = List; + using SimpleTagsList = List; class TAGLIB_EXPORT Tag : public TagLib::Tag #ifndef DO_NOT_DOCUMENT , private Element @@ -76,7 +76,7 @@ namespace TagLib { * The attached files such as pictures with key "PICTURE" are available * with Matroska::File::complexProperties(). */ - List complexProperties(const String& key) const override; + List complexProperties(const String &key) const override; /*! * Set the binary simple tags as maps with keys "data", "name", @@ -88,8 +88,8 @@ namespace TagLib { */ bool setComplexProperties(const String& key, const List& value) override; - void addSimpleTag(SimpleTag *tag); - void removeSimpleTag(SimpleTag *tag); + void addSimpleTag(const SimpleTag &tag); + void removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue); void clearSimpleTags(); const SimpleTagsList &simpleTagsList() const; From 7a5a10102e447c5fff0731accafec9c5d05ee369 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Wed, 27 Aug 2025 16:42:19 +0200 Subject: [PATCH 17/31] 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. */ From c817cc0a47e701e299f2166ad3f49284a9c0f605 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Tue, 26 Aug 2025 22:11:01 +0200 Subject: [PATCH 18/31] tagwriter: Support setting of complex properties A complex property can be set with -C The second parameter can be set to "" to delete complex properties with the given key. The set complex property values, a simple shorthand syntax can be used. Multiple maps are separated by ';', values within a map are assigned with key=value and separated by a ','. Types are automatically detected, double quotes can be used to force a string. A ByteVector can be constructed from the contents of a file with the path is given after "file://". There is no escape, but hex codes are supported, e.g. "\x2C" to include a ',' and \x3B to include a ';'. Examples: Set a GEOB frame in an ID3v2 tag: examples/tagwriter -C GENERALOBJECT \ 'data=file://file.bin,description=My description,fileName=file.bin,mimeType=application/octet-stream' \ file.mp3 Set an APIC frame in an ID3v2 tag (same as -p file.jpg 'My description'): examples/tagwriter -C PICTURE \ 'data=file://file.jpg,description=My description,pictureType=Front Cover,mimeType=image/jpeg' \ file.mp3 Set an attached file in a Matroska file: examples/tagwriter -C file.bin \ 'fileName=file.bin,data=file://file.bin,mimeType=application/octet-stream' \ file.mka Set simple tag with target type in a Matroska file: examples/tagwriter -C PART_NUMBER \ name=PART_NUMBER,targetTypeValue=20,value=2 file.mka Set simple tag with binary value in a Matroska file: examples/tagwriter -C BINARY \ name=BINARY,data=file://file.bin,targetTypeValue=60 file.mka --- examples/tagwriter.cpp | 110 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/examples/tagwriter.cpp b/examples/tagwriter.cpp index cdf78cef..aca99491 100644 --- a/examples/tagwriter.cpp +++ b/examples/tagwriter.cpp @@ -71,6 +71,7 @@ void usage() std::cout << " -R " << std::endl; std::cout << " -I " << std::endl; std::cout << " -D " << std::endl; + std::cout << " -C " << std::endl; std::cout << " -p (\"\" \"\" to remove)" << std::endl; std::cout << std::endl; @@ -95,6 +96,102 @@ void checkForRejectedProperties(const TagLib::PropertyMap &tags) } } +/*! + * Create a list of variant maps from a string. + * The shorthand syntax in the string is kept simple, but should be sufficient + * for testing. Multiple maps are separated by ';', values within a map are + * assigned with key=value and separated by a ','. Types are detected, use + * double quotes to force a string. A ByteVector can be constructed from the + * contents of a file, the path is given after "file://". There is no escape + * character, use hex codes for ',' (\x2C) or ';' (\x3B). + */ +TagLib::List parseComplexPropertyValues(const TagLib::String &str) +{ + if(str.isEmpty() || str == "\"\"" || str == "''") { + return {}; + } + TagLib::List values; + const auto valueStrs = str.split(";"); + for(const auto &valueStr : valueStrs) { + TagLib::VariantMap value; + const auto keyValStrs = valueStr.split(","); + for(const auto &keyValStr : keyValStrs) { + if(int equalPos = keyValStr.find('='); equalPos != -1) { + TagLib::String key = keyValStr.substr(0, equalPos); + TagLib::String valStr = keyValStr.substr(equalPos + 1); + bool hasDot = false; + bool hasNonNumeric = false; + bool hasSign = false; + for(auto it = valStr.cbegin(); it != valStr.cend(); ++it) { + if(it == valStr.cbegin() && (*it == '-' || *it == '+')) { + hasSign = true; + } + else if(*it == '.') { + hasDot = true; + } + else if(*it < '0' || *it > '9') { + hasNonNumeric = true; + } + } + TagLib::Variant val; + if(valStr == "null") { + // keep empty variant + } + else if(valStr == "true" || valStr == "false") { + val = TagLib::Variant(valStr == "true"); + } + else if(!hasNonNumeric && hasDot) { + val = TagLib::Variant(std::stod(valStr.to8Bit())); + } + else if(!hasNonNumeric && hasSign) { + val = valStr.toLongLong(nullptr); + } + else if(!hasNonNumeric) { + val = valStr.toULongLong(nullptr); + } + else if(valStr.startsWith("file://")) { + auto filePath = valStr.substr(7 ); + if(isFile(filePath.toCString())) { + std::ifstream fs; + fs.open(filePath.toCString(), std::ios::in | std::ios::binary); + std::stringstream buffer; + buffer << fs.rdbuf(); + fs.close(); + TagLib::String buf(buffer.str()); + val = TagLib::Variant(buf.data(TagLib::String::Latin1)); + } + else { + std::cout << filePath.toCString() << " not found." << std::endl; + val = TagLib::Variant(TagLib::ByteVector()); + } + } + else { + int len = valStr.size(); + if(len >= 2 && valStr[0] == '"' && valStr[len - 1] == '"') { + valStr = valStr.substr(1, len - 2); + } + int hexPos = 0; + while((hexPos = valStr.find("\\x", hexPos)) != -1) { + char ch; + bool ok; + if(static_cast(valStr.length()) < hexPos + 4 || + (ch = static_cast( + valStr.substr(hexPos + 2, 2).toLongLong(&ok, 16)), !ok)) { + break; + } + valStr = valStr.substr(0, hexPos) + ch + valStr.substr(hexPos + 4); + ++hexPos; + } + val = TagLib::Variant(valStr); + } + value.insert(key, val); + } + } + values.append(value); + } + return values; +} + int main(int argc, char *argv[]) { TagLib::List fileList; @@ -170,6 +267,19 @@ int main(int argc, char *argv[]) checkForRejectedProperties(f.setProperties(map)); break; } + case 'C': { + if(i + 2 < argc) { + numArgsConsumed = 3; + if(!value.isEmpty()) { + TagLib::List values = parseComplexPropertyValues(argv[i + 2]); + f.setComplexProperties(value, values); + } + } + else { + usage(); + } + break; + } case 'p': { if(i + 2 < argc) { numArgsConsumed = 3; From 48104959b24477516989e6fe51292e733433502d Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sat, 23 Aug 2025 10:39:31 +0200 Subject: [PATCH 19/31] Integrate cues, fix element rendering and resizing --- taglib/matroska/ebml/ebmlelement.h | 3 +- taglib/matroska/ebml/ebmlmkcues.cpp | 4 +- taglib/matroska/ebml/ebmlmkcues.h | 6 +- taglib/matroska/ebml/ebmlmkseekhead.cpp | 4 +- taglib/matroska/ebml/ebmlmkseekhead.h | 2 +- taglib/matroska/ebml/ebmlmksegment.cpp | 18 +++++- taglib/matroska/ebml/ebmlmksegment.h | 4 ++ taglib/matroska/matroskaattachments.cpp | 22 +++---- taglib/matroska/matroskaattachments.h | 4 +- taglib/matroska/matroskacues.cpp | 82 +++++++++++++++++++------ taglib/matroska/matroskacues.h | 40 ++++++------ taglib/matroska/matroskaelement.cpp | 44 ++++++++++++- taglib/matroska/matroskaelement.h | 10 ++- taglib/matroska/matroskafile.cpp | 64 ++++++++++++++++--- taglib/matroska/matroskafile.h | 6 +- taglib/matroska/matroskaseekhead.cpp | 65 +++++++++++--------- taglib/matroska/matroskaseekhead.h | 8 +-- taglib/matroska/matroskasegment.cpp | 23 +++++-- taglib/matroska/matroskasegment.h | 2 + taglib/matroska/matroskatag.cpp | 28 +++++---- taglib/matroska/matroskatag.h | 2 +- 21 files changed, 316 insertions(+), 125 deletions(-) diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 8f6a3fe8..84639c8f 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -130,6 +130,7 @@ namespace TagLib::EBML { class MkTags; class MkAttachments; class MkSeekHead; + class MkCues; class VoidElement; template @@ -157,7 +158,7 @@ namespace TagLib::EBML { template <> struct GetElementTypeById { using type = MasterElement; }; template <> struct GetElementTypeById { using type = MasterElement; }; template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MkCues; }; template <> struct GetElementTypeById { using type = UTF8StringElement; }; template <> struct GetElementTypeById { using type = UTF8StringElement; }; template <> struct GetElementTypeById { using type = UTF8StringElement; }; diff --git a/taglib/matroska/ebml/ebmlmkcues.cpp b/taglib/matroska/ebml/ebmlmkcues.cpp index a97f7dd2..4ba50b3e 100644 --- a/taglib/matroska/ebml/ebmlmkcues.cpp +++ b/taglib/matroska/ebml/ebmlmkcues.cpp @@ -24,9 +24,9 @@ using namespace TagLib; -Matroska::Cues *EBML::MkCues::parse() +std::unique_ptr EBML::MkCues::parse(offset_t segmentDataOffset) { - auto cues = new Matroska::Cues(); + auto cues = std::make_unique(segmentDataOffset); cues->setOffset(offset); cues->setSize(getSize()); cues->setID(static_cast(id)); diff --git a/taglib/matroska/ebml/ebmlmkcues.h b/taglib/matroska/ebml/ebmlmkcues.h index 79530513..edb2b428 100644 --- a/taglib/matroska/ebml/ebmlmkcues.h +++ b/taglib/matroska/ebml/ebmlmkcues.h @@ -39,12 +39,16 @@ namespace TagLib { MasterElement(Id::MkCues, sizeLength, dataSize, offset) { } + MkCues(Id, int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkCues, sizeLength, dataSize, offset) + { + } MkCues() : MasterElement(Id::MkCues, 0, 0, 0) { } - Matroska::Cues *parse(); + std::unique_ptr parse(offset_t segmentDataOffset); }; } } diff --git a/taglib/matroska/ebml/ebmlmkseekhead.cpp b/taglib/matroska/ebml/ebmlmkseekhead.cpp index 20ae34bf..dccca140 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.cpp +++ b/taglib/matroska/ebml/ebmlmkseekhead.cpp @@ -25,9 +25,9 @@ using namespace TagLib; -std::unique_ptr EBML::MkSeekHead::parse() +std::unique_ptr EBML::MkSeekHead::parse(offset_t segmentDataOffset) { - auto seekHead = std::make_unique(); + auto seekHead = std::make_unique(segmentDataOffset); seekHead->setOffset(offset); seekHead->setSize(getSize() + padding); diff --git a/taglib/matroska/ebml/ebmlmkseekhead.h b/taglib/matroska/ebml/ebmlmkseekhead.h index de405c6a..da7b3071 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.h +++ b/taglib/matroska/ebml/ebmlmkseekhead.h @@ -46,7 +46,7 @@ namespace TagLib { { } - std::unique_ptr parse(); + std::unique_ptr parse(offset_t segmentDataOffset); }; } } diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index 887ca5d9..c8e068e2 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -28,6 +28,7 @@ #include "matroskafile.h" #include "matroskatag.h" #include "matroskaattachments.h" +#include "matroskacues.h" #include "matroskaseekhead.h" #include "matroskasegment.h" @@ -35,6 +36,11 @@ using namespace TagLib; EBML::MkSegment::~MkSegment() = default; +offset_t EBML::MkSegment::segmentDataOffset() const +{ + return offset + idSize(id) + sizeLength; +} + bool EBML::MkSegment::read(File &file) { offset_t maxOffset = file.tell() + dataSize; @@ -49,6 +55,11 @@ bool EBML::MkSegment::read(File &file) if(!seekHead->read(file)) return false; } + else if(id == Id::MkCues) { + cues = element_cast(std::move(element)); + if(!cues->read(file)) + return false; + } else if(id == Id::MkInfo) { info = element_cast(std::move(element)); if(!info->read(file)) @@ -94,7 +105,12 @@ std::unique_ptr EBML::MkSegment::parseAttachments() std::unique_ptr EBML::MkSegment::parseSeekHead() { - return seekHead ? seekHead->parse() : nullptr; + return seekHead ? seekHead->parse(segmentDataOffset()) : nullptr; +} + +std::unique_ptr EBML::MkSegment::parseCues() +{ + return cues ? cues->parse(segmentDataOffset()) : nullptr; } std::unique_ptr EBML::MkSegment::parseSegment() diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index e913f36c..b2c3e95b 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -26,6 +26,7 @@ #include "ebmlmktags.h" #include "ebmlmkattachments.h" #include "ebmlmkseekhead.h" +#include "ebmlmkcues.h" #include "ebmlmkinfo.h" #include "ebmlmktracks.h" #include "taglib.h" @@ -50,10 +51,12 @@ namespace TagLib { { } ~MkSegment() override; + offset_t segmentDataOffset() const; bool read(File &file) override; std::unique_ptr parseTag(); std::unique_ptr parseAttachments(); std::unique_ptr parseSeekHead(); + std::unique_ptr parseCues(); std::unique_ptr parseSegment(); void parseInfo(Matroska::Properties *properties); void parseTracks(Matroska::Properties *properties); @@ -62,6 +65,7 @@ namespace TagLib { std::unique_ptr tags; std::unique_ptr attachments; std::unique_ptr seekHead; + std::unique_ptr cues; std::unique_ptr info; std::unique_ptr tracks; }; diff --git a/taglib/matroska/matroskaattachments.cpp b/taglib/matroska/matroskaattachments.cpp index b11eafb2..2a0ad9d7 100644 --- a/taglib/matroska/matroskaattachments.cpp +++ b/taglib/matroska/matroskaattachments.cpp @@ -56,6 +56,7 @@ Matroska::Attachments::~Attachments() = default; void Matroska::Attachments::addAttachedFile(const AttachedFile& file) { d->files.append(file); + setNeedsRender(true); } void Matroska::Attachments::removeAttachedFile(unsigned long long uid) @@ -66,12 +67,14 @@ void Matroska::Attachments::removeAttachedFile(unsigned long long uid) }); if(it != d->files.end()) { d->files.erase(it); + setNeedsRender(true); } } void Matroska::Attachments::clear() { d->files.clear(); + setNeedsRender(true); } const Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFileList() const @@ -85,11 +88,17 @@ const Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFi Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFiles() { + setNeedsRender(true); return d->files; } -bool Matroska::Attachments::render() +ByteVector Matroska::Attachments::renderInternal() { + if(d->files.isEmpty()) { + // Avoid writing an Attachments element without AttachedFile element. + return {}; + } + EBML::MkAttachments attachments; for(const auto &attachedFile : std::as_const(d->files)) { auto attachedFileElement = EBML::make_unique_element(); @@ -127,14 +136,5 @@ bool Matroska::Attachments::render() attachments.appendElement(std::move(attachedFileElement)); } - - auto beforeSize = size(); - auto data = attachments.render(); - auto afterSize = data.size(); - if(beforeSize != afterSize) { - if(!emitSizeChanged(afterSize - beforeSize)) - return false; - } - setData(data); - return true; + return attachments.render(); } diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index b5ae7d1e..d7296057 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -47,7 +47,7 @@ namespace TagLib { Attachments(); //! Destroy attachements. - ~Attachments(); + virtual ~Attachments(); //! Add an attached file. void addAttachedFile(const AttachedFile &file); @@ -67,7 +67,7 @@ namespace TagLib { class AttachmentsPrivate; // private Element implementation - bool render() override; + ByteVector renderInternal() override; AttachedFileList &attachedFiles(); TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp index a064ae51..d4390a5c 100644 --- a/taglib/matroska/matroskacues.cpp +++ b/taglib/matroska/matroskacues.cpp @@ -29,14 +29,18 @@ using namespace TagLib; -Matroska::Cues::Cues() : - Element(static_cast(EBML::Element::Id::MkCues)) +Matroska::Cues::Cues(offset_t segmentDataOffset) : + Element(static_cast(EBML::Element::Id::MkCues)), + segmentDataOffset(segmentDataOffset) { + setNeedsRender(false); } ByteVector Matroska::Cues::renderInternal() { + auto beforeSize = sizeRenderedOrWritten(); EBML::MkCues cues; + cues.setMinRenderSize(beforeSize); for(const auto &cuePoint : cuePoints) { auto cuePointElement = EBML::make_unique_element(); auto timestamp = EBML::make_unique_element(); @@ -57,7 +61,33 @@ ByteVector Matroska::Cues::renderInternal() clusterPosition->setValue(cueTrack->getClusterPosition()); cueTrackElement->appendElement(std::move(clusterPosition)); - // Todo - other elements + // Relative position, optional + if(cueTrack->getRelativePosition().has_value()) { + auto relativePosition = EBML::make_unique_element(); + relativePosition->setValue(cueTrack->getRelativePosition().value()); + cueTrackElement->appendElement(std::move(relativePosition)); + } + + // Duration, optional + if(cueTrack->getDuration().has_value()) { + auto duration = EBML::make_unique_element(); + duration->setValue(cueTrack->getDuration().value()); + cueTrackElement->appendElement(std::move(duration)); + } + + // Block number, optional + if(cueTrack->getBlockNumber().has_value()) { + auto blockNumber = EBML::make_unique_element(); + blockNumber->setValue(cueTrack->getBlockNumber().value()); + cueTrackElement->appendElement(std::move(blockNumber)); + } + + // Codec state, not in version 1 + if(cueTrack->getCodecState().has_value()) { + auto codecState = EBML::make_unique_element(); + codecState->setValue(cueTrack->getCodecState().value()); + cueTrackElement->appendElement(std::move(codecState)); + } // Reference times auto referenceTimes = cueTrack->referenceTimes(); @@ -72,29 +102,33 @@ ByteVector Matroska::Cues::renderInternal() } cuePointElement->appendElement(std::move(cueTrackElement)); } + cues.appendElement(std::move(cuePointElement)); } return cues.render(); } -bool Matroska::Cues::render() +void Matroska::Cues::write(TagLib::File& file) { - if(!needsRender) - return true; - - setData(renderInternal()); - needsRender = false; - return true; + if(!data().isEmpty()) + Element::write(file); } bool Matroska::Cues::sizeChanged(Element &caller, offset_t delta) { - offset_t offset = caller.offset(); - for(auto &cuePoint : cuePoints) - needsRender |= cuePoint->adjustOffset(offset, delta); + // Adjust own offset + if(!Element::sizeChanged(caller, delta)) + return false; + + offset_t offset = caller.offset() - segmentDataOffset; + for(auto &cuePoint : cuePoints) { + if(cuePoint->adjustOffset(offset, delta)) { + setNeedsRender(true); + } + } return true; } -bool Matroska::Cues::isValid(TagLib::File &file, offset_t segmentDataOffset) const +bool Matroska::Cues::isValid(TagLib::File &file) const { for(const auto &cuePoint : cuePoints) { if(!cuePoint->isValid(file, segmentDataOffset)) @@ -150,8 +184,8 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) debug("No cluster found at position"); return false; } - if(codecState) { - file.seek(segmentDataOffset + codecState); + if(codecState.has_value() && codecState.value() != 0) { + file.seek(segmentDataOffset + codecState.value()); if(EBML::Element::readId(file) != static_cast(EBML::Element::Id::MkCodecState)) { debug("No codec state found at position"); return false; @@ -160,8 +194,18 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) return true; } -bool Matroska::CueTrack::adjustOffset(offset_t, offset_t) +bool Matroska::CueTrack::adjustOffset(offset_t offset, offset_t delta) { - // TODO implement - return false; + bool ret = false; + if(clusterPosition > offset) { + clusterPosition += delta; + ret = true; + } + offset_t codecStateValue; + if(codecState.has_value() && (codecStateValue = codecState.value()) != 0 && + codecStateValue > offset) { + codecState = codecStateValue + delta; + ret = true; + } + return ret; } diff --git a/taglib/matroska/matroskacues.h b/taglib/matroska/matroskacues.h index 192599f3..ee971d04 100644 --- a/taglib/matroska/matroskacues.h +++ b/taglib/matroska/matroskacues.h @@ -22,6 +22,8 @@ #define TAGLIB_MATROSKACUES_H #ifndef DO_NOT_DOCUMENT +#include + #include "tlist.h" #include "matroskaelement.h" @@ -38,20 +40,20 @@ namespace TagLib { { public: using CuePointList = std::list>; - Cues(); + explicit Cues(offset_t segmentDataOffset); ~Cues() override = default; - bool isValid(File &file, offset_t segmentDataOffset) const; + bool isValid(TagLib::File &file) const; void addCuePoint(std::unique_ptr &&cuePoint); const CuePointList &cuePointList() { return cuePoints; } bool sizeChanged(Element &caller, offset_t delta) override; - bool render() override; + void write(TagLib::File &file) override; private: friend class EBML::MkCues; - ByteVector renderInternal(); - bool needsRender = false; + ByteVector renderInternal() override; CuePointList cuePoints; + const offset_t segmentDataOffset; }; class CuePoint @@ -61,7 +63,7 @@ namespace TagLib { using Time = unsigned long long; CuePoint(); ~CuePoint() = default; - bool isValid(File &file, offset_t segmentDataOffset) const; + bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; void addCueTrack(std::unique_ptr &&cueTrack); const CueTrackList &cueTrackList() const { return cueTracks; } void setTime(Time time) { this->time = time; } @@ -79,19 +81,19 @@ namespace TagLib { using ReferenceTimeList = List; CueTrack() = default; ~CueTrack() = default; - bool isValid(File &file, offset_t segmentDataOffset) const; + bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; void setTrackNumber(unsigned long long trackNumber) { this->trackNumber = trackNumber; } unsigned long long getTrackNumber() const { return trackNumber; } void setClusterPosition(offset_t clusterPosition) { this->clusterPosition = clusterPosition; } offset_t getClusterPosition() const { return clusterPosition; } - void setRelativePosition(offset_t relativePosition) { this->relativePosition = relativePosition; } - offset_t getRelativePosition() const { return relativePosition; } - void setCodecState(offset_t codecState) { this->codecState = codecState; } - offset_t getCodecState() const { return codecState; } - void setBlockNumber(unsigned long long blockNumber) { this->blockNumber = blockNumber; } - unsigned long long getBlockNumber() const { return blockNumber; } - void setDuration(unsigned long long duration) { this->duration = duration; } - unsigned long long getDuration() const { return duration; } + void setRelativePosition(std::optional relativePosition) { this->relativePosition = relativePosition; } + std::optional getRelativePosition() const { return relativePosition; } + void setCodecState(std::optional codecState) { this->codecState = codecState; } + std::optional getCodecState() const { return codecState; } + void setBlockNumber(std::optional blockNumber) { this->blockNumber = blockNumber; } + std::optional getBlockNumber() const { return blockNumber; } + void setDuration(std::optional duration) { this->duration = duration; } + std::optional getDuration() const { return duration; } void addReferenceTime(unsigned long long refTime) { refTimes.append(refTime); } const ReferenceTimeList &referenceTimes() const { return refTimes; } bool adjustOffset(offset_t offset, offset_t delta); @@ -99,10 +101,10 @@ namespace TagLib { private: unsigned long long trackNumber = 0; offset_t clusterPosition = 0; - offset_t relativePosition = 0; - unsigned long long blockNumber = 0; - unsigned long long duration = 0; - offset_t codecState = 0; + std::optional relativePosition; + std::optional blockNumber; + std::optional duration; + std::optional codecState; ReferenceTimeList refTimes; }; } diff --git a/taglib/matroska/matroskaelement.cpp b/taglib/matroska/matroskaelement.cpp index 3fb65d6d..2bd5c076 100644 --- a/taglib/matroska/matroskaelement.cpp +++ b/taglib/matroska/matroskaelement.cpp @@ -38,6 +38,10 @@ public: ByteVector data; List sizeListeners; List offsetListeners; + // The default write() implementation will delete an unrendered element, + // therefore rendering is required by default and needs to be explicitly set + // using setNeedsRender(false) together with overriding the write() method. + bool needsRender = true; }; Matroska::Element::Element(ID id) : @@ -112,6 +116,35 @@ void Matroska::Element::setID(ID id) e->id = id; } +bool Matroska::Element::render() +{ + if(!needsRender()) + return true; + + auto beforeSize = sizeRenderedOrWritten(); + auto data = renderInternal(); + setNeedsRender(false); + auto afterSize = data.size(); + if(afterSize != beforeSize) { + if(!emitSizeChanged(afterSize - beforeSize)) { + return false; + } + } + + setData(data); + return true; +} + +void Matroska::Element::setNeedsRender(bool needsRender) +{ + e->needsRender = needsRender; +} + +bool Matroska::Element::needsRender() const +{ + return e->needsRender; +} + bool Matroska::Element::emitSizeChanged(offset_t delta) { for(auto element : e->sizeListeners) { @@ -132,13 +165,22 @@ bool Matroska::Element::emitOffsetChanged(offset_t delta) bool Matroska::Element::sizeChanged(Element &caller, offset_t delta) { - if(caller.offset() < e->offset) { + // The equal case is needed when multiple new elements are added + // (e.g. Attachments and Tags), they will start with the same offset + // are updated via size change handling. + if(caller.offset() <= e->offset && caller.id() != e->id) { e->offset += delta; //return emitOffsetChanged(delta); } return true; } +offset_t Matroska::Element::sizeRenderedOrWritten() const +{ + offset_t dataSize = e->data.size(); + return dataSize != 0 ? dataSize : e->size; +} + bool Matroska::Element::offsetChanged(Element &, offset_t) { // Most elements don't need to handle this diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index 9c1f19c3..35e76393 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -45,8 +45,9 @@ namespace TagLib { void adjustOffset(offset_t delta); void setSize(offset_t size); void setID(ID id); - //virtual ByteVector render() = 0; - virtual bool render() = 0; + virtual bool render(); + void setNeedsRender(bool needsRender); + bool needsRender() const; void setData(const ByteVector &data); const ByteVector &data() const; virtual void write(TagLib::File &file); @@ -60,7 +61,12 @@ namespace TagLib { virtual bool offsetChanged(Element &caller, offset_t delta); virtual bool sizeChanged(Element &caller, offset_t delta); + protected: + offset_t sizeRenderedOrWritten() const; + private: + virtual ByteVector renderInternal() = 0; + class ElementPrivate; TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr e; diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index 3ee1065f..29e4d9db 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -23,6 +23,7 @@ #include "matroskaattachments.h" #include "matroskaattachedfile.h" #include "matroskaseekhead.h" +#include "matroskacues.h" #include "matroskasegment.h" #include "ebmlutils.h" #include "ebmlelement.h" @@ -48,6 +49,7 @@ public: std::unique_ptr tag; std::unique_ptr attachments; std::unique_ptr seekHead; + std::unique_ptr cues; std::unique_ptr segment; std::unique_ptr properties; }; @@ -260,7 +262,7 @@ Matroska::Attachments *Matroska::File::attachments(bool create) const return d->attachments.get(); } -void Matroska::File::read(bool readProperties, Properties::ReadStyle) +void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle) { offset_t fileLength = length(); @@ -295,16 +297,23 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle) // Parse the elements d->segment = segment->parseSegment(); d->seekHead = segment->parseSeekHead(); + d->cues = segment->parseCues(); d->tag = segment->parseTag(); d->attachments = segment->parseAttachments(); - setValid(true); - if(readProperties) { d->properties = std::make_unique(this); segment->parseInfo(d->properties.get()); segment->parseTracks(d->properties.get()); } + + if(readStyle == AudioProperties::Accurate && + ((d->seekHead && !d->seekHead->isValid(*this)) || + (d->cues && !d->cues->isValid(*this)))) { + setValid(false); + return; + } + setValid(true); } bool Matroska::File::save() @@ -318,6 +327,19 @@ bool Matroska::File::save() return false; } + // Do not create new attachments or tags and corresponding seek head entries + // if only empty objects were created. + if(d->attachments && d->attachments->attachedFileList().isEmpty() && + d->attachments->size() == 0 && d->attachments->offset() == 0 && + d->attachments->data().isEmpty()) { + d->attachments.reset(); + } + if(d->tag && d->tag->isEmpty() && + d->tag->size() == 0 && d->tag->offset() == 0 && + d->tag->data().isEmpty()) { + d->tag.reset(); + } + List renderList; List newElements; @@ -345,7 +367,7 @@ bool Matroska::File::save() newElements.append(element); } } - if(renderList.isEmpty()) + if(renderList.isEmpty() && newElements.isEmpty()) return true; auto sortAscending = [](const auto a, const auto b) { return a->offset() < b->offset(); }; @@ -364,21 +386,45 @@ bool Matroska::File::save() for(auto it = renderList.begin(); it != renderList.end(); ++it) { for(auto it2 = std::next(it); it2 != renderList.end(); ++it2) (*it)->addSizeListener(*it2); + if(d->cues) + (*it)->addSizeListener(d->cues.get()); if(d->seekHead) (*it)->addSizeListener(d->seekHead.get()); (*it)->addSizeListener(d->segment.get()); } + if(d->cues) { + renderList.append(d->cues.get()); + d->cues->addSizeListeners(renderList); + if(d->seekHead) { + d->cues->addSizeListener(d->seekHead.get()); + } + d->cues->addSizeListener(d->segment.get()); + } if(d->seekHead) { - d->seekHead->addSizeListeners(renderList); renderList.append(d->seekHead.get()); + d->seekHead->addSizeListeners(renderList); + d->seekHead->addSizeListener(d->segment.get()); } d->segment->addSizeListeners(renderList); renderList.append(d->segment.get()); - // Render the elements - for(auto element : renderList) { - if(!element->render()) - return false; + // Render the elements. + // Because size changes of elements can cause segment offset updates and + // size changes in other elements, we might need multiple rounds until no more + // element needs rendering. + int renderRound = 0; + bool rendering = true; + while(rendering && renderRound < 5) { + rendering = false; + for(auto element : renderList) { + if(element->needsRender()) { + rendering = true; + if(!element->render()) { + return false; + } + } + } + ++renderRound; } // Write out to file diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index 7ac23fa3..66c1c07f 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -41,7 +41,8 @@ namespace TagLib::Matroska { * Constructs a Matroska file from \a file. If \a readProperties is \c true the * file's audio properties will also be read. * - * The \a readStyle parameter is currently unused. + * If \a readStyle is \c Accurate all seek head and cues segment positions + * are verified for the isValid() state of the file. */ explicit File(FileName file, bool readProperties = true, Properties::ReadStyle readStyle = Properties::Average); @@ -50,7 +51,8 @@ namespace TagLib::Matroska { * Constructs a Matroska file from \a stream. If \a readProperties is \c true the * file's audio properties will also be read. * - * The \a readStyle parameter is currently unused. + * If \a readStyle is \c Accurate all seek head and cues segment positions + * are verified for the isValid() state of the file. */ explicit File(IOStream *stream, bool readProperties = true, Properties::ReadStyle readStyle = Properties::Average); diff --git a/taglib/matroska/matroskaseekhead.cpp b/taglib/matroska/matroskaseekhead.cpp index 09a3070d..f297335e 100644 --- a/taglib/matroska/matroskaseekhead.cpp +++ b/taglib/matroska/matroskaseekhead.cpp @@ -27,27 +27,42 @@ using namespace TagLib; -Matroska::SeekHead::SeekHead() : - Element(static_cast(EBML::Element::Id::MkSeekHead)) +Matroska::SeekHead::SeekHead(offset_t segmentDataOffset) : + Element(static_cast(EBML::Element::Id::MkSeekHead)), + segmentDataOffset(segmentDataOffset) { + setNeedsRender(false); +} + +bool Matroska::SeekHead::isValid(TagLib::File& file) const +{ + bool result = true; + for(const auto &[id, offset] : entries) { + file.seek(segmentDataOffset + offset); + if(EBML::Element::readId(file) != id) { + debug(Utils::formatString("No ID %x found at seek position", id)); + result = false; + } + } + return result; } void Matroska::SeekHead::addEntry(const Element &element) { entries.append({element.id(), element.offset()}); debug("adding to seekhead"); - needsRender = true; + setNeedsRender(true); } void Matroska::SeekHead::addEntry(ID id, offset_t offset) { entries.append({id, offset}); - needsRender = true; + setNeedsRender(true); } ByteVector Matroska::SeekHead::renderInternal() { - auto beforeSize = size(); + auto beforeSize = sizeRenderedOrWritten(); EBML::MkSeekHead seekHead; seekHead.setMinRenderSize(beforeSize); for(const auto &[id, position] : entries) { @@ -65,26 +80,6 @@ ByteVector Matroska::SeekHead::renderInternal() return seekHead.render(); } -bool Matroska::SeekHead::render() -{ - if(!needsRender) - return true; - - auto beforeSize = size(); - auto data = renderInternal(); - needsRender = false; - auto afterSize = data.size(); - if(afterSize != beforeSize) { - return false; - // TODO handle expansion of seek head - if(!emitSizeChanged(afterSize - beforeSize)) - return false; - } - - setData(data); - return true; -} - void Matroska::SeekHead::write(File &file) { if(!data().isEmpty()) @@ -104,19 +99,33 @@ bool Matroska::SeekHead::sizeChanged(Element &caller, offset_t delta) return true; } else { - offset_t offset = caller.offset(); + // The equal case is needed when multiple new elements are added + // (e.g. Attachments and Tags), they will start with the same offset + // and are updated via size change handling. + offset_t offset = caller.offset() - segmentDataOffset; auto it = entries.begin(); while(it != entries.end()) { it = std::find_if(it, entries.end(), - [offset](const auto a){ return a.second > offset; } + [offset, callerID](const auto &a) { + return a.second >= offset && a.first != callerID; + } ); if(it != entries.end()) { it->second += delta; - needsRender = true; + setNeedsRender(true); ++it; } } + + if(caller.data().isEmpty() && caller.size() + delta == 0) { + // The caller element is removed, remove it from the seek head. + it = std::find_if(entries.begin(), entries.end(), + [callerID](const auto &a){ return a.first == callerID; }); + if(it != entries.end()) { + entries.erase(it); + } + } return true; } } diff --git a/taglib/matroska/matroskaseekhead.h b/taglib/matroska/matroskaseekhead.h index 4f4b09da..f41979a4 100644 --- a/taglib/matroska/matroskaseekhead.h +++ b/taglib/matroska/matroskaseekhead.h @@ -32,20 +32,20 @@ namespace TagLib { class SeekHead : public Element { public: - SeekHead(); + explicit SeekHead(offset_t segmentDataOffset); ~SeekHead() override = default; + bool isValid(TagLib::File &file) const; void addEntry(const Element &element); void addEntry(ID id, offset_t offset); - bool render() override; void write(TagLib::File &file) override; void sort(); //bool offsetChanged(Element &caller, offset_t delta) override; bool sizeChanged(Element &caller, offset_t delta) override; private: - ByteVector renderInternal(); + ByteVector renderInternal() override; List> entries; - bool needsRender = false; + const offset_t segmentDataOffset; }; } } diff --git a/taglib/matroska/matroskasegment.cpp b/taglib/matroska/matroskasegment.cpp index ddcffa90..19c32800 100644 --- a/taglib/matroska/matroskasegment.cpp +++ b/taglib/matroska/matroskasegment.cpp @@ -31,17 +31,27 @@ Matroska::Segment::Segment(offset_t sizeLength, offset_t dataSize, offset_t leng setSize(sizeLength); } +ByteVector Matroska::Segment::renderInternal() +{ + return EBML::renderVINT(dataSize, static_cast(sizeLength)); +} + bool Matroska::Segment::render() { - auto data = EBML::renderVINT(dataSize, static_cast(sizeLength)); - if(data.size() != sizeLength) { + auto beforeSize = sizeLength; + auto data = renderInternal(); + setNeedsRender(false); + auto afterSize = data.size(); + if(afterSize != beforeSize) { sizeLength = 8; - if(!emitSizeChanged(sizeLength - data.size())) - return false; - data = EBML::renderVINT(dataSize, static_cast(sizeLength)); - if(data.size() != sizeLength) + data = renderInternal(); + setNeedsRender(false); + afterSize = data.size(); + if(!emitSizeChanged(afterSize - beforeSize)) { return false; + } } + setData(data); return true; } @@ -49,5 +59,6 @@ bool Matroska::Segment::render() bool Matroska::Segment::sizeChanged(Element &, offset_t delta) { dataSize += delta; + setNeedsRender(true); return true; } diff --git a/taglib/matroska/matroskasegment.h b/taglib/matroska/matroskasegment.h index 0c9b2e0a..6e6d88bf 100644 --- a/taglib/matroska/matroskasegment.h +++ b/taglib/matroska/matroskasegment.h @@ -35,6 +35,8 @@ namespace TagLib::Matroska { offset_t dataOffset() const { return offset() + sizeLength; } private: + ByteVector renderInternal() override; + offset_t sizeLength; offset_t dataSize; }; diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 082c5775..b4e7b215 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -87,6 +87,7 @@ Matroska::Tag::~Tag() = default; void Matroska::Tag::addSimpleTag(const SimpleTag &tag) { d->tags.append(tag); + setNeedsRender(true); } void Matroska::Tag::removeSimpleTag(const String &name, @@ -99,12 +100,14 @@ void Matroska::Tag::removeSimpleTag(const String &name, ); if(it != d->tags.end()) { d->tags.erase(it); + setNeedsRender(true); } } void Matroska::Tag::clearSimpleTags() { d->tags.clear(); + setNeedsRender(true); } const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsList() const @@ -139,12 +142,12 @@ void Matroska::Tag::setGenre(const String &s) void Matroska::Tag::setYear(unsigned int i) { - d->setTag("DATE", String::number(i)); + d->setTag("DATE", i != 0 ? String::number(i) : String()); } void Matroska::Tag::setTrack(unsigned int i) { - d->setTag("TRACKNUMBER", String::number(i)); + d->setTag("TRACKNUMBER", i != 0 ? String::number(i) : String()); } String Matroska::Tag::title() const @@ -195,8 +198,13 @@ bool Matroska::Tag::isEmpty() const return d->tags.isEmpty(); } -bool Matroska::Tag::render() +ByteVector Matroska::Tag::renderInternal() { + if(d->tags.isEmpty()) { + // Avoid writing a Tags element without Tag element. + return {}; + } + EBML::MkTags tags; List targetList; @@ -266,16 +274,7 @@ bool Matroska::Tag::render() } tags.appendElement(std::move(tag)); } - - auto data = tags.render(); - auto beforeSize = size(); - auto afterSize = data.size(); - if(afterSize != beforeSize) { - if(!emitSizeChanged(afterSize - beforeSize)) - return false; - } - setData(data); - return true; + return tags.render(); } namespace @@ -421,6 +420,7 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) if(it->type() == SimpleTag::StringType && !(key = translateTag(it->name(), it->targetTypeValue())).isEmpty()) { it = d->tags.erase(it); + setNeedsRender(true); } else { ++it; @@ -434,6 +434,7 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) if(auto [name, targetTypeValue, _] = translateKey(key); !name.isEmpty()) { d->tags.append(SimpleTag(name, value, targetTypeValue)); + setNeedsRender(true); } else { unsupportedProperties[key] = values; @@ -529,6 +530,7 @@ bool Matroska::Tag::setComplexProperties(const String& key, const List(), targetTypeValue, language, defaultLanguage)); + setNeedsRender(true); result = true; } } diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 8be38424..e9106b82 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -99,7 +99,7 @@ namespace TagLib { class TagPrivate; // private Element implementation - bool render() override; + ByteVector renderInternal() override; TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; From 241b3b2921ce27ce28bb183a39c6c80b97c04824 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Tue, 2 Sep 2025 12:51:27 +0200 Subject: [PATCH 20/31] Remove offset listeners, they are not used --- taglib/matroska/matroskaelement.cpp | 27 --------------------------- taglib/matroska/matroskaelement.h | 5 ----- taglib/matroska/matroskaseekhead.h | 1 - 3 files changed, 33 deletions(-) diff --git a/taglib/matroska/matroskaelement.cpp b/taglib/matroska/matroskaelement.cpp index 2bd5c076..17c978d1 100644 --- a/taglib/matroska/matroskaelement.cpp +++ b/taglib/matroska/matroskaelement.cpp @@ -37,7 +37,6 @@ public: ID id = 0; ByteVector data; List sizeListeners; - List offsetListeners; // The default write() implementation will delete an unrendered element, // therefore rendering is required by default and needs to be explicitly set // using setNeedsRender(false) together with overriding the write() method. @@ -101,16 +100,6 @@ void Matroska::Element::addSizeListeners(const List &elements) e->sizeListeners.append(elements); } -void Matroska::Element::addOffsetListener(Element *element) -{ - e->offsetListeners.append(element); -} - -void Matroska::Element::addOffsetListeners(const List &elements) -{ - e->offsetListeners.append(elements); -} - void Matroska::Element::setID(ID id) { e->id = id; @@ -154,15 +143,6 @@ bool Matroska::Element::emitSizeChanged(offset_t delta) return true; } -bool Matroska::Element::emitOffsetChanged(offset_t delta) -{ - for(auto element : e->offsetListeners) { - if(!element->offsetChanged(*this, delta)) - return false; - } - return true; -} - bool Matroska::Element::sizeChanged(Element &caller, offset_t delta) { // The equal case is needed when multiple new elements are added @@ -170,7 +150,6 @@ bool Matroska::Element::sizeChanged(Element &caller, offset_t delta) // are updated via size change handling. if(caller.offset() <= e->offset && caller.id() != e->id) { e->offset += delta; - //return emitOffsetChanged(delta); } return true; } @@ -181,12 +160,6 @@ offset_t Matroska::Element::sizeRenderedOrWritten() const return dataSize != 0 ? dataSize : e->size; } -bool Matroska::Element::offsetChanged(Element &, offset_t) -{ - // Most elements don't need to handle this - return true; -} - void Matroska::Element::write(File &file) { file.insert(e->data, e->offset, e->size); diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index 35e76393..f78f8f56 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -53,12 +53,7 @@ namespace TagLib { virtual void write(TagLib::File &file); void addSizeListener(Element *element); void addSizeListeners(const List &elements); - void addOffsetListener(Element *element); - void addOffsetListeners(const List &elements); - //virtual void updatePosition(Element &caller, offset_t delta) = 0; bool emitSizeChanged(offset_t delta); - bool emitOffsetChanged(offset_t delta); - virtual bool offsetChanged(Element &caller, offset_t delta); virtual bool sizeChanged(Element &caller, offset_t delta); protected: diff --git a/taglib/matroska/matroskaseekhead.h b/taglib/matroska/matroskaseekhead.h index f41979a4..c9c23034 100644 --- a/taglib/matroska/matroskaseekhead.h +++ b/taglib/matroska/matroskaseekhead.h @@ -39,7 +39,6 @@ namespace TagLib { void addEntry(ID id, offset_t offset); void write(TagLib::File &file) override; void sort(); - //bool offsetChanged(Element &caller, offset_t delta) override; bool sizeChanged(Element &caller, offset_t delta) override; private: From 1d3a3757658e457a5922f78aa0f849922046f4b6 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Tue, 2 Sep 2025 18:04:30 +0200 Subject: [PATCH 21/31] Use segment title as fallback for returned tag title Some applications like handbrake store the title only in the segment info element. --- taglib/matroska/ebml/ebmlelement.cpp | 1 + taglib/matroska/ebml/ebmlelement.h | 2 ++ taglib/matroska/ebml/ebmlmkinfo.cpp | 5 +++++ taglib/matroska/matroskafile.cpp | 9 ++++++++- taglib/matroska/matroskaproperties.cpp | 11 +++++++++++ taglib/matroska/matroskaproperties.h | 8 ++++++++ taglib/matroska/matroskatag.cpp | 12 +++++++++++- taglib/matroska/matroskatag.h | 2 ++ 8 files changed, 48 insertions(+), 2 deletions(-) diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 7c947e85..348303ac 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -91,6 +91,7 @@ std::unique_ptr EBML::Element::factory(File &file) RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileData); RETURN_ELEMENT_FOR_CASE(Id::MkSeekID); RETURN_ELEMENT_FOR_CASE(Id::MkDuration); + RETURN_ELEMENT_FOR_CASE(Id::MkTitle); RETURN_ELEMENT_FOR_CASE(Id::MkSamplingFrequency); RETURN_ELEMENT_FOR_CASE(Id::MkSeekHead); RETURN_ELEMENT_FOR_CASE(Id::VoidElement); diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 84639c8f..3b8c9267 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -75,6 +75,7 @@ namespace TagLib::EBML { MkInfo = 0x1549A966, MkTimestampScale = 0x2AD7B1, MkDuration = 0x4489, + MkTitle = 0x7BA9, MkTracks = 0x1654AE6B, MkTrackEntry = 0xAE, MkCodecID = 0x86, @@ -186,6 +187,7 @@ namespace TagLib::EBML { template <> struct GetElementTypeById { using type = BinaryElement; }; template <> struct GetElementTypeById { using type = BinaryElement; }; template <> struct GetElementTypeById { using type = FloatElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; template <> struct GetElementTypeById { using type = FloatElement; }; template <> struct GetElementTypeById { using type = MkSeekHead; }; template <> struct GetElementTypeById { using type = VoidElement; }; diff --git a/taglib/matroska/ebml/ebmlmkinfo.cpp b/taglib/matroska/ebml/ebmlmkinfo.cpp index c6c48f3c..d9228b40 100644 --- a/taglib/matroska/ebml/ebmlmkinfo.cpp +++ b/taglib/matroska/ebml/ebmlmkinfo.cpp @@ -38,6 +38,7 @@ void EBML::MkInfo::parse(Matroska::Properties *properties) unsigned long long timestampScale = 1000000; double duration = 0.0; + String title; for(const auto &element : elements) { Id id = element->getId(); if (id == Id::MkTimestampScale) { @@ -46,8 +47,12 @@ void EBML::MkInfo::parse(Matroska::Properties *properties) else if (id == Id::MkDuration) { duration = element_cast(element)->getValueAsDouble(); } + else if (id == Id::MkTitle) { + title = element_cast(element)->getValue(); + } } properties->setLengthInMilliseconds( static_cast(duration * static_cast(timestampScale) / 1000000.0)); + properties->setTitle(title); } diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index 29e4d9db..5f3634d8 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -108,8 +108,12 @@ Tag *Matroska::File::tag() const Matroska::Tag *Matroska::File::tag(bool create) const { - if(!d->tag && create) + if(!d->tag && create) { d->tag = std::make_unique(); + if(d->properties) { + d->tag->setSegmentTitle(d->properties->title()); + } + } return d->tag.get(); } @@ -305,6 +309,9 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle) d->properties = std::make_unique(this); segment->parseInfo(d->properties.get()); segment->parseTracks(d->properties.get()); + if(d->tag) { + d->tag->setSegmentTitle(d->properties->title()); + } } if(readStyle == AudioProperties::Accurate && diff --git a/taglib/matroska/matroskaproperties.cpp b/taglib/matroska/matroskaproperties.cpp index 9bb9d0fc..aa14dcd8 100644 --- a/taglib/matroska/matroskaproperties.cpp +++ b/taglib/matroska/matroskaproperties.cpp @@ -40,6 +40,7 @@ public: File *file; String codecName; + String title; int length { 0 }; int bitrate { -1 }; int sampleRate { 0 }; @@ -92,6 +93,11 @@ String Matroska::Properties::codecName() const return d->codecName; } +String Matroska::Properties::title() const +{ + return d->title; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// @@ -125,3 +131,8 @@ void Matroska::Properties::setCodecName(const String &codecName) { d->codecName = codecName; } + +void Matroska::Properties::setTitle(const String& title) +{ + d->title = title; +} diff --git a/taglib/matroska/matroskaproperties.h b/taglib/matroska/matroskaproperties.h index 805d3dbe..11d61c95 100644 --- a/taglib/matroska/matroskaproperties.h +++ b/taglib/matroska/matroskaproperties.h @@ -86,6 +86,13 @@ namespace TagLib::Matroska { */ String codecName() const; + /*! + * Returns the general name of the segment. + * Some applications store the title of the file here, but players should + * prioritize the tag title over the segment title. + */ + String title() const; + private: class PropertiesPrivate; friend class EBML::MkInfo; @@ -97,6 +104,7 @@ namespace TagLib::Matroska { void setChannels(int channels); void setBitsPerSample(int bitsPerSample); void setCodecName(const String &codecName); + void setTitle(const String &title); TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index b4e7b215..7c896934 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -74,6 +74,7 @@ public: SimpleTagsList tags; ByteVector data; + String segmentTitle; }; Matroska::Tag::Tag() : @@ -115,6 +116,11 @@ const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsList() const return d->tags; } +void Matroska::Tag::setSegmentTitle(const String& title) +{ + d->segmentTitle = title; +} + void Matroska::Tag::setTitle(const String &s) { d->setTag("TITLE", s); @@ -152,7 +158,11 @@ void Matroska::Tag::setTrack(unsigned int i) String Matroska::Tag::title() const { - return d->getTag("TITLE"); + String s = d->getTag("TITLE"); + if(s.isEmpty()) { + return d->segmentTitle; + } + return s; } String Matroska::Tag::artist() const diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index e9106b82..2ceaa453 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -98,6 +98,8 @@ namespace TagLib { friend class EBML::MkTags; class TagPrivate; + void setSegmentTitle(const String &title); + // private Element implementation ByteVector renderInternal() override; From b9122afaca8cb819789536b5071b674c52529856 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Tue, 2 Sep 2025 20:45:54 +0200 Subject: [PATCH 22/31] Preserve track UID in simple tags Some encoders write track specific DURATION tags, which should not be removed. --- examples/matroskareader.cpp | 5 ++++ taglib/matroska/ebml/ebmlelement.cpp | 1 + taglib/matroska/ebml/ebmlelement.h | 2 ++ taglib/matroska/ebml/ebmlmktags.cpp | 10 +++++-- taglib/matroska/matroskasimpletag.cpp | 28 +++++++++++++------ taglib/matroska/matroskasimpletag.h | 12 +++++++-- taglib/matroska/matroskatag.cpp | 39 ++++++++++++++++++++------- taglib/matroska/matroskatag.h | 3 ++- 8 files changed, 78 insertions(+), 22 deletions(-) diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp index 453d583a..4f7f86cc 100644 --- a/examples/matroskareader.cpp +++ b/examples/matroskareader.cpp @@ -44,6 +44,11 @@ int main(int argc, char *argv[]) PRINT_PRETTY("Target Type Value", targetTypeValue == 0 ? "None" : TagLib::Utils::formatString("%i", targetTypeValue).toCString(false) ); + if(auto trackUid = t.trackUid()) { + PRINT_PRETTY("Track UID", + TagLib::Utils::formatString("%llu",trackUid).toCString(false) + ); + } const TagLib::String &language = t.language(); PRINT_PRETTY("Language", !language.isEmpty() ? language.toCString(false) : "Not set"); diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 348303ac..8d2e3a41 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -82,6 +82,7 @@ std::unique_ptr EBML::Element::factory(File &file) RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileMediaType); RETURN_ELEMENT_FOR_CASE(Id::MkCodecID); RETURN_ELEMENT_FOR_CASE(Id::MkTagTargetTypeValue); + RETURN_ELEMENT_FOR_CASE(Id::MkTagTrackUID); RETURN_ELEMENT_FOR_CASE(Id::MkTagsLanguageDefault); RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileUID); RETURN_ELEMENT_FOR_CASE(Id::MkSeekPosition); diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 3b8c9267..ac043150 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -40,6 +40,7 @@ namespace TagLib::EBML { MkTag = 0x7373, MkTagTargets = 0x63C0, MkTagTargetTypeValue = 0x68CA, + MkTagTrackUID = 0x63C5, MkSimpleTag = 0x67C8, MkTagName = 0x45A3, MkTagLanguage = 0x447A, @@ -168,6 +169,7 @@ namespace TagLib::EBML { template <> struct GetElementTypeById { using type = Latin1StringElement; }; template <> struct GetElementTypeById { using type = Latin1StringElement; }; template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; template <> struct GetElementTypeById { using type = UIntElement; }; template <> struct GetElementTypeById { using type = UIntElement; }; template <> struct GetElementTypeById { using type = UIntElement; }; diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index bbfec2c6..ffee7e5c 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -54,6 +54,7 @@ std::unique_ptr EBML::MkTags::parse() // Parse the element Matroska::SimpleTag::TargetTypeValue targetTypeValue = Matroska::SimpleTag::TargetTypeValue::None; + unsigned long long trackUid = 0; if(targets) { for(const auto &targetsChild : *targets) { Id id = targetsChild->getId(); @@ -63,6 +64,9 @@ std::unique_ptr EBML::MkTags::parse() element_cast(targetsChild)->getValue() ); } + else if(id == Id::MkTagTrackUID) { + trackUid = element_cast(targetsChild)->getValue(); + } } } @@ -92,9 +96,11 @@ std::unique_ptr EBML::MkTags::parse() mTag->addSimpleTag(tagValueString ? Matroska::SimpleTag(tagName, *tagValueString, - targetTypeValue, language, defaultLanguageFlag) + targetTypeValue, language, defaultLanguageFlag, + trackUid) : Matroska::SimpleTag(tagName, *tagValueBinary, - targetTypeValue, language, defaultLanguageFlag)); + targetTypeValue, language, defaultLanguageFlag, + trackUid)); } } return mTag; diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp index 43fb0fc5..2c5209fa 100644 --- a/taglib/matroska/matroskasimpletag.cpp +++ b/taglib/matroska/matroskasimpletag.cpp @@ -30,16 +30,19 @@ class Matroska::SimpleTag::SimpleTagPrivate { public: explicit SimpleTagPrivate(const String &name, const String& value, - TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) : - value(value), name(name), language(language), + TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage, + unsigned long long trackUid) : + value(value), name(name), language(language), trackUid(trackUid), targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {} explicit SimpleTagPrivate(const String &name, const ByteVector& value, - TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) : - value(value), name(name), language(language), + TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage, + unsigned long long trackUid) : + value(value), name(name), language(language), trackUid(trackUid), targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {} const std::variant value; const String name; const String language; + const unsigned long long trackUid; const TargetTypeValue targetTypeValue; const bool defaultLanguageFlag; }; @@ -49,16 +52,20 @@ public: //////////////////////////////////////////////////////////////////////////////// Matroska::SimpleTag::SimpleTag(const String &name, const String &value, - TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) : + TargetTypeValue targetTypeValue, + const String &language, bool defaultLanguage, + unsigned long long trackUid) : d(std::make_unique(name, value, targetTypeValue, - language, defaultLanguage)) + language, defaultLanguage, trackUid)) { } Matroska::SimpleTag::SimpleTag(const String &name, const ByteVector &value, - TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage) : + TargetTypeValue targetTypeValue, + const String &language, bool defaultLanguage, + unsigned long long trackUid) : d(std::make_unique(name, value, targetTypeValue, - language, defaultLanguage)) + language, defaultLanguage, trackUid)) { } @@ -106,6 +113,11 @@ bool Matroska::SimpleTag::defaultLanguageFlag() const return d->defaultLanguageFlag; } +unsigned long long Matroska::SimpleTag::trackUid() const +{ + return d->trackUid; +} + Matroska::SimpleTag::ValueType Matroska::SimpleTag::type() const { return std::holds_alternative(d->value) ? BinaryType : StringType; diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index def0fea8..faff96e0 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -55,14 +55,16 @@ namespace TagLib { */ SimpleTag(const String &name, const String &value, TargetTypeValue targetTypeValue = None, - const String &language = String(), bool defaultLanguage = true); + const String &language = String(), bool defaultLanguage = true, + unsigned long long trackUid = 0); /*! * Construct a binary simple tag. */ SimpleTag(const String &name, const ByteVector &value, TargetTypeValue targetTypeValue = None, - const String &language = String(), bool defaultLanguage = true); + const String &language = String(), bool defaultLanguage = true, + unsigned long long trackUid = 0); /*! * Construct a simple tag as a copy of \a other. @@ -114,6 +116,12 @@ namespace TagLib { */ bool defaultLanguageFlag() const; + /*! + * Returns the UID that identifies the track that the tags belong to, + * zero if not defined, the tag applies to all tracks + */ + unsigned long long trackUid() const; + /*! * Returns the type of the value. */ diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 7c896934..94a19e88 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -92,11 +92,13 @@ void Matroska::Tag::addSimpleTag(const SimpleTag &tag) } void Matroska::Tag::removeSimpleTag(const String &name, - SimpleTag::TargetTypeValue targetTypeValue) + SimpleTag::TargetTypeValue targetTypeValue, + unsigned long long trackUid) { auto it = std::find_if(d->tags.begin(), d->tags.end(), - [&name, targetTypeValue](const SimpleTag &t) { - return t.name() == name && t.targetTypeValue() == targetTypeValue; + [&name, targetTypeValue, trackUid](const SimpleTag &t) { + return t.name() == name && t.targetTypeValue() == targetTypeValue && + t.trackUid() == trackUid; } ); if(it != d->tags.end()) { @@ -221,11 +223,13 @@ ByteVector Matroska::Tag::renderInternal() // Build target-based list for(const auto &tag : std::as_const(d->tags)) { auto targetTypeValue = tag.targetTypeValue(); + auto trackUid = tag.trackUid(); auto it = std::find_if(targetList.begin(), targetList.end(), [&](const auto &list) { const auto &simpleTag = list.front(); - return simpleTag.targetTypeValue() == targetTypeValue; + return simpleTag.targetTypeValue() == targetTypeValue && + simpleTag.trackUid() == trackUid; } ); if(it == targetList.end()) { @@ -239,6 +243,7 @@ ByteVector Matroska::Tag::renderInternal() for(const auto &list : targetList) { const auto &frontTag = list.front(); auto targetTypeValue = frontTag.targetTypeValue(); + auto trackUid = frontTag.trackUid(); auto tag = EBML::make_unique_element(); // Build element @@ -248,6 +253,11 @@ ByteVector Matroska::Tag::renderInternal() element->setValue(static_cast(targetTypeValue)); targets->appendElement(std::move(element)); } + if(trackUid != 0) { + auto element = EBML::make_unique_element(); + element->setValue(trackUid); + targets->appendElement(std::move(element)); + } tag->appendElement(std::move(targets)); // Build element @@ -401,7 +411,8 @@ String Matroska::Tag::TagPrivate::getTag(const String &key) const return t.name() == name && t.type() == SimpleTag::StringType && (t.targetTypeValue() == targetTypeValue || - (t.targetTypeValue() == SimpleTag::TargetTypeValue::None && !strict)); + (t.targetTypeValue() == SimpleTag::TargetTypeValue::None && !strict)) + && t.trackUid() == 0; } ); return it != tags.end() ? it->toString() : String(); @@ -411,7 +422,7 @@ PropertyMap Matroska::Tag::properties() const { PropertyMap properties; for(const auto &simpleTag : std::as_const(d->tags)) { - if(simpleTag.type() == SimpleTag::StringType) { + if(simpleTag.type() == SimpleTag::StringType && simpleTag.trackUid() == 0) { String key = translateTag(simpleTag.name(), simpleTag.targetTypeValue()); if(!key.isEmpty()) properties[key].append(simpleTag.toString()); @@ -428,6 +439,7 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) for(auto it = d->tags.begin(); it != d->tags.end();) { String key; if(it->type() == SimpleTag::StringType && + it->trackUid() == 0 && !(key = translateTag(it->name(), it->targetTypeValue())).isEmpty()) { it = d->tags.erase(it); setNeedsRender(true); @@ -468,6 +480,7 @@ StringList Matroska::Tag::complexPropertyKeys() const StringList keys; for(const SimpleTag &t : std::as_const(d->tags)) { if(t.type() != SimpleTag::StringType || + t.trackUid() != 0 || translateTag(t.name(), t.targetTypeValue()).isEmpty()) { keys.append(t.name()); } @@ -482,6 +495,7 @@ List Matroska::Tag::complexProperties(const String& key) const for(const SimpleTag &t : std::as_const(d->tags)) { if(t.name() == key && (t.type() != SimpleTag::StringType || + t.trackUid() != 0 || translateTag(t.name(), t.targetTypeValue()).isEmpty())) { VariantMap property; if(t.type() != SimpleTag::StringType) { @@ -491,7 +505,12 @@ List Matroska::Tag::complexProperties(const String& key) const property.insert("value", t.toString()); } property.insert("name", t.name()); - property.insert("targetTypeValue", t.targetTypeValue()); + if(t.targetTypeValue() != SimpleTag::TargetTypeValue::None) { + property.insert("targetTypeValue", t.targetTypeValue()); + } + if(t.trackUid()) { + property.insert("trackUid", t.trackUid()); + } property.insert("language", t.language()); property.insert("defaultLanguage", t.defaultLanguageFlag()); props.append(property); @@ -511,6 +530,7 @@ bool Matroska::Tag::setComplexProperties(const String& key, const List(); bool defaultLanguage = property.value("defaultLanguage", true).value(); + auto trackUid = property.value("trackUid", 0ULL).value(); d->tags.append(property.contains("data") ? SimpleTag(key, property.value("data").value(), - targetTypeValue, language, defaultLanguage) + targetTypeValue, language, defaultLanguage, trackUid) : SimpleTag(key, property.value("value").value(), - targetTypeValue, language, defaultLanguage)); + targetTypeValue, language, defaultLanguage, trackUid)); setNeedsRender(true); result = true; } diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 2ceaa453..81f83876 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -89,7 +89,8 @@ namespace TagLib { bool setComplexProperties(const String& key, const List& value) override; void addSimpleTag(const SimpleTag &tag); - void removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue); + void removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue, + unsigned long long trackUid = 0); void clearSimpleTags(); const SimpleTagsList &simpleTagsList() const; From 81815e1091ceec4a875f7789c4fcab460d535e16 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Wed, 3 Sep 2025 16:53:03 +0200 Subject: [PATCH 23/31] Include doc type and version in properties --- examples/matroskareader.cpp | 10 ++++++++++ taglib/matroska/ebml/ebmlelement.cpp | 2 ++ taglib/matroska/ebml/ebmlelement.h | 6 +++++- taglib/matroska/matroskafile.cpp | 27 +++++++++++++++++++++++--- taglib/matroska/matroskafile.h | 2 +- taglib/matroska/matroskaproperties.cpp | 27 +++++++++++++++++++++----- taglib/matroska/matroskaproperties.h | 14 ++++++++++++- 7 files changed, 77 insertions(+), 11 deletions(-) diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp index 4f7f86cc..c688f2e0 100644 --- a/examples/matroskareader.cpp +++ b/examples/matroskareader.cpp @@ -76,5 +76,15 @@ int main(int argc, char *argv[]) else printf("File has no attachments\n"); + if(auto properties = dynamic_cast(file.audioProperties())) { + printf("Properties:\n"); + PRINT_PRETTY("Doc Type", properties->docType().toCString(false)); + PRINT_PRETTY("Doc Type Version", TagLib::String::number(properties->docTypeVersion()).toCString(false)); + PRINT_PRETTY("Codec Name", properties->codecName().toCString(true)); + PRINT_PRETTY("Bitrate", TagLib::String::number(properties->bitrate()).toCString(false)); + PRINT_PRETTY("Sample Rate", TagLib::String::number(properties->sampleRate()).toCString(false)); + PRINT_PRETTY("Channels", TagLib::String::number(properties->channels()).toCString(false)); + PRINT_PRETTY("Length [ms]", TagLib::String::number(properties->lengthInMilliseconds()).toCString(false)); + } return 0; } diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 8d2e3a41..8ecea7fb 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -62,6 +62,8 @@ std::unique_ptr EBML::Element::factory(File &file) auto id = static_cast(uintId); switch(id) { RETURN_ELEMENT_FOR_CASE(Id::EBMLHeader); + RETURN_ELEMENT_FOR_CASE(Id::DocType); + RETURN_ELEMENT_FOR_CASE(Id::DocTypeVersion); RETURN_ELEMENT_FOR_CASE(Id::MkSegment); RETURN_ELEMENT_FOR_CASE(Id::MkInfo); RETURN_ELEMENT_FOR_CASE(Id::MkTracks); diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index ac043150..7487889c 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -34,6 +34,8 @@ namespace TagLib::EBML { enum class Id : unsigned int { EBMLHeader = 0x1A45DFA3, + DocType = 0x4282, + DocTypeVersion = 0x4287, VoidElement = 0xEC, MkSegment = 0x18538067, MkTags = 0x1254C367, @@ -143,7 +145,9 @@ namespace TagLib::EBML { template struct GetElementTypeById; - template <> struct GetElementTypeById { using type = Element; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; template <> struct GetElementTypeById { using type = MkSegment; }; template <> struct GetElementTypeById { using type = MkInfo; }; template <> struct GetElementTypeById { using type = MkTracks; }; diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index 5f3634d8..e0accda1 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -27,6 +27,8 @@ #include "matroskasegment.h" #include "ebmlutils.h" #include "ebmlelement.h" +#include "ebmlstringelement.h" +#include "ebmluintelement.h" #include "ebmlmksegment.h" #include "tlist.h" #include "tdebug.h" @@ -96,7 +98,7 @@ Matroska::File::File(IOStream *stream, bool readProperties, Matroska::File::~File() = default; -AudioProperties *Matroska::File::audioProperties() const +Matroska::Properties *Matroska::File::audioProperties() const { return d->properties.get(); } @@ -271,13 +273,19 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle) offset_t fileLength = length(); // Find the EBML Header - std::unique_ptr head(EBML::Element::factory(*this)); + auto head = EBML::element_cast( + EBML::Element::factory(*this)); if(!head || head->getId() != EBML::Element::Id::EBMLHeader) { debug("Failed to find EBML head"); setValid(false); return; } - head->skipData(*this); + if(readProperties) { + head->read(*this); + } + else { + head->skipData(*this); + } // Find the Matroska segment in the file std::unique_ptr segment( @@ -307,6 +315,19 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle) if(readProperties) { d->properties = std::make_unique(this); + + for(const auto &element : *head) { + auto id = element->getId(); + if (id == EBML::Element::Id::DocType) { + d->properties->setDocType( + EBML::element_cast(element)->getValue()); + } + else if (id == EBML::Element::Id::DocTypeVersion) { + d->properties->setDocTypeVersion(static_cast( + EBML::element_cast(element)->getValue())); + } + } + segment->parseInfo(d->properties.get()); segment->parseTracks(d->properties.get()); if(d->tag) { diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index 66c1c07f..af57e5a6 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -134,7 +134,7 @@ namespace TagLib::Matroska { * Returns the Matroska::Properties for this file. If no audio properties * were read then this will return a null pointer. */ - AudioProperties *audioProperties() const override; + Properties *audioProperties() const override; /*! * Save the file. diff --git a/taglib/matroska/matroskaproperties.cpp b/taglib/matroska/matroskaproperties.cpp index aa14dcd8..c1c2a3c5 100644 --- a/taglib/matroska/matroskaproperties.cpp +++ b/taglib/matroska/matroskaproperties.cpp @@ -41,6 +41,8 @@ public: File *file; String codecName; String title; + String docType; + int docTypeVersion { 0 }; int length { 0 }; int bitrate { -1 }; int sampleRate { 0 }; @@ -88,6 +90,16 @@ int Matroska::Properties::bitsPerSample() const return d->bitsPerSample; } +String Matroska::Properties::docType() const +{ + return d->docType; +} + +int Matroska::Properties::docTypeVersion() const +{ + return d->docTypeVersion; +} + String Matroska::Properties::codecName() const { return d->codecName; @@ -107,11 +119,6 @@ void Matroska::Properties::setLengthInMilliseconds(int length) d->length = length; } -void Matroska::Properties::setBitrate(int bitrate) -{ - d->bitrate = bitrate; -} - void Matroska::Properties::setSampleRate(int sampleRate) { d->sampleRate = sampleRate; @@ -127,6 +134,16 @@ void Matroska::Properties::setBitsPerSample(int bitsPerSample) d->bitsPerSample = bitsPerSample; } +void Matroska::Properties::setDocType(const String &docType) +{ + d->docType = docType; +} + +void Matroska::Properties::setDocTypeVersion(int docTypeVersion) +{ + d->docTypeVersion = docTypeVersion; +} + void Matroska::Properties::setCodecName(const String &codecName) { d->codecName = codecName; diff --git a/taglib/matroska/matroskaproperties.h b/taglib/matroska/matroskaproperties.h index 11d61c95..0481ffc4 100644 --- a/taglib/matroska/matroskaproperties.h +++ b/taglib/matroska/matroskaproperties.h @@ -80,6 +80,16 @@ namespace TagLib::Matroska { */ int bitsPerSample() const; + /*! + * Returns the EBML doc type, "matroska" or "webm". + */ + String docType() const; + + /*! + * Returns the EBML doc type version, typical values are 2 or 4. + */ + int docTypeVersion() const; + /*! * Returns the concrete codec name, for example "A_MPEG/L3" * used in the file if available, otherwise an empty string. @@ -97,12 +107,14 @@ namespace TagLib::Matroska { class PropertiesPrivate; friend class EBML::MkInfo; friend class EBML::MkTracks; + friend class File; void setLengthInMilliseconds(int length); - void setBitrate(int bitrate); void setSampleRate(int sampleRate); void setChannels(int channels); void setBitsPerSample(int bitsPerSample); + void setDocType(const String &docType); + void setDocTypeVersion(int docTypeVersion); void setCodecName(const String &codecName); void setTitle(const String &title); From aef78068b327fbff184d4a8cba91282aef15054f Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Thu, 4 Sep 2025 22:12:41 +0200 Subject: [PATCH 24/31] Render when tags are modified --- taglib/matroska/matroskatag.cpp | 51 ++++++++++++++++++++------------- taglib/matroska/matroskatag.h | 1 + 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 94a19e88..34a2b2d7 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -123,39 +123,48 @@ void Matroska::Tag::setSegmentTitle(const String& title) d->segmentTitle = title; } +bool Matroska::Tag::setTag(const String &key, const String &value) +{ + bool found = d->setTag(key, value); + if(found) { + setNeedsRender(true); + } + return found; +} + void Matroska::Tag::setTitle(const String &s) { - d->setTag("TITLE", s); + setTag("TITLE", s); } void Matroska::Tag::setArtist(const String &s) { - d->setTag("ARTIST", s); + setTag("ARTIST", s); } void Matroska::Tag::setAlbum(const String &s) { - d->setTag("ALBUM", s); + setTag("ALBUM", s); } void Matroska::Tag::setComment(const String &s) { - d->setTag("COMMENT", s); + setTag("COMMENT", s); } void Matroska::Tag::setGenre(const String &s) { - d->setTag("GENRE", s); + setTag("GENRE", s); } void Matroska::Tag::setYear(unsigned int i) { - d->setTag("DATE", i != 0 ? String::number(i) : String()); + setTag("DATE", i != 0 ? String::number(i) : String()); } void Matroska::Tag::setTrack(unsigned int i) { - d->setTag("TRACKNUMBER", i != 0 ? String::number(i) : String()); + setTag("TRACKNUMBER", i != 0 ? String::number(i) : String()); } String Matroska::Tag::title() const @@ -468,11 +477,12 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) void Matroska::Tag::removeUnsupportedProperties(const StringList& properties) { - d->removeSimpleTags( - [&properties](const SimpleTag &t) { - return properties.contains(t.name()); - } - ); + if(d->removeSimpleTags( + [&properties](const SimpleTag& t) { + return properties.contains(t.name()); + }) > 0) { + setNeedsRender(true); + } } StringList Matroska::Tag::complexPropertyKeys() const @@ -526,14 +536,15 @@ bool Matroska::Tag::setComplexProperties(const String& key, const ListremoveSimpleTags( - [&key](const SimpleTag &t) { - return t.name() == key && - (t.type() != SimpleTag::StringType || - t.trackUid() != 0 || - translateTag(t.name(), t.targetTypeValue()).isEmpty()); - } - ); + if(d->removeSimpleTags( + [&key](const SimpleTag &t) { + return t.name() == key && + (t.type() != SimpleTag::StringType || + t.trackUid() != 0 || + translateTag(t.name(), t.targetTypeValue()).isEmpty()); + }) > 0) { + setNeedsRender(true); + } bool result = false; for(const auto &property : value) { if(property.value("name").value() == key && diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 81f83876..7af36d41 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -99,6 +99,7 @@ namespace TagLib { friend class EBML::MkTags; class TagPrivate; + bool setTag(const String &key, const String &value); void setSegmentTitle(const String &title); // private Element implementation From 280d6306d229d3797ef501cf974f579ff3d0f2fa Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Fri, 5 Sep 2025 17:27:24 +0200 Subject: [PATCH 25/31] Update property mapping documentation for Matroska --- taglib/matroska/matroskatag.cpp | 1 - taglib/toolkit/propertymapping.dox | 208 +++++++++++++++-------------- 2 files changed, 106 insertions(+), 103 deletions(-) diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 34a2b2d7..d2b5b6be 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -331,7 +331,6 @@ namespace std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album, true), std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("ALBUMARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album, true), - std::tuple("ENCODEDBY", "ENCODED_BY", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("MEDIA", "ORIGINAL_MEDIA_TYPE", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("LABEL", "LABEL_CODE", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("CATALOGNUMBER", "CATALOG_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track, false), diff --git a/taglib/toolkit/propertymapping.dox b/taglib/toolkit/propertymapping.dox index 67450af8..b3fcc68e 100644 --- a/taglib/toolkit/propertymapping.dox +++ b/taglib/toolkit/propertymapping.dox @@ -6,107 +6,111 @@ - Vorbis Comments are not included in the table because they use the names from the *Key* column. - %APE tags will also use these keys unless another name can be found in its column. +- %Matroska tags will also use these keys unless another name can be found in its column, + additionally, the target level is listed, e.g. /50 for Album, /30 for Track (default). -| Key | %ID3v2 | %RIFF | %MP4 | %APE | %ASF | -| -------------------------- | ------ | ----- | -------------------------------------------------------- | ----------------------- | --------------------------------- | -| ACOUSTID_FINGERPRINT | | | | | Acoustid/Fingerprint | -| ACOUSTID_ID | | | | | Acoustid/Id | -| ALBUM | TALB | IPRD | ©alb | | WM/AlbumTitle | -| ALBUMARTIST | TPE2 | | aART | ALBUM ARTIST | WM/AlbumArtist | -| ALBUMARTISTSORT | TSO2 | | soaa | | WM/AlbumArtistSortOrder | -| ALBUMSORT | TSOA | | soal | | WM/AlbumSortOrder | -| ARRANGER | | IENG | | | | -| ARTIST | TPE1 | IART | ©ART | | | -| ARTISTS | | | \----:com.apple.iTunes:ARTISTS | | WM/ARTISTS | -| ARTISTSORT | TSOP | | soar | | WM/ArtistSortOrder | -| ARTISTWEBPAGE | WOAR | IBSU | | | WM/AuthorURL | -| ASIN | | | \----:com.apple.iTunes:ASIN | | | -| AUDIOSOURCEWEBPAGE | WOAS | | | | ASIN | -| BARCODE | | | \----:com.apple.iTunes:BARCODE | | WM/Barcode | -| BPM | TBPM | IBPM | tmpo | | WM/BeatsPerMinute | -| CATALOGNUMBER | | | \----:com.apple.iTunes:CATALOGNUMBER | | WM/CatalogNo | -| COMMENT | COMM | ICMT | ©cmt | | | -| COMPILATION | TCMP | | cpil | | | -| COMPOSER | TCOM | IMUS | ©wrt | | WM/Composer | -| COMPOSERSORT | TSOC | | soco | | | -| CONDUCTOR | TPE3 | | \----:com.apple.iTunes:CONDUCTOR | | WM/Conductor | -| COPYRIGHT | TCOP | ICOP | cprt | | | -| COPYRIGHTURL | WCOP | | | | | -| DATE | TDRC | ICRD | ©day | YEAR | WM/Year | -| DISCNUMBER | TPOS | | disk | DISC | WM/PartOfSet | -| DISCSUBTITLE | TSST | PRT1 | \----:com.apple.iTunes:DISCSUBTITLE | | WM/SetSubTitle | -| DJMIXER | | | \----:com.apple.iTunes:DJMIXER | | | -| ENCODEDBY | TENC | ITCH | ©enc | | WM/EncodedBy | -| ENCODING | TSSE | ISFT | ©too | | WM/EncodingSettings | -| ENCODINGTIME | TDEN | IDIT | | | WM/EncodingTime | -| ENGINEER | | | \----:com.apple.iTunes:ENGINEER | | | -| FILETYPE | TFLT | | | | | -| FILEWEBPAGE | WOAF | | | | WM/AudioFileURL | -| GAPLESSPLAYBACK | | | pgap | | | -| GENRE | TCON | IGNR | ©gen | | WM/Genre | -| GROUPING | GRP1 | | ©grp | | | -| INITIALKEY | TKEY | | | | WM/InitialKey | -| INVOLVEDPEOPLE | TIPL | | | | | -| ISRC | TSRC | ISRC | \----:com.apple.iTunes:ISRC | | WM/ISRC | -| LABEL | TPUB | IPUB | \----:com.apple.iTunes:LABEL | | WM/Publisher | -| LANGUAGE | TLAN | ILNG | \----:com.apple.iTunes:LANGUAGE | | WM/Language | -| LENGTH | TLEN | | | | | -| LICENSE | | | \----:com.apple.iTunes:LICENSE | | | -| LYRICIST | TEXT | IWRI | \----:com.apple.iTunes:LYRICIST | | WM/Writer | -| LYRICS | USLT | | ©lyr | | WM/Lyrics | -| MEDIA | TMED | IMED | \----:com.apple.iTunes:MEDIA | | WM/Media | -| MIXER | | | \----:com.apple.iTunes:MIXER | | | -| MOOD | TMOO | | \----:com.apple.iTunes:MOOD | | WM/Mood | -| MOVEMENTCOUNT | | | ©mvc | | | -| MOVEMENTNAME | MVNM | | ©mvn | | | -| MOVEMENTNUMBER | MVIN | | ©mvi | | | -| MUSICBRAINZ_ALBUMID | | | \----:com.apple.iTunes:MusicBrainz Album Id | | MusicBrainz/Album Id | -| MUSICBRAINZ_ALBUMARTISTID | | | \----:com.apple.iTunes:MusicBrainz Album Artist Id | | MusicBrainz/Album Artist Id | -| MUSICBRAINZ_ARTISTID | | | \----:com.apple.iTunes:MusicBrainz Artist Id | | MusicBrainz/Artist Id | -| MUSICBRAINZ_RELEASEGROUPID | | | \----:com.apple.iTunes:MusicBrainz Release Group Id | | MusicBrainz/Release Group Id | -| MUSICBRAINZ_RELEASETRACKID | | | \----:com.apple.iTunes:MusicBrainz Release Track Id | | MusicBrainz/Release Track Id | -| MUSICBRAINZ_TRACKID | | | \----:com.apple.iTunes:MusicBrainz Track Id | | MusicBrainz/Track Id | -| MUSICBRAINZ_WORKID | | | \----:com.apple.iTunes:MusicBrainz Work Id | | MusicBrainz/Work Id | -| MUSICIANCREDITS | TMCL | | | | | -| MUSICIP_PUID | | | | | MusicIP/PUID | -| ORIGINALALBUM | TOAL | | | | WM/OriginalAlbumTitle | -| ORIGINALARTIST | TOPE | | | | WM/OriginalArtist | -| ORIGINALDATE | TDOR | | \----:com.apple.iTunes:ORIGINALDATE | | WM/OriginalReleaseYear | -| ORIGINALFILENAME | TOFN | | | | WM/OriginalFilename | -| ORIGINALLYRICIST | TOLY | | | | WM/OriginalLyricist | -| OWNER | TOWN | | ownr | | | -| PAYMENTWEBPAGE | WPAY | | | | | -| PERFORMER | | ISTR | | | | -| PLAYLISTDELAY | TDLY | | | | | -| PODCAST | PCST | | pcst | | | -| PODCASTCATEGORY | TCAT | | catg | | | -| PODCASTDESC | TDES | | desc | | | -| PODCASTID | TGID | | egid | | | -| PODCASTURL | WFED | | purl | | | -| PRODUCEDNOTICE | TPRO | | | | | -| PRODUCER | | | \----:com.apple.iTunes:PRODUCER | | WM/Producer | -| PUBLISHERWEBPAGE | WPUB | | | | | -| RADIOSTATION | TRSN | | | | | -| RADIOSTATIONOWNER | TRSO | | | | | -| RADIOSTATIONWEBPAGE | WORS | | | | | -| RELEASECOUNTRY | | ICNT | \----:com.apple.iTunes:MusicBrainz Album Release Country | | MusicBrainz/Album Release Country | -| RELEASEDATE | TDRL | | \----:com.apple.iTunes:RELEASEDATE | | | -| RELEASESTATUS | | | \----:com.apple.iTunes:MusicBrainz Album Status | MUSICBRAINZ_ALBUMSTATUS | MusicBrainz/Album Status | -| RELEASETYPE | | | \----:com.apple.iTunes:MusicBrainz Album Type | MUSICBRAINZ_ALBUMTYPE | MusicBrainz/Album Type | -| REMIXER | TPE4 | IEDT | \----:com.apple.iTunes:REMIXER | MIXARTIST | WM/ModifiedBy | -| SCRIPT | | | \----:com.apple.iTunes:SCRIPT | | WM/Script | -| SHOWSORT | | | sosn | | | -| SHOWWORKMOVEMENT | | | shwm | | | -| SUBTITLE | TIT3 | | \----:com.apple.iTunes:SUBTITLE | | WM/SubTitle | -| TAGGINGDATE | TDTG | | | | | -| TITLE | TIT2 | INAM | ©nam | | | -| TITLESORT | TSOT | | sonm | | WM/TitleSortOrder | -| TRACKNUMBER | TRCK | IPRT | trkn | TRACK | WM/TrackNumber | -| TVEPISODE | | | tves | | | -| TVEPISODEID | | | tven | | | -| TVNETWORK | | | tvnn | | | -| TVSEASON | | | tvsn | | | -| TVSHOW | | | tvsh | | | -| URL | WXXX | | | | | -| WORK | TIT1 | | ©wrk | | WM/ContentGroupDescription | +| Key | %ID3v2 | %RIFF | %MP4 | %APE | %ASF | %Matroska | +| -------------------------- | ------ | ----- | -------------------------------------------------------- | ----------------------- | --------------------------------- | ----------------------------- | +| ACOUSTID_FINGERPRINT | | | | | Acoustid/Fingerprint | | +| ACOUSTID_ID | | | | | Acoustid/Id | | +| ALBUM | TALB | IPRD | ©alb | | WM/AlbumTitle | TITLE/50 | +| ALBUMARTIST | TPE2 | | aART | ALBUM ARTIST | WM/AlbumArtist | ARTIST/50 | +| ALBUMARTISTSORT | TSO2 | | soaa | | WM/AlbumArtistSortOrder | ARTISTSORT/50 | +| ALBUMSORT | TSOA | | soal | | WM/AlbumSortOrder | TITLESORT/50 | +| ARRANGER | | IENG | | | | | +| ARTIST | TPE1 | IART | ©ART | | | | +| ARTISTS | | | \----:com.apple.iTunes:ARTISTS | | WM/ARTISTS | | +| ARTISTSORT | TSOP | | soar | | WM/ArtistSortOrder | | +| ARTISTWEBPAGE | WOAR | IBSU | | | WM/AuthorURL | | +| ASIN | | | \----:com.apple.iTunes:ASIN | | | | +| AUDIOSOURCEWEBPAGE | WOAS | | | | ASIN | | +| BARCODE | | | \----:com.apple.iTunes:BARCODE | | WM/Barcode | | +| BPM | TBPM | IBPM | tmpo | | WM/BeatsPerMinute | | +| CATALOGNUMBER | | | \----:com.apple.iTunes:CATALOGNUMBER | | WM/CatalogNo | CATALOG_NUMBER | +| COMMENT | COMM | ICMT | ©cmt | | | | +| COMPILATION | TCMP | | cpil | | | | +| COMPOSER | TCOM | IMUS | ©wrt | | WM/Composer | | +| COMPOSERSORT | TSOC | | soco | | | | +| CONDUCTOR | TPE3 | | \----:com.apple.iTunes:CONDUCTOR | | WM/Conductor | | +| COPYRIGHT | TCOP | ICOP | cprt | | | | +| COPYRIGHTURL | WCOP | | | | | | +| DATE | TDRC | ICRD | ©day | YEAR | WM/Year | DATE_RELEASED/50 | +| DISCNUMBER | TPOS | | disk | DISC | WM/PartOfSet | PART_NUMBER/50 | +| DISCSUBTITLE | TSST | PRT1 | \----:com.apple.iTunes:DISCSUBTITLE | | WM/SetSubTitle | | +| DISCTOTAL | | | | | | TOTAL_PARTS/50 | +| DJMIXER | | | \----:com.apple.iTunes:DJMIXER | | | MIXED_BY | +| ENCODEDBY | TENC | ITCH | ©enc | | WM/EncodedBy | ENCODER | +| ENCODING | TSSE | ISFT | ©too | | WM/EncodingSettings | ENCODER_SETTINGS | +| ENCODINGTIME | TDEN | IDIT | | | WM/EncodingTime | DATE_ENCODED | +| ENGINEER | | | \----:com.apple.iTunes:ENGINEER | | | | +| FILETYPE | TFLT | | | | | | +| FILEWEBPAGE | WOAF | | | | WM/AudioFileURL | | +| GAPLESSPLAYBACK | | | pgap | | | | +| GENRE | TCON | IGNR | ©gen | | WM/Genre | | +| GROUPING | GRP1 | | ©grp | | | | +| INITIALKEY | TKEY | | | | WM/InitialKey | INITIAL_KEY | +| INVOLVEDPEOPLE | TIPL | | | | | | +| ISRC | TSRC | ISRC | \----:com.apple.iTunes:ISRC | | WM/ISRC | | +| LABEL | TPUB | IPUB | \----:com.apple.iTunes:LABEL | | WM/Publisher | LABEL_CODE | +| LANGUAGE | TLAN | ILNG | \----:com.apple.iTunes:LANGUAGE | | WM/Language | | +| LENGTH | TLEN | | | | | | +| LICENSE | | | \----:com.apple.iTunes:LICENSE | | | | +| LYRICIST | TEXT | IWRI | \----:com.apple.iTunes:LYRICIST | | WM/Writer | | +| LYRICS | USLT | | ©lyr | | WM/Lyrics | | +| MEDIA | TMED | IMED | \----:com.apple.iTunes:MEDIA | | WM/Media | ORIGINAL_MEDIA_TYPE | +| MIXER | | | \----:com.apple.iTunes:MIXER | | | | +| MOOD | TMOO | | \----:com.apple.iTunes:MOOD | | WM/Mood | | +| MOVEMENTCOUNT | | | ©mvc | | | | +| MOVEMENTNAME | MVNM | | ©mvn | | | | +| MOVEMENTNUMBER | MVIN | | ©mvi | | | | +| MUSICBRAINZ_ALBUMID | | | \----:com.apple.iTunes:MusicBrainz Album Id | | MusicBrainz/Album Id | MUSICBRAINZ_ALBUMID/50 | +| MUSICBRAINZ_ALBUMARTISTID | | | \----:com.apple.iTunes:MusicBrainz Album Artist Id | | MusicBrainz/Album Artist Id | MUSICBRAINZ_ALBUMARTISTID/50 | +| MUSICBRAINZ_ARTISTID | | | \----:com.apple.iTunes:MusicBrainz Artist Id | | MusicBrainz/Artist Id | | +| MUSICBRAINZ_RELEASEGROUPID | | | \----:com.apple.iTunes:MusicBrainz Release Group Id | | MusicBrainz/Release Group Id | MUSICBRAINZ_RELEASEGROUPID/50 | +| MUSICBRAINZ_RELEASETRACKID | | | \----:com.apple.iTunes:MusicBrainz Release Track Id | | MusicBrainz/Release Track Id | | +| MUSICBRAINZ_TRACKID | | | \----:com.apple.iTunes:MusicBrainz Track Id | | MusicBrainz/Track Id | | +| MUSICBRAINZ_WORKID | | | \----:com.apple.iTunes:MusicBrainz Work Id | | MusicBrainz/Work Id | | +| MUSICIANCREDITS | TMCL | | | | | | +| MUSICIP_PUID | | | | | MusicIP/PUID | | +| ORIGINALALBUM | TOAL | | | | WM/OriginalAlbumTitle | | +| ORIGINALARTIST | TOPE | | | | WM/OriginalArtist | | +| ORIGINALDATE | TDOR | | \----:com.apple.iTunes:ORIGINALDATE | | WM/OriginalReleaseYear | | +| ORIGINALFILENAME | TOFN | | | | WM/OriginalFilename | | +| ORIGINALLYRICIST | TOLY | | | | WM/OriginalLyricist | | +| OWNER | TOWN | | ownr | | | PURCHASE_OWNER | +| PAYMENTWEBPAGE | WPAY | | | | | | +| PERFORMER | | ISTR | | | | | +| PLAYLISTDELAY | TDLY | | | | | | +| PODCAST | PCST | | pcst | | | | +| PODCASTCATEGORY | TCAT | | catg | | | | +| PODCASTDESC | TDES | | desc | | | | +| PODCASTID | TGID | | egid | | | | +| PODCASTURL | WFED | | purl | | | | +| PRODUCEDNOTICE | TPRO | | | | | | +| PRODUCER | | | \----:com.apple.iTunes:PRODUCER | | WM/Producer | | +| PUBLISHERWEBPAGE | WPUB | | | | | | +| RADIOSTATION | TRSN | | | | | | +| RADIOSTATIONOWNER | TRSO | | | | | | +| RADIOSTATIONWEBPAGE | WORS | | | | | | +| RELEASECOUNTRY | | ICNT | \----:com.apple.iTunes:MusicBrainz Album Release Country | | MusicBrainz/Album Release Country | | +| RELEASEDATE | TDRL | | \----:com.apple.iTunes:RELEASEDATE | | | DATE_RELEASED/30 | +| RELEASESTATUS | | | \----:com.apple.iTunes:MusicBrainz Album Status | MUSICBRAINZ_ALBUMSTATUS | MusicBrainz/Album Status | | +| RELEASETYPE | | | \----:com.apple.iTunes:MusicBrainz Album Type | MUSICBRAINZ_ALBUMTYPE | MusicBrainz/Album Type | | +| REMIXER | TPE4 | IEDT | \----:com.apple.iTunes:REMIXER | MIXARTIST | WM/ModifiedBy | REMIXED_BY | +| SCRIPT | | | \----:com.apple.iTunes:SCRIPT | | WM/Script | | +| SHOWSORT | | | sosn | | | | +| SHOWWORKMOVEMENT | | | shwm | | | | +| SUBTITLE | TIT3 | | \----:com.apple.iTunes:SUBTITLE | | WM/SubTitle | | +| TAGGINGDATE | TDTG | | | | | DATE_TAGGED | +| TITLE | TIT2 | INAM | ©nam | | | | +| TITLESORT | TSOT | | sonm | | WM/TitleSortOrder | | +| TRACKNUMBER | TRCK | IPRT | trkn | TRACK | WM/TrackNumber | PART_NUMBER/30 | +| TRACKTOTAL | | | | | | TOTAL_PARTS/30 | +| TVEPISODE | | | tves | | | | +| TVEPISODEID | | | tven | | | | +| TVNETWORK | | | tvnn | | | | +| TVSEASON | | | tvsn | | | | +| TVSHOW | | | tvsh | | | | +| URL | WXXX | | | | | | +| WORK | TIT1 | | ©wrk | | WM/ContentGroupDescription | | */ From d2bf7e72d2e753bc2c425b0dbd764b96c7283790 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Thu, 4 Sep 2025 15:58:02 +0200 Subject: [PATCH 26/31] Unit tests for Matroska --- tests/data/no-tags.mka | Bin 0 -> 12390 bytes tests/data/no-tags.webm | Bin 0 -> 352 bytes tests/data/optimized.mkv | Bin 0 -> 3341 bytes tests/data/tags-before-cues.mkv | Bin 0 -> 3412 bytes tests/test_matroska.cpp | 980 +++++++++++++++++++++++++++++++- tests/test_sizes.cpp | 10 +- 6 files changed, 984 insertions(+), 6 deletions(-) create mode 100644 tests/data/no-tags.mka create mode 100644 tests/data/no-tags.webm create mode 100644 tests/data/optimized.mkv create mode 100644 tests/data/tags-before-cues.mkv diff --git a/tests/data/no-tags.mka b/tests/data/no-tags.mka new file mode 100644 index 0000000000000000000000000000000000000000..b69ed2c435f73b6a468e9322df9b638686c31fba GIT binary patch literal 12390 zcmeI2XH*kipspu{76ODG1vNnEh@lILLg-C`bO8w+q$)+wPYAt*-Vu~0C`C|+0xE=F zrArY|X-Y4Gg_;9?=bRts&t2!|z1L^0DZ9+fp6AV4lkCZ9waYzvt&I&p__}XgRQ$8%1=UE zT3iMtiaM_Nud$v~%85&uDi^rB`nfyYadJTU$xDl4P(p%Q9xgsEHa-r5!YEnk^R_NN zs?kwO!ziVb!Km6q!8$=JCQeg;cTu7Mf3rhw@hjW5#MkD5Sz{w7_tg0O-B>WlyDiGw zp|JV>gic0iFqoH=6YA?>9}%r=t%p<7xS%V=Hy0WPiq9P2CFP#9%tVvIG{jVa$HCwe z6G?9ps)K>oF~^#>LfEu7gCYN^g=O!5YvgbLzXtvq_-o*=fxia+8u)AAuYtb?{u=me z;IDzd2L2lOYv8YezXtyAFmO)gm9NF@w_u=9k1H4eB50KJ8_A8wQ1%n;|4+A|<>Bag z+=uCSK>z?389@92^#{x!l>P> z>#0eJ%gf&Zm7WYyOalPe2xutk8UPSZ#=+o9|Ns7!-oF8XhhZjZXgpXR_;{ue4q^tG z>({*^fTSOZyp}*f0O~vdX-{N@w%ESBt;`FhMWB?(T#=EH5hvbg{^t%Y(^ZP~vE`-X zMl=%>W%PIpnmL950ANTergv?_7~`ayyc$>K!>?}x{{g3WXGbKCazGF zI<2Mx*Fh#&AAI25dfC%>bgN*yf`f9XHa{$ZQRb^Z`cU>cN8L8Hi!F#kQ8VN(uuWFi zEU;w=qQFA(pdq)+V(E@QG0G?aV5alON9CMtt3RWmijchkc4EuYbMzUjjOY|bF%(7Z z+!PgdH7t_v-!{o}Zag;R<6TKP+N1om@~un$=8^P-N{gG^@p(J*1rf`K=|uH4HStwZ z_LJ>la7c(^|GC=$pwYa1_$>;Q&;$~{How$KfiEo(PBUIyWu!|3JC}oL!h^MM_6Un# z9KZ!Kw5hUaQ{gW<$CyXb(?M~oW=XdCjIgPEy8f^?2`gE}ab?+=wdvz~F&mUe*`ANC zC@!xrQ@*W)zgUEvy=zsN36_O~yc80MIw@sD0_4hcP z5nE-@YU?s&^L^PFFtZ!KqKoBObZ8gMcUqXSNqzgeLYzxqXegE6Qk?d|nNj$&o77ME z0#Wit?q8w@z6{Q>yOdK=7ey20kMIi(*=Rwk5LU2KPd8b^Y~gt0lFo zrM{2Ha$CmrcU(NbmF)vmga#_+)kCV)CY||~bB^z+C(_MBgh~33E|EN`-4iNUXe@T; zlQhHlFX?0y!OY|DQ1(XGe8L$x);%SDh*r?O+E!b$yVK1l63mCds)(rAv4qUYc1Tf^ z5ws@>F~T8Ditky=xs^0DVfFatfQ0iP>1%J@wFHSmOU1|$6o@LdCJJnJeYZ(R#!=)R zwAXZJ8B$`Wg3~`2!t!j%M_rz#D{<;wys)<4E84w@eVre5!uB2`9jGU`QK#?sIy$q) zL_GD*Qz(3dk$=uAnO~Q)1tlk`QJg3{A-+dB?x~@^u-t(9f=-(e!_u^Pr3;r{5IYWx zqW#~eJM{2gx{|qCJn(cfj~w%GBIx}CKNot+Si^GMp*5xVX?keJk2#A&;Di6IJ^{ZM zc-wr0Ep3F|CIZDnjNu3i&PU$5h>V7p+UCy?CMBsbhkBfY4MC+wT@l`-T;iI_;00!p zlqcAw+(1=0?uU&tQ>$kw2 zs>g@Yg#o|zxdC3B+if8LOuTFp_OzTh%j~MW%|NHqR*}RI;D&G)@3VaKc6~@aV{-oI zm_QtgOC#9YJ2tV>@_TCfqWHQGU!85^e1DmAWU4l(pxUjF^{t6FT4Rnj_zON6k5IC4 zDLqMuo(P${qMK4Fio@E${A@x66U4S;J&J0S8Dh(8c8JAzFHL+E9zPIq`E*9G3lpf{ ztOF7dR>JPk2DQfs6IpL`t05^)=?Wy`llyic*z2%+)QN*El@El7m2ELmb5rp?ia(dH z-*R$V|CQT7Dy$`ZXMh)ZyCoi(L>F&?Z5Qqw-*ld7gpzs8Ko|&D99+D?F`gOtDG=?s4Z$$@a0*{tj&GtEGzJA z2$0y_oog6G*$KDGSVtI7^K)8@C=CLcP0(x&=S4OHA&2W+s zJTA0dMwhit#7i~g)@e@xIMWY^IFtP|GKEVhx11IOQf>;)tA#6l3+6smz^;J+@^z#dr%lP#)D$4D!{BG`~A1hzQYUBq~ z)s?YPUSxCL0d8@Y%CI8&1-*KfHX*(Spab|)Z@^mOY2kD)T_GCS+LoQL-y^aWJLeZN ziw>316LJ9-X9BF}hv?p|Glw)wJlkI~+yy05n~1-TJZ<`(rGqp_ERT;*{+ zibXdQqtT<&UvHPbjQXO=jl5#j&vR{Y@tbyaZg(-WXpHEah8vS67W6@pV}oy6skmdR z<1g8Z4GdL(R}PI9C$NFFp+X7fhF7Dj_|YT9wjK|!_&ugQc%&wkYQVUf^6er&53%ny zy?eSkU(ia$@dJ&rFOXl~Hm=~XLMh8J2GvZZE-;3k6@HJAln_d74*`LN20~MJP6H*Z#d7Jhlg4VlhbRY;@db^LMDN;e{ojI8^RF#6*9Nn+n1aZYK_w;!>_YPtLdTfx)A|#-AJ$+0zQm zbVnXU;=W%#Y>NAMUU&Jro`U}H+uyjR#I>g*1>n1Q;WCbSm0(+|pvK+oXwzYDV`~0M zRa;7qVQFgq-F>g-{-c~u9CJtq-0)qA#kzXJ;rdnv06@_o{g%|5OO_GG0NShP*}`S{D*3La>$&b82wB;qy3(YmUs2K?wx$6Zqi&FynrYG zgr7e3tgv7yQvIjfFwp{~7F66{=300?-?X80iY;!iao8MDzzA#6ckSCC0~|$x;he$O z!ytYwVb3AILtmCQS%{97?;DUB`kc<%N)_A`OY$h8LY^_ll;c*M)u7z^A3hI{wSsno z23`w}>q5|ep|(zaCZ3Yx2d!;VA{lUdti&^`N7S6nR9Lb-r%DRS&)WaNs#A9ZRj2Gn z0cxm{$ZA+~IqVBk`Et4|Jn^nj#>)Lss})?ty{x#Kj$NCd6V}!J^k>$OpN)G&=^5c_ zyKCRAo+m*S_68Lwx#Hu>k={w~D_H1&JRuIWJd5@!i*llz_@hFx*R<1;eP*hYgj9vZ zD!fazn~_OJ`D|kQrGn=3lo2ay&qR~PG25NHKY!0Bs)VwWQ>e!t zj0T0#oFpa008y3c=ZfG~!p?~@U<6W#Tg@#}^zBnNn-YQ?qd96M5|eJY3vyog_uaic zfh@;Y=dn)KlB{fZV;09aty5k|m_Dd~mlr_!ai*v)v?N=2#>Q=MQhSF^2@qfs_DM1> zDK&+$O@2-y3279gK-2?_FbKd_JC1?Kg}yXrLl1=*cY0PV_6VN|n0j6!>^5Kaoxrh< zD%#mhXk(COljJl@wf;09T-F??{eU3G>i!0byOP9>GvoCqv&F%qiHFzbDuiGHO2W>8 zpItP3`LF+|`>kh}rN8%!MSCia&C|DBMCB1$6oozt6|4JT3}u;e5p;_qi(Y=_Cpdw* zC2O(EN*_-W;O}@bn$CaWa?SE z$)bg82dWg&;l_P@T;;$y3T6AUCACqlSC5`0XMY}mNCYfw?$YiU`WqFwQzP-T)tR;} z6~-qCDFP5U|Cp;V*C&-0n4W#xb@-a1lKWdd&0jEA=b!LL#D07LOQrd$L(cYv8(0>| zhbg7o=^)Zp{7rE;^Vtf@qT+6iT4N{pK<8`dor#N=cvbXiq!##?Ii;X{P`E56gy2of zXjY2ovLbmMP3wl`R85q*R|~o@-j~st553In5Wl6y!M4FB)GTgV{8J(6gZ<5(eUbabYkOPNK2nlfCvCJjC+EvsFJdS8I)`~ zr`41RpMK*6wx_n`0w*cvZ|dDkk&eDIT4q!rPM~oXU5k_cR(vFWG<0iY!mPgfvR^AXGfBk#o0G$0n^3rJ@_^RhP+kV|BYX8)j&v({g-S17KCPR+^ z&E3%}CkdGlAZMzOO>;sg!dCj+(x}zoljgfddyu(ld zijfZiJ8Po-ZRR(f`U_@!W=G=Y^aFFfnAtjt3>*xLgEQ@77o(<6Rg|0gJ*52noV_(W zG;vpRsaSubQK4}<&GGHXII>;xpA__|tTc=L-6cGE=9`czAGNP7hDsskHjsI>^#^O> zs+@QNE5_nH5t|dnj~RN&?kgJ7yULizN(hyfFV5+0qlAtUlpRRH4T-T#sJ{z6H71r@J0Qm-fKvYgd2kEhlf`=>|= zsU=O^NkSe3h?wTEjymgsVsmM|BA;BW|CmiV(rZhMela%(DfDi(bx%WFuqXV=c3rqE zTI1^54wl;10QmiC1!3H1sZZQj3WH4JNPVQfm)hxd4 zJlZE3_-go(XGZtupS_GB@#B%C^gI{R5v9EU#l}UUZ;fBc;MSbcybv=K049i}$6pP7 zfwII0pLXWON2%9vwe69u{NLuki~HV^U~3*;+?nV*X4`@J&g^eL$$F~OHW1ayG$Kj) zxKY!+z+vy?F;RN>EqjmKOK~lNE{S>#itk!9b-O-uB-<8R4}2>NFYr`eS*KI?4# zaHlK;)xu|A242pgZHsbjjMt-%&0crk6k4njI43RdPQ%I?)lzO*&`7aRMD3Mlc|3u= zo<%K^(_N}C+1nUz=irue`Q7X>@>)4Zwy5NTpY zs<+)lBX+ zu9RYH*-sLRih`hI4SNK{EU^Wp&ujz)-KzyXDRYwth&z}(nyev ztU|;zihVbAul_jS8R5Y8kDDBASmo6_pR4;_*fy-j%1kc^cZkd#Rj#*gP*w~jjUMgz z;Hp&L%3UT8B?$0}UfCl9uC-7BabPhK37i7a*IW+YoVHe9xA#dDac_Of6{p52+1*?i z+AS-}p?#lnv@Ek0TU}8cJ}RdDiOX`%0FD-t27YEh47e_a^JyRdS5gIg!I1sCE&*Lb z&E$2!K|MG{^AVL5_aVQk%gS}YkimX^=kSKn9VH~yR5${$hzzOzb+F2(Gm(1HxR`j( z$2MB{GizFVDGw{7*+%0fZK@1!;b%SBAy3jT(%>IvY~GYdQ`HrDAYE^x=5DwZ^&HVO zpOePyAWss?BtWh!nGVBM#jwHMQciTVaFj3BeTF6&d8S-wGx}BXd4mO41D_Y(hczVb zJ<>Gkj_9^Cd{*MY#@gsu0^@kuvnSwb(w9-WOA37iTwd~BN4Vk7GpczCI8J{jCu4E(O{;hF*w+XUD+4>cqXB873DFK9C&^4_V4%7NSFtnN5@l+Oi6u z;ua?KyBy42_YoFN6OO2-;iBUCY`$qSImRZ`5z>P#r_P=0YAr?HHynFLPb+#ab)$c+ zF?VU#$;k3@^$c&Ku_HWIZ2bd$5^G)>M5ApzjmMOV6Db`2YDkqcfn3oxXo515KT99! z=4`+q@`-c{$%C!uv~qSZ6<&EG*6!1lVd6ltrce%^e_kkfDha=w@EUec*X(t7DGaAF zJHK^RZvG^p3IgPU^dOfyyOIh>LB-~)BoV!T?LFFEg8R|(QUvWM-6aGk4sV|>5NM;$ z26idS-MyP@SwzxAnLTnNF{TE?Cmd+e&e^Jptz!DF^G`dIEscJ?8=Ac9>HR3yE}c7l zV!LW>bor>V?7@S&t)MM|ICl!S&&R3dug@#z;)5ttpO%Aavi(fLj;CtVSG>-mmQV~rz$V*~i57abZ;S{dd6VmZW9rd#d`t(B?wM`uDdc;fC>7n5p%6NLuQ8vCV zh-;;0xm?x0p^D62l(LpJk>;WNfSP@X5hZDS{Zoaqzq+u%490GA^qkP}GgR<=tr4+j zL2}&HRAf9sU#25D;2_K>NG`O(Vt3MPCF$Yx`n}!mq-dFEog5z?X5I~&_Own|h`9RR z^X`4M)P<9TS_qH?{XupYJY3HXCRm;caFG--yHf=4dWmD1>?W{L^lri-;_CL}R4=>| z27@|v{KI%11t0;K)ui~x?nxkOrjOn1$A`GEyp4E9d)+Rga;xKwJBUSJXoNI2gTGac zp8<2*+{KR1F-EKvz0f+KTsoJJ9Wcmlo63B+#i6t9Fab@wQVW2!x_EK#6;nKJdqK4F z#=S&mOdM%skz012zT&XR7*!rd4G*bHX~vb_zR$_^mrXEnSbj6NKtpT}_l;2535Uo4-|k&Pr-IYW?@w z4$@IYnu(MxH2{E_LBUEEDrrJ#SAR46-zvVk)O)ynVe34-FSiTr(}$-FX0T zFafe+qt^9}&HPRdE}bp*4Gavv4gEQpNg(Ix8t54Vp+UG~Us*v#YF@HJnW3Jcsh$DI zDGS%lzuw4Le6+oZ%W0xi)qzpOaETD091Wdt2}B ztatX#zEmQ<=3+Yy0XIMhX(*wts0vYIL|X)raG;{HiiG$FDxji~stWN-LbMVhDui!l z?=I;d{K?6>oA+kk%zN+q-kb65h1ZwAvD&ildSut|ql+GkLLsNv@Itd^_*k=NQ)$lL z`=?t*pNM|D+Io1)>no#|9$#%WUdwJReXq5l_-v(gq50zIrBkb|^64*bhHm5Jb5o@Y zk0wp6T)gj}8=(Bisv)a1p$i=Jq z7|jLm&S*F z`PS^W-}tD1;fI?})75}x`di>18-mA^z(Pnaf z>A*vy>(t-9UpupY zGbZV9Qk&i|bABV)I(qtmv-Opgjpxsk*R-AbTDN%%EZ&z-{xp2PJa|_kxqjPN^1atO zpF6id^@sH@+_wAom-avXpCdneXKXgp-$%BUYmRG?^uWN~gpzEkZ`Z-Sj$fNSba3~` z2uTBqRQk}t{$5gcqC=4JgLQjSu+5(1u0?kt2M$iKAqwXIE+|GcVrMuQdfv6rjEfT<#q%>kur+~SdS6#VxD8QXKC4C zD#p;f!4%te_o32tOytQR(&RojYf-LOr9nottAIJP+}LdysP!rUI2baD+#t;V|xl|0#hY-k#(mIQzl~O zau&J`s`C+E|EAos<2sa8;p$}e8Te=?87QDJR+3I7tw5(FAnXb$T`r{(%h%T_dPJes zKFI>7nHx2b??5(UxOVon$6`9y+XfE?NGE_$yLERq%hQ3VAj<|Z!|pINb7ok9rjpA5 zd@^kcF8~etF$`qH1C@Y?MUu{hPNd2`mQ=5S-~=HDi*Q^VbfH>h{DGR z#NfWII*f7=RS|Md4hx^7@@uF}ogyr9O!S^|NT%(W^Pn|C0ARKv7CZvybNvHq;P|nX zrX8)9>%dU88Bs_1n@r8tL0qBxN|ftE(JB7mbm59H2ptbP@YR#uUm9)wSt^qn7&>+xsQ4-*$u4*!_PIG6fo zg{oyKOi(V|K`TcQN=HIllf2!5K$ebLGL+f>m~#bF#J}XVO9kZku=Ajv7Hst)QWL3l31w zvYKk#9rr+eouE#$?N+D+;)p_}M`e@OO(a?kWnHJV;&41Z0Jy%DRE7C8CbO=lE}~35 z9)MKNxZyf&QZv^Rq19IAN4Kv?`F->93G@16&t5xUSYFKUpBS1f?iw(t~6B#!JOgexWt)ib%?DnwRJ7wXYYJ zPv(av3lB{c_7;Xpg*@if`&s>`@a7+ziRJG$6N$NxZ%baRfBMP4T>Rb4wQKe#fBlb@ z&yK%*c8_u8I~?!Ix!a7d-1PpRd}{e@Gclgn(3AYtEf2KPKkzQUUHrn_-@-FXPs|T4 fpJ^txC2mb5uWVhNzu&oh>iN$7<9~ub|M5+LZ4^f&5q*4g) z&H8-qAN>y*J}esDKUYdv_}olx9XTE?k1QX~KR>^2__<>KZ2k1e^4Eb{Kk>p;{_Ot5x|saf#Ooy> zKYDjeelov|vp28(OT3WvkXi5l@Pk(U?ZW)rR~~rlGnZdFaLYj7m!CUSW`SE!Y>JP} zJ-(Awi#^+t>8;6>Z9mw5YzXECaE_#sy~z|w+vzU5E8T0``pl8Rj@QTfe|~lL^xK~b z&Ys_JILnqgubzD3(Curj?4hN;FUAg_@1I+HVgBryjgPz#JJ|WJ%k_l*>C(YO|DS&U z#^s-#s9Sfwdg4GOa2Jm3esp9kw_|9_aDLa8q1^7l1NFpRZy%WNC_G|aIeo5v?zQ@n z$#X|1U#ll3U#j0Qxm>?~@`o@!58ofuJDg{Rzmw=7u=5eK`~B}&AN>3M_buy|>xh-G zHuNQKN?f~!0&7rW4Sw$b*3r&8V3H0bjB4A=sr6*z$kV&U#y8KcKXr=y#^~YCnmg_U zjgMp#KUS|4`tGq3ots7z*I!$C?%Mp!Z#!STZO5;dC(r!nYd`s5bT-}FLpBvE!gEP# z+qQcNC0)s$?R$2LpfbC6&yK-ClCqQOWNPo8-fmJ5(O#h81#&}4&^4hTj&!*LtU(#p zlqi)W$VaM+!q0tVlXEW_sd$laq~bG7@y%p7K;d!}h1<7mK?{;nm4G9|NXS5MCr(%q zZYBkf-L;Jr74tcb%8D~rXS&Et7?MEJ(1%^)LHyEzLQ-qt6Hk_?$E)1Sq|s`Zhfz6` zvWW=7N<TaW>P)(4ORC9KTxU{SduLPK-N1>s@|eRjHi=}!JZg;4K$=yla2*(z z!B-jyu3)uF*`);+n2HxO-Dn8&JUK%H8Bj+CMX7uiIL1=NL(U>v27kEHgarYgEf|f8 zi?6bB!GfYBWv0upBCsOvhf;wZ9;=~_40%9HG9nXYNBO#hnP>%m>fdERC9zU1`r!?DB+84qLZ1k9`L<5WZPLg8rQ@`5Lwsf@iHvc;q^ zmq~-i<`jepkgB-{Q5RKEWim!CXUeNl6A!ieOSxN$>rj`aXM)*d!()tO00EO@q)aGj z5qL;JhCQjZ$F)YXuqub}h?Lvh$puU^FRDSjLtTs0wY8@?meZN;X7WG*!U>7M)mke% zh3QZhA<8y!RBNc5jH8OcDQ41;d@}7wKLi-`!Z6ek4>14{i!_~4BEoWyDeN^sh)^-; zNtsj?F@6cb%GVbc`T2!#(LfB_Fm*kRB_s0zh7*(U=Im0y9%G$8^Z;Xv;xC^B7& zF%Pf?7a(CJ1Qu8Xn9uZX!+{85Edx7REmR?g*k)uE%HLr)TLs`s(^sNQ4-_5Z2X>d9 zRG|_+@BpuYE}Hga*my?}umf?*O8N}o0V}q~G?EaSHo73ANczBX1!RiV=!^(lIfF7D z6!>P_6tGwE8L+Ti%`gQ=G4|gyMOG-Gm*@hNceNs*SAt_$FVNhOhE5j~&AyVtc)&40 zr$ZWs1};cJx~Ydv#xr4Fney>ip&`@S=qONF=|as;mb81~?u4gD3$?nMl? zLzc-Y9zjTyT?OaG$^bV&!Zqv_O1KJJ7Ul%Xg$roqP=s1&;8rwmb|3)D3R^QMv;Q&Y zNru2~*MmfR9O}r4BzB<~^$;+~>xW3wqnfjR6>{1fLz|_LJJ3e2Km|OATM#w`oW)Eu z`EtfY(uEG(RBxFb)xsEBVGVl=Y=Eg{6>Quq<^l9gfL5Aqt3owo9K68rU^IEvfk5L> zS4~KZg2Uwl5^hc=w;iWVNK&kP9%*83AK=g`bRj)(${H*} z`Jx-JHPjOX0&=kmp*g(#_7}BYb}k-vR_}nNOW(~kma>!M{S(8xMzf1g+0an&lT4+t z`@xglhgZbxJlq|1-#?s-^W`_|x0f!R$Tb%F$0vsK6WPUue$1g>l`LI)HrF_u&E*Hj zhsWZT4`;b{lrWc|cEMA)|L}NjaAMbZc5x{y@RXFQUF1xzv6vej+?CCN#D_1-?PAAr zje|RKqvN^k;=vs}P`rKeB&?C292w~!e>l6iI4FIe2T{8K__LIMcy!lTemI|9oFDUK z+qBlt$Bw5 znzJnH?oEjos}G(0^NYWlxpuAe*H)Jj>yhI(M7>%^Pm~(F2Wd*R3&- zw$^ndesSxA^Qo8pi|-A8b?&d~=#k?KeU0OFYZFd?+)7;8cwyo5;^I@UeCaXyjXCSe c9R8(wa`}mu>(=?l;Q!O#_uus7JN3^00y1W!FaQ7m literal 0 HcmV?d00001 diff --git a/tests/test_matroska.cpp b/tests/test_matroska.cpp index d1fc3c0a..ca1cd0e0 100644 --- a/tests/test_matroska.cpp +++ b/tests/test_matroska.cpp @@ -1,10 +1,136 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +/* + * # Test files + * + * no-tags.mka was created from a small mp3 file using + * mkvmerge -o small.mka small.mp3 + * The tags were removed afterwards. + * + * tags-before-cues.mkv was created using + * ffmpeg -t 0.1 -s qcif -f rawvideo -pix_fmt rgb24 -r 25 \ + * -i /dev/zero zero_second.mkv + * The tags were added using Handbrake, which places them before the Cluster + * and Cues elements. The tags were rewritten to conform to the specification. + * + * optimized.mpk is the same as tags-before-cues.mkv, but optimized using + * mkclean --keep-cues --optimize tags-before-cues.mkv + * This is done to have a segment with the size encoded with fewer than 8 bytes, + * so the resizing of the segment can be verified by adding a large attachment. + * + * no-tags.webm was created using + * ffmpeg -f lavfi -i color=c=blue:s=64x64 -frames:v 1 \ + * -pix_fmt yuv420p frame.yuv + * vpxenc --codec=vp8 --width=64 --height=64 --fps=10/1 \ + * --end-usage=cq --cq-level=4 --lag-in-frames=0 --auto-alt-ref=0 \ + * --profile=0 --target-bitrate=1000 -o onems.webm frame.yuv + * Then the EMBL void element after the seek head was transformed to have + * the content size encoded with 8 bytes instead of 1 byte to have it in the + * same format as used here. + * + * # File validation + * + * The files are read with read style "Accurate" to verify the segment positions + * in seek head and cues. + * + * # Manual testing + * + * The integrity of Matroska files modified with TagLib can be verified with + * mkvalidator v0.6.0. For inspection of files, mkvinfo and mkvtoolnix-gui + * are useful. + * + * All tags of Matroska files can be read and written using properties and + * complex properties, so tagwriter and tagreader are sufficient for testing. + * + * - Set standard tags + * examples/tagwriter -t 'Track Title' -a 'Artist Name' -A 'Album Title' \ + * -c 'Comment' -g 'Genre' -y 2025 -T 1 test.mka + * + * To remove standard tags, set them to an empty string or 0 for numeric tags. + * Using the property interface, the properties listed in propertymapping.dox + * and arbitrary string tags with track target level can be written. + * + * - Insert property + * examples/tagwriter -I 'ALBUMARTIST' 'Album Artist' test.mka + * - Replace property + * examples/tagwriter -R 'ALBUMARTIST' 'Other Artist' test.mka + * - Delete property + * examples/tagwriter -D 'ALBUMARTIST' test.mka + * + * Pictures can be attached with a description + * examples/tagwriter -p file.jpg 'Picture description' test.mka + * + * Alternatively, they can be set using complex properties. A complex property + * can be set with + * + * examples/tagwriter -C FILE + * + * The second parameter can be set to "" to delete complex properties with the + * given key. To set complex property values, a simple shorthand syntax can be + * used. Multiple maps are separated by ';', values within a map are assigned + * with key=value and separated by a ','. Types are automatically detected, + * double quotes can be used to force a string. A ByteVector can be constructed + * from the contents of a file with the path given after "file://". There is + * no escape character, but hex codes are supported, e.g. "\x2C" to include a ',' + * and \x3B to include a ';'. + * + * - Set an attached file in a Matroska file: + * examples/tagwriter -C file.bin \ + * 'fileName=file.bin,data=file://file.bin,mimeType=application/octet-stream' \ + * file.mka + * + * - Set simple tag with target type in a Matroska file: + * examples/tagwriter -C PART_NUMBER \ + * name=PART_NUMBER,targetTypeValue=20,value=2 file.mka + * + * - Set simple tag with binary value in a Matroska file: + * examples/tagwriter -C BINARY \ + * name=BINARY,data=file://file.bin,targetTypeValue=60 file.mka + * + * # Test coverage + * + * Not yet covered by the unit tests are: + * - MkCueDuration, MkCueBlockNumber, MkCueCodecState, MkCueReference, MkCueRefTime, + * MkBitDepth and the methods handling these elements because none of the test + * files has such elements, + * - some unused functions like EBML::parseVINT(), EBML::FloatElement::render(), + * - some error cases because they never occur with the unit tests. + */ + #include #include #include "tbytevectorlist.h" +#include "tbytevectorstream.h" #include "tpropertymap.h" -#include "tag.h" #include "matroskafile.h" +#include "matroskatag.h" +#include "matroskaattachments.h" +#include "matroskaattachedfile.h" +#include "matroskasimpletag.h" #include "plainfile.h" #include #include "utils.h" @@ -15,14 +141,860 @@ using namespace TagLib; class TestMatroska : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestMatroska); - CPPUNIT_TEST(testTags); + CPPUNIT_TEST(testPropertiesMka); + CPPUNIT_TEST(testPropertiesMkv); + CPPUNIT_TEST(testPropertiesWebm); + CPPUNIT_TEST(testSimpleTagsAndAttachments); + CPPUNIT_TEST(testAddRemoveTagsAttachments); + CPPUNIT_TEST(testTagsWebm); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST(testPropertyInterface); + CPPUNIT_TEST(testComplexProperties); + CPPUNIT_TEST(testOpenInvalid); + CPPUNIT_TEST(testSegmentSizeChange); CPPUNIT_TEST_SUITE_END(); public: - void testTags() + void testPropertiesMka() { - // TODO implement + Matroska::File f(TEST_FILE_PATH_C("no-tags.mka")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(444, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(223, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(String("matroska"), f.audioProperties()->docType()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->docTypeVersion()); + CPPUNIT_ASSERT_EQUAL(String("A_MPEG/L3"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String(""), f.audioProperties()->title()); } + + void testPropertiesMkv() + { + Matroska::File f(TEST_FILE_PATH_C("tags-before-cues.mkv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(120, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(227, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(String("matroska"), f.audioProperties()->docType()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->docTypeVersion()); + CPPUNIT_ASSERT_EQUAL(String(""), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("handbrake"), f.audioProperties()->title()); + } + + void testPropertiesWebm() + { + Matroska::File f(TEST_FILE_PATH_C("no-tags.webm")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(2816, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(String("webm"), f.audioProperties()->docType()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->docTypeVersion()); + CPPUNIT_ASSERT_EQUAL(String(""), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String(""), f.audioProperties()->title()); + + Matroska::File noProps(TEST_FILE_PATH_C("no-tags.webm"), false); + CPPUNIT_ASSERT(!noProps.audioProperties()); + } + + void testSimpleTagsAndAttachments() + { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + auto tag = f.tag(true); + CPPUNIT_ASSERT(tag->isEmpty()); + tag->addSimpleTag(Matroska::SimpleTag( + "Test Name 1", String("Test Value 1"), + Matroska::SimpleTag::TargetTypeValue::Track, "en")); + tag->addSimpleTag(Matroska::SimpleTag( + "Test Name 2", String("Test Value 2"), + Matroska::SimpleTag::TargetTypeValue::Album)); + tag->setTitle("Test title"); + tag->setArtist("Test artist"); + tag->setYear(1969); + auto attachments = f.attachments(true); + Matroska::AttachedFile attachedFile; + attachedFile.setFileName("cover.jpg"); + attachedFile.setMediaType("image/jpeg"); + attachedFile.setDescription("Cover"); + attachedFile.setData(ByteVector("JPEG data")); + attachedFile.setUID(5081000385627515072ULL); + attachments->addAttachedFile(attachedFile); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + auto attachments = f.attachments(false); + CPPUNIT_ASSERT(attachments); + CPPUNIT_ASSERT(!tag->isEmpty()); + + CPPUNIT_ASSERT_EQUAL(String("Test title"), tag->title()); + CPPUNIT_ASSERT_EQUAL(String("Test artist"), tag->artist()); + CPPUNIT_ASSERT_EQUAL(1969U, tag->year()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->album()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->comment()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->genre()); + CPPUNIT_ASSERT_EQUAL(0U, tag->track()); + + const auto &simpleTags = tag->simpleTagsList(); + CPPUNIT_ASSERT_EQUAL(5U, simpleTags.size()); + + CPPUNIT_ASSERT_EQUAL(String("en"), simpleTags[0].language()); + CPPUNIT_ASSERT_EQUAL(String("Test Name 1"), simpleTags[0].name()); + CPPUNIT_ASSERT_EQUAL(String("Test Value 1"), simpleTags[0].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[0].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[0].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[0].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[0].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[1].language()); + CPPUNIT_ASSERT_EQUAL(String("TITLE"), simpleTags[1].name()); + CPPUNIT_ASSERT_EQUAL(String("Test title"), simpleTags[1].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[1].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[1].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[1].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[1].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[1].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[2].language()); + CPPUNIT_ASSERT_EQUAL(String("ARTIST"), simpleTags[2].name()); + CPPUNIT_ASSERT_EQUAL(String("Test artist"), simpleTags[2].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[2].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[2].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[2].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[2].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[2].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[3].language()); + CPPUNIT_ASSERT_EQUAL(String("Test Name 2"), simpleTags[3].name()); + CPPUNIT_ASSERT_EQUAL(String("Test Value 2"), simpleTags[3].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[3].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[3].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Album, simpleTags[3].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[3].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[3].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[4].language()); + CPPUNIT_ASSERT_EQUAL(String("DATE_RELEASED"), simpleTags[4].name()); + CPPUNIT_ASSERT_EQUAL(String("1969"), simpleTags[4].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[4].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[4].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Album, simpleTags[4].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[4].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[4].type()); + + const auto &attachedFiles = attachments->attachedFileList(); + CPPUNIT_ASSERT_EQUAL(1U, attachedFiles.size()); + + CPPUNIT_ASSERT_EQUAL(String("Cover"), attachedFiles[0].description()); + CPPUNIT_ASSERT_EQUAL(String("cover.jpg"), attachedFiles[0].fileName()); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), attachedFiles[0].mediaType()); + CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), attachedFiles[0].data()); + CPPUNIT_ASSERT_EQUAL(5081000385627515072ULL, attachedFiles[0].uid()); + + PropertyMap expectedProps; + expectedProps["ARTIST"] = StringList("Test artist"); + expectedProps["DATE"] = StringList("1969"); + expectedProps["TEST NAME 1"] = StringList("Test Value 1"); + expectedProps["TITLE"] = StringList("Test title"); + expectedProps.addUnsupportedData("Test Name 2"); + auto props = f.properties(); + if (expectedProps != props) { + CPPUNIT_ASSERT_EQUAL(expectedProps.toString(), props.toString()); + } + CPPUNIT_ASSERT(expectedProps == props); + CPPUNIT_ASSERT(expectedProps == tag->properties()); + + auto keys = f.complexPropertyKeys(); + CPPUNIT_ASSERT_EQUAL(StringList({"Test Name 2", "PICTURE"}), keys); + auto pictures = f.complexProperties("PICTURE"); + CPPUNIT_ASSERT_EQUAL(1U, pictures.size()); + const VariantMap expectedPic { + {"data", ByteVector("JPEG data")}, + {"mimeType", "image/jpeg"}, + {"description", "Cover"}, + {"fileName", "cover.jpg"}, + {"uid", 5081000385627515072ULL} + }; + CPPUNIT_ASSERT(List({expectedPic}) == pictures); + const VariantMap expectedComplexProps { + {"defaultLanguage", true}, + {"language", "und"}, + {"name", "Test Name 2"}, + {"value", "Test Value 2"}, + {"targetTypeValue", 50}, + }; + auto complexProps = f.complexProperties("Test Name 2"); + CPPUNIT_ASSERT(List({expectedComplexProps}) == complexProps); + + tag->clearSimpleTags(); + attachments->clear(); + f.save(); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + + // Check if file without tags is same as original empty file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("no-tags.mka")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + + void testAddRemoveTagsAttachments() + { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + f.tag(true)->setComment("C"); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(String("C"), f.tag(false)->comment()); + f.tag()->setComment(""); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + f.attachments(true)->addAttachedFile(Matroska::AttachedFile()); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + const auto &attachedFiles = f.attachments(false)->attachedFileList(); + CPPUNIT_ASSERT_EQUAL(1U, attachedFiles.size()); + f.attachments()->removeAttachedFile(attachedFiles.front().uid()); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + } + + void testTagsWebm() + { + ScopedFileCopy copy("no-tags", ".webm"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + + f.setProperties(SimplePropertyMap{ + {"ARTIST", {"First artist", "second artist"}}, + {"", {"Invalid", "Ignored"}} + }); + f.tag(false)->addSimpleTag(Matroska::SimpleTag("", ByteVector("Not valid"))); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), false, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(!f.attachments(false)); + CPPUNIT_ASSERT_EQUAL(String("First artist"), tag->artist()); + CPPUNIT_ASSERT_EQUAL(StringList({"First artist", "second artist"}), f.properties().value("ARTIST")); + + tag->setAlbum("Album"); + tag->setTrack(5); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), false, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT_EQUAL(String("First artist"), tag->artist()); + CPPUNIT_ASSERT_EQUAL(String("Album"), tag->album()); + CPPUNIT_ASSERT_EQUAL(5U, tag->track()); + + tag->setArtist(""); + tag->removeSimpleTag("TITLE", Matroska::SimpleTag::TargetTypeValue::Album); + tag->setTrack(0); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), false, AudioProperties::Accurate); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + + // Check if file without tags is same as original empty file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("no-tags.webm")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + + void testRepeatedSave() + { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + String text = "01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789"; + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.save()); + f.tag(true)->setTitle(text.substr(0, 23)); + CPPUNIT_ASSERT(f.save()); + f.tag(true)->setTitle(text.substr(0, 5)); + CPPUNIT_ASSERT(f.save()); + f.tag(true)->setTitle(text); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(text, f.tag()->title()); + } + } + + void testPropertyInterface() + { + ScopedFileCopy copy("tags-before-cues", ".mkv"); + string newname = copy.fileName(); + + PropertyMap initialProps; + initialProps["ARTIST"] = StringList("Actors"); + initialProps["DATE"] = StringList("2023"); + initialProps["DESCRIPTION"] = StringList("Description"); + initialProps["DIRECTOR"] = StringList("Director"); + initialProps["ENCODEDBY"] = StringList("Lavf59.27.100"); + initialProps["GENRE"] = StringList("Genre"); + initialProps["SUMMARY"] = StringList("Comment"); + initialProps["SYNOPSIS"] = StringList("Plot"); + + const VariantMap initialComplexProps { + {"defaultLanguage", true}, + {"language", "und"}, + {"name", "DURATION"}, + {"value", "00:00:00.120000000"}, + {"trackUid", 9584013959154292683ULL}, + }; + + PropertyMap newProps; + newProps["ALBUM"] = StringList("Album"); + newProps["ALBUMARTIST"] = StringList("Album Artist"); + newProps["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + newProps["ALBUMSORT"] = StringList("Album Sort"); + newProps["ARTIST"] = StringList("Artist"); + newProps["ARTISTS"] = StringList("Artists"); + newProps["ARTISTSORT"] = StringList("Artist Sort"); + newProps["ASIN"] = StringList("ASIN"); + newProps["BARCODE"] = StringList("Barcode"); + newProps["CATALOGNUMBER"] = StringList("Catalog Number 1").append("Catalog Number 2"); + newProps["COMMENT"] = StringList("Comment"); + newProps["DATE"] = StringList("2021-01-10"); + newProps["DISCNUMBER"] = StringList("3"); + newProps["DISCTOTAL"] = StringList("5"); + newProps["DJMIXER"] = StringList("Mixed by"); + newProps["ENCODEDBY"] = StringList("Encoded by"); + newProps["ENCODING"] = StringList("Encoder settings"); + newProps["ENCODINGTIME"] = StringList("Date encoded"); + newProps["GENRE"] = StringList("Genre"); + newProps["INITIALKEY"] = StringList("Initial key"); + newProps["ISRC"] = StringList("UKAAA0500001"); + newProps["LABEL"] = StringList("Label 1").append("Label 2"); + newProps["MEDIA"] = StringList("Media"); + newProps["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + newProps["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + newProps["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + newProps["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + newProps["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + newProps["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + newProps["OWNER"] = StringList("Purchase owner"); + newProps["ORIGINALDATE"] = StringList("2021-01-09"); + newProps["RELEASECOUNTRY"] = StringList("Release Country"); + newProps["RELEASEDATE"] = StringList("2021-01-10"); + newProps["RELEASESTATUS"] = StringList("Release Status"); + newProps["RELEASETYPE"] = StringList("Release Type"); + newProps["REMIXER"] = StringList("Remixed by"); + newProps["SCRIPT"] = StringList("Script"); + newProps["TAGGINGDATE"] = StringList("2021-01-08"); + newProps["TITLE"] = StringList("Title"); + newProps["TRACKNUMBER"] = StringList("2"); + newProps["TRACKTOTAL"] = StringList("4"); + + const VariantMap newComplexProps { + {"defaultLanguage", false}, + {"language", "en"}, + {"name", "BINARY"}, + {"data", ByteVector("\x01\x02\x03\x04\x05\x06")}, + {"targetTypeValue", static_cast(Matroska::SimpleTag::TargetTypeValue::Collection)}, + }; + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(!f.attachments(false)); + + CPPUNIT_ASSERT_EQUAL(String("handbrake"), tag->title()); + CPPUNIT_ASSERT_EQUAL(String("Actors"), tag->artist()); + CPPUNIT_ASSERT_EQUAL(2023U, tag->year()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->album()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->comment()); + CPPUNIT_ASSERT_EQUAL(String("Genre"), tag->genre()); + CPPUNIT_ASSERT_EQUAL(0U, tag->track()); + + const auto &simpleTags = tag->simpleTagsList(); + CPPUNIT_ASSERT_EQUAL(9U, simpleTags.size()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[0].language()); + CPPUNIT_ASSERT_EQUAL(String("DURATION"), simpleTags[0].name()); + CPPUNIT_ASSERT_EQUAL(String("00:00:00.120000000"), simpleTags[0].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[0].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::None, simpleTags[0].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(9584013959154292683ULL, simpleTags[0].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[0].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[1].language()); + CPPUNIT_ASSERT_EQUAL(String("ARTIST"), simpleTags[1].name()); + CPPUNIT_ASSERT_EQUAL(String("Actors"), simpleTags[1].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[1].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[1].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[1].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[1].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[1].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[2].language()); + CPPUNIT_ASSERT_EQUAL(String("DESCRIPTION"), simpleTags[2].name()); + CPPUNIT_ASSERT_EQUAL(String("Description"), simpleTags[2].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[2].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[2].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[2].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[2].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[2].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[3].language()); + CPPUNIT_ASSERT_EQUAL(String("DIRECTOR"), simpleTags[3].name()); + CPPUNIT_ASSERT_EQUAL(String("Director"), simpleTags[3].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[3].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[3].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[3].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[3].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[3].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[4].language()); + CPPUNIT_ASSERT_EQUAL(String("ENCODER"), simpleTags[4].name()); + CPPUNIT_ASSERT_EQUAL(String("Lavf59.27.100"), simpleTags[4].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[4].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[4].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[4].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[4].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[4].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[5].language()); + CPPUNIT_ASSERT_EQUAL(String("GENRE"), simpleTags[5].name()); + CPPUNIT_ASSERT_EQUAL(String("Genre"), simpleTags[5].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[5].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[5].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[5].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[5].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[5].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[6].language()); + CPPUNIT_ASSERT_EQUAL(String("SUMMARY"), simpleTags[6].name()); + CPPUNIT_ASSERT_EQUAL(String("Comment"), simpleTags[6].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[6].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[6].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[6].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[6].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[6].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[7].language()); + CPPUNIT_ASSERT_EQUAL(String("SYNOPSIS"), simpleTags[7].name()); + CPPUNIT_ASSERT_EQUAL(String("Plot"), simpleTags[7].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[7].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[7].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[7].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[7].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[7].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[8].language()); + CPPUNIT_ASSERT_EQUAL(String("DATE_RELEASED"), simpleTags[8].name()); + CPPUNIT_ASSERT_EQUAL(String("2023"), simpleTags[8].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[8].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[8].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Album, simpleTags[8].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[8].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[8].type()); + + auto props = f.properties(); + if (initialProps != props) { + CPPUNIT_ASSERT_EQUAL(initialProps.toString(), props.toString()); + } + CPPUNIT_ASSERT(initialProps == props); + CPPUNIT_ASSERT(initialProps == tag->properties()); + + auto keys = f.complexPropertyKeys(); + CPPUNIT_ASSERT_EQUAL(StringList({"DURATION"}), keys); + auto complexProps = f.complexProperties("DURATION"); + CPPUNIT_ASSERT(List({initialComplexProps}) == complexProps); + + CPPUNIT_ASSERT(f.setComplexProperties("DURATION", {})); + CPPUNIT_ASSERT(f.setComplexProperties("BINARY", {newComplexProps})); + CPPUNIT_ASSERT(f.setProperties(newProps).isEmpty()); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(!f.attachments(false)); + + auto props = f.properties(); + if (newProps != props) { + CPPUNIT_ASSERT_EQUAL(newProps.toString(), props.toString()); + } + CPPUNIT_ASSERT(newProps == props); + CPPUNIT_ASSERT(newProps == tag->properties()); + + auto keys = f.complexPropertyKeys(); + CPPUNIT_ASSERT_EQUAL(StringList({"BINARY"}), keys); + auto complexProps = f.complexProperties("BINARY"); + CPPUNIT_ASSERT(List({newComplexProps}) == complexProps); + + const auto &simpleTags = tag->simpleTagsList(); + StringList simpleTagNames; + for(const auto &simpleTag : simpleTags) { + simpleTagNames.append(simpleTag.name()); + } + const StringList expectedSimpleTagNames { + "BINARY", "TITLE", "ARTIST", "ARTISTSORT", "TITLESORT", + "DATE_RELEASED", "PART_NUMBER", "TOTAL_PARTS", + "MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMID", + "MUSICBRAINZ_RELEASEGROUPID", "ARTIST", "ARTISTS", "ARTISTSORT", + "ASIN", "BARCODE", "CATALOG_NUMBER", "CATALOG_NUMBER", "COMMENT", + "MIXED_BY", "ENCODER", "ENCODER_SETTINGS", "DATE_ENCODED", "GENRE", + "INITIAL_KEY", "ISRC", "LABEL_CODE", "LABEL_CODE", + "ORIGINAL_MEDIA_TYPE", "MUSICBRAINZ_ARTISTID", + "MUSICBRAINZ_RELEASETRACKID", "MUSICBRAINZ_TRACKID", "ORIGINALDATE", + "PURCHASE_OWNER", "RELEASECOUNTRY", "DATE_RELEASED", "RELEASESTATUS", + "RELEASETYPE", "REMIXED_BY", "SCRIPT", "DATE_TAGGED", "TITLE", + "PART_NUMBER", "TOTAL_PARTS" + }; + CPPUNIT_ASSERT_EQUAL(expectedSimpleTagNames, simpleTagNames); + + CPPUNIT_ASSERT(f.setComplexProperties("DURATION", {initialComplexProps})); + CPPUNIT_ASSERT(f.setComplexProperties("BINARY", {})); + CPPUNIT_ASSERT(f.setProperties(initialProps).isEmpty()); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + + // Check if file with initial tags is same as original file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("tags-before-cues.mkv")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + + void testComplexProperties() + { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + const VariantMap picture { + {"data", ByteVector("JPEG data")}, + {"mimeType", "image/jpeg"}, + {"description", "Cover"}, + {"fileName", "folder.jpg"}, + {"uid", 123ULL} + }; + const VariantMap font { + {"data", ByteVector("TTF data")}, + {"mimeType", "font/ttf"}, + {"description", "Subtitle font"}, + {"fileName", "file.ttf"}, + {"uid", 456ULL} + }; + const VariantMap trackUidTag { + {"defaultLanguage", true}, + {"language", "und"}, + {"name", "DURATION"}, + {"trackUid", 8315232342706310039ULL}, + {"value", "00:00:00.120000000"}, + }; + const VariantMap targetTypeTag { + {"defaultLanguage", false}, + {"language", "en"}, + {"name", "PART_NUMBER"}, + {"targetTypeValue", 20}, + {"value", "2"}, + }; + const VariantMap binaryTag { + {"defaultLanguage", false}, + {"language", "und"}, + {"name", "THUMBNAIL"}, + {"targetTypeValue", 30}, + {"data", ByteVector("JPEG data")}, + }; + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + CPPUNIT_ASSERT(f.complexPropertyKeys().isEmpty()); + CPPUNIT_ASSERT(f.complexProperties("PICTURE").isEmpty()); + + CPPUNIT_ASSERT(f.setComplexProperties("PICTURE", {picture})); + CPPUNIT_ASSERT(f.setComplexProperties("file.ttf", {font})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + auto attachments = f.attachments(false); + CPPUNIT_ASSERT(attachments); + + const auto &attachedFiles = attachments->attachedFileList(); + CPPUNIT_ASSERT_EQUAL(2U, attachedFiles.size()); + CPPUNIT_ASSERT_EQUAL(picture.value("fileName").value(), attachedFiles[0].fileName()); + CPPUNIT_ASSERT_EQUAL(picture.value("data").value(), attachedFiles[0].data()); + CPPUNIT_ASSERT_EQUAL(picture.value("description").value(), attachedFiles[0].description()); + CPPUNIT_ASSERT_EQUAL(picture.value("mimeType").value(), attachedFiles[0].mediaType()); + CPPUNIT_ASSERT_EQUAL(picture.value("uid").value(), attachedFiles[0].uid()); + CPPUNIT_ASSERT_EQUAL(font.value("fileName").value(), attachedFiles[1].fileName()); + CPPUNIT_ASSERT_EQUAL(font.value("data").value(), attachedFiles[1].data()); + CPPUNIT_ASSERT_EQUAL(font.value("description").value(), attachedFiles[1].description()); + CPPUNIT_ASSERT_EQUAL(font.value("mimeType").value(), attachedFiles[1].mediaType()); + CPPUNIT_ASSERT_EQUAL(font.value("uid").value(), attachedFiles[1].uid()); + + CPPUNIT_ASSERT_EQUAL(StringList({"PICTURE", "file.ttf"}), f.complexPropertyKeys()); + CPPUNIT_ASSERT(List({picture}) == f.complexProperties("PICTURE")); + CPPUNIT_ASSERT(List({font}) == f.complexProperties("file.ttf")); + + CPPUNIT_ASSERT(f.setComplexProperties("DURATION", {trackUidTag})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(true); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(f.attachments(false)); + + const auto &simpleTags = tag->simpleTagsList(); + CPPUNIT_ASSERT_EQUAL(1U, simpleTags.size()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("value").value(), simpleTags[0].toString()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("language").value(), simpleTags[0].language()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("name").value(), simpleTags[0].name()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("defaultLanguage").value(), simpleTags[0].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("trackUid").value(), simpleTags[0].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::None, simpleTags[0].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[0].type()); + + CPPUNIT_ASSERT_EQUAL(StringList({ "DURATION", "PICTURE", "file.ttf"}), f.complexPropertyKeys()); + CPPUNIT_ASSERT(List({trackUidTag}) == f.complexProperties("DURATION")); + + CPPUNIT_ASSERT(f.setComplexProperties("PICTURE", {})); + CPPUNIT_ASSERT(f.setComplexProperties("file.ttf", {})); + CPPUNIT_ASSERT(f.setComplexProperties("PART_NUMBER", {targetTypeTag})); + CPPUNIT_ASSERT(f.setComplexProperties("THUMBNAIL", {binaryTag})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(true); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(!f.attachments(false)); + + const auto &simpleTags = tag->simpleTagsList(); + CPPUNIT_ASSERT_EQUAL(3U, simpleTags.size()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("value").value(), simpleTags[0].toString()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("language").value(), simpleTags[0].language()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("name").value(), simpleTags[0].name()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("defaultLanguage").value(), simpleTags[0].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("trackUid").value(), simpleTags[0].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::None, simpleTags[0].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[0].type()); + + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("value").value(), simpleTags[1].toString()); + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("language").value(), simpleTags[1].language()); + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("name").value(), simpleTags[1].name()); + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("defaultLanguage").value(), simpleTags[1].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("trackUid").value(), simpleTags[1].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Subtrack, simpleTags[1].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[1].toByteVector()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[1].type()); + + CPPUNIT_ASSERT_EQUAL(binaryTag.value("data").value(), simpleTags[2].toByteVector()); + CPPUNIT_ASSERT_EQUAL(binaryTag.value("language").value(), simpleTags[2].language()); + CPPUNIT_ASSERT_EQUAL(binaryTag.value("name").value(), simpleTags[2].name()); + CPPUNIT_ASSERT_EQUAL(binaryTag.value("defaultLanguage").value(), simpleTags[2].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(binaryTag.value("trackUid").value(), simpleTags[2].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[2].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(String(), simpleTags[2].toString()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::BinaryType, simpleTags[2].type()); + + CPPUNIT_ASSERT_EQUAL(StringList({"DURATION", "PART_NUMBER", "THUMBNAIL"}), f.complexPropertyKeys()); + CPPUNIT_ASSERT(List({trackUidTag}) == f.complexProperties("DURATION")); + CPPUNIT_ASSERT(List({targetTypeTag}) == f.complexProperties("PART_NUMBER")); + CPPUNIT_ASSERT(List({binaryTag}) == f.complexProperties("THUMBNAIL")); + + CPPUNIT_ASSERT(f.setComplexProperties("PART_NUMBER", {})); + CPPUNIT_ASSERT(f.setComplexProperties("THUMBNAIL", {})); + CPPUNIT_ASSERT(f.setComplexProperties("DURATION", {})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + + CPPUNIT_ASSERT(f.setComplexProperties("font/ttf", {{ + {"data", ByteVector("TTF data")}, + {"description", "Subtitle font"}, + {"fileName", "file.ttf"}, + }})); + CPPUNIT_ASSERT(f.setComplexProperties("file.otf", {{ + {"data", ByteVector("OTF data")}, + {"mimeType", "font/otf"}, + {"description", "OpenType"}, + }})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(f.attachments(false)); + + CPPUNIT_ASSERT_EQUAL(StringList({"file.ttf", "file.otf"}), f.complexPropertyKeys()); + auto ttfs = f.complexProperties("file.ttf"); + auto otfs = f.complexProperties("file.otf"); + CPPUNIT_ASSERT_EQUAL(1U, ttfs.size()); + CPPUNIT_ASSERT_EQUAL(1U, otfs.size()); + auto ttf = ttfs.front(); + auto otf = otfs.front(); + CPPUNIT_ASSERT_EQUAL(ByteVector("TTF data"), ttf.value("data").value()); + CPPUNIT_ASSERT_EQUAL(String("Subtitle font"), ttf.value("description").value()); + CPPUNIT_ASSERT_EQUAL(String("file.ttf"), ttf.value("fileName").value()); + CPPUNIT_ASSERT_EQUAL(String("font/ttf"), ttf.value("mimeType").value()); + CPPUNIT_ASSERT(ttf.value("uid").value() != 0ULL); + CPPUNIT_ASSERT_EQUAL(ByteVector("OTF data"), otf.value("data").value()); + CPPUNIT_ASSERT_EQUAL(String("OpenType"), otf.value("description").value()); + CPPUNIT_ASSERT_EQUAL(String("file.otf"), otf.value("fileName").value()); + CPPUNIT_ASSERT_EQUAL(String("font/otf"), otf.value("mimeType").value()); + CPPUNIT_ASSERT(otf.value("uid").value() != 0ULL); + + CPPUNIT_ASSERT(f.setComplexProperties("file.ttf", {})); + CPPUNIT_ASSERT(f.setComplexProperties("file.otf", {})); + CPPUNIT_ASSERT(f.save()); + } + + // Check if file without tags is same as original empty file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("no-tags.mka")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + + void testOpenInvalid() + { + { + Matroska::File f(TEST_FILE_PATH_C("garbage.mp3")); + CPPUNIT_ASSERT(!f.isValid()); + } + { + ByteVector origData = PlainFile(TEST_FILE_PATH_C("no-tags.mka")).readAll(); + ByteVectorStream origStream(origData); + Matroska::File origFile(&origStream, true, AudioProperties::Accurate); + CPPUNIT_ASSERT(origFile.isValid()); + + ByteVector truncatedData = origData.mid(0, 4000); + ByteVectorStream truncatedStream(truncatedData); + Matroska::File truncatedFile(&truncatedStream, true, AudioProperties::Accurate); + CPPUNIT_ASSERT(!truncatedFile.isValid()); + } + } + + void testSegmentSizeChange() + { + ScopedFileCopy copy("optimized", ".mkv"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + auto attachments = f.attachments(true); + Matroska::AttachedFile attachedFile; + attachedFile.setFileName("cover.jpg"); + attachedFile.setMediaType("image/jpeg"); + attachedFile.setDescription("Cover"); + // Large enough for emitSizeChanged() from Matroska::Segment::render() + attachedFile.setData(ByteVector(20000, 'x')); + attachedFile.setUID(5081000385627515072ULL); + attachments->addAttachedFile(attachedFile); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(true); + CPPUNIT_ASSERT(tag); + auto attachments = f.attachments(false); + CPPUNIT_ASSERT(attachments); + CPPUNIT_ASSERT(PropertyMap(SimplePropertyMap{ + {"ARTIST", {"Actors"}}, + {"DATE", {"2023"}}, + {"DESCRIPTION", {"Description"}}, + {"DIRECTOR", {"Director"}}, + {"ENCODEDBY", {"Lavf59.27.100"}}, + {"GENRE", {"Genre"}}, + {"SUMMARY", {"Comment"}}, + {"SYNOPSIS", {"Plot"}} + }) == tag->properties()); + + attachments->clear(); + f.save(); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMatroska); diff --git a/tests/test_sizes.cpp b/tests/test_sizes.cpp index 9bf0ee93..550919fc 100644 --- a/tests/test_sizes.cpp +++ b/tests/test_sizes.cpp @@ -156,6 +156,9 @@ #include +#include "matroskaattachedfile.h" +#include "matroskaattachments.h" + using namespace std; using namespace TagLib; @@ -306,8 +309,11 @@ public: #ifdef TAGLIB_WITH_MATROSKA CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::File)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Properties)); - // TODO move non pimpl fields into private class. - // CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Tag)); + CPPUNIT_ASSERT_EQUAL(classSize(3, true), sizeof(TagLib::Matroska::Tag)); + CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::Matroska::Element)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Attachments)); + CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::SimpleTag)); + CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::AttachedFile)); #endif } From b625be569093d52449d25ad2c5f3d605fa12b4df Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Wed, 12 Nov 2025 16:55:21 +0100 Subject: [PATCH 27/31] Support for Matroska chapters --- examples/matroskareader.cpp | 38 +++- taglib/CMakeLists.txt | 8 + taglib/matroska/ebml/ebmlelement.cpp | 14 ++ taglib/matroska/ebml/ebmlelement.h | 27 +++ taglib/matroska/ebml/ebmlmkchapters.cpp | 102 ++++++++++ taglib/matroska/ebml/ebmlmkchapters.h | 59 ++++++ taglib/matroska/ebml/ebmlmksegment.cpp | 16 +- taglib/matroska/ebml/ebmlmksegment.h | 4 + taglib/matroska/matroskachapter.cpp | 156 ++++++++++++++++ taglib/matroska/matroskachapter.h | 174 +++++++++++++++++ taglib/matroska/matroskachapteredition.cpp | 101 ++++++++++ taglib/matroska/matroskachapteredition.h | 106 +++++++++++ taglib/matroska/matroskachapters.cpp | 152 +++++++++++++++ taglib/matroska/matroskachapters.h | 83 +++++++++ taglib/matroska/matroskafile.cpp | 107 ++++++++++- taglib/matroska/matroskafile.h | 19 ++ tests/test_matroska.cpp | 206 +++++++++++++++++++++ tests/test_sizes.cpp | 2 + 18 files changed, 1364 insertions(+), 10 deletions(-) create mode 100644 taglib/matroska/ebml/ebmlmkchapters.cpp create mode 100644 taglib/matroska/ebml/ebmlmkchapters.h create mode 100644 taglib/matroska/matroskachapter.cpp create mode 100644 taglib/matroska/matroskachapter.h create mode 100644 taglib/matroska/matroskachapteredition.cpp create mode 100644 taglib/matroska/matroskachapteredition.h create mode 100644 taglib/matroska/matroskachapters.cpp create mode 100644 taglib/matroska/matroskachapters.h diff --git a/examples/matroskareader.cpp b/examples/matroskareader.cpp index c688f2e0..e44c2a35 100644 --- a/examples/matroskareader.cpp +++ b/examples/matroskareader.cpp @@ -4,6 +4,8 @@ #include "matroskasimpletag.h" #include "matroskaattachments.h" #include "matroskaattachedfile.h" +#include "matroskachapters.h" +#include "matroskachapteredition.h" #include "tstring.h" #include "tutils.h" #include "tbytevector.h" @@ -66,16 +68,48 @@ int main(int argc, char *argv[]) const TagLib::String &mediaType = attachedFile.mediaType(); PRINT_PRETTY("Media Type", !mediaType.isEmpty() ? mediaType.toCString(false) : "None"); PRINT_PRETTY("Data Size", - TagLib::Utils::formatString("%u byte(s)",attachedFile.data().size()).toCString(false) + TagLib::Utils::formatString("%u byte(s)", attachedFile.data().size()).toCString(false) ); PRINT_PRETTY("UID", - TagLib::Utils::formatString("%llu",attachedFile.uid()).toCString(false) + TagLib::Utils::formatString("%llu", attachedFile.uid()).toCString(false) ); } } else printf("File has no attachments\n"); + TagLib::Matroska::Chapters *chapters = file.chapters(); + if(chapters) { + printf("Chapters:\n"); + const TagLib::Matroska::Chapters::ChapterEditionList &editions = chapters->chapterEditionList(); + for(const auto &edition : editions) { + if(edition.uid()) { + PRINT_PRETTY("Edition UID", TagLib::Utils::formatString("%llu", edition.uid()) + .toCString(false)); + } + PRINT_PRETTY("Edition Flags", TagLib::Utils::formatString("default=%d, ordered=%d", + edition.isDefault(), edition.isOrdered()) + .toCString(false)); + printf("\n"); + for(const auto &chapter : edition.chapterList()) { + PRINT_PRETTY("Chapter UID", TagLib::Utils::formatString("%llu", chapter.uid()) + .toCString(false)); + PRINT_PRETTY("Chapter flags", TagLib::Utils::formatString("hidden=%d", chapter.isHidden()) + .toCString(false)); + PRINT_PRETTY("Start-End", TagLib::Utils::formatString("%llu-%llu", + chapter.timeStart(), chapter.timeEnd()).toCString(false)); + for(const auto &display : chapter.displayList()) { + PRINT_PRETTY("Display", display.string().toCString(false)); + PRINT_PRETTY("Language", !display.language().isEmpty() + ? display.language().toCString(false) : "Not set"); + } + printf("\n"); + } + } + } + else + printf("File has no chapters\n"); + if(auto properties = dynamic_cast(file.audioProperties())) { printf("Properties:\n"); PRINT_PRETTY("Doc Type", properties->docType().toCString(false)); diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 68983824..24fb4a7d 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -230,6 +230,9 @@ if(WITH_MATROSKA) set(tag_HDRS ${tag_HDRS} matroska/matroskaattachedfile.h matroska/matroskaattachments.h + matroska/matroskachapter.h + matroska/matroskachapteredition.h + matroska/matroskachapters.h matroska/matroskacues.h matroska/matroskaelement.h matroska/matroskafile.h @@ -242,6 +245,7 @@ if(WITH_MATROSKA) matroska/ebml/ebmlelement.h matroska/ebml/ebmlmasterelement.h matroska/ebml/ebmlmkattachments.h + matroska/ebml/ebmlmkchapters.h matroska/ebml/ebmlmkcues.h matroska/ebml/ebmlmkseekhead.h matroska/ebml/ebmlmksegment.h @@ -449,6 +453,9 @@ if(WITH_MATROSKA) set(matroska_SRCS matroska/matroskaattachedfile.cpp matroska/matroskaattachments.cpp + matroska/matroskachapter.cpp + matroska/matroskachapteredition.cpp + matroska/matroskachapters.cpp matroska/matroskacues.cpp matroska/matroskaelement.cpp matroska/matroskafile.cpp @@ -464,6 +471,7 @@ if(WITH_MATROSKA) matroska/ebml/ebmlelement.cpp matroska/ebml/ebmlmasterelement.cpp matroska/ebml/ebmlmkattachments.cpp + matroska/ebml/ebmlmkchapters.cpp matroska/ebml/ebmlmkcues.cpp matroska/ebml/ebmlmkseekhead.cpp matroska/ebml/ebmlmksegment.cpp diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 8ecea7fb..3acef51e 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -27,6 +27,7 @@ #include "ebmlmksegment.h" #include "ebmlmktags.h" #include "ebmlmkattachments.h" +#include "ebmlmkchapters.h" #include "ebmlmktracks.h" #include "ebmlstringelement.h" #include "ebmluintelement.h" @@ -113,6 +114,19 @@ std::unique_ptr EBML::Element::factory(File &file) RETURN_ELEMENT_FOR_CASE(Id::MkCueCodecState); RETURN_ELEMENT_FOR_CASE(Id::MkCueReference); RETURN_ELEMENT_FOR_CASE(Id::MkCueRefTime); + RETURN_ELEMENT_FOR_CASE(Id::MkChapters); + RETURN_ELEMENT_FOR_CASE(Id::MkEditionEntry); + RETURN_ELEMENT_FOR_CASE(Id::MkEditionUID); + RETURN_ELEMENT_FOR_CASE(Id::MkEditionFlagDefault); + RETURN_ELEMENT_FOR_CASE(Id::MkEditionFlagOrdered); + RETURN_ELEMENT_FOR_CASE(Id::MkChapterAtom); + RETURN_ELEMENT_FOR_CASE(Id::MkChapterUID); + RETURN_ELEMENT_FOR_CASE(Id::MkChapterTimeStart); + RETURN_ELEMENT_FOR_CASE(Id::MkChapterTimeEnd); + RETURN_ELEMENT_FOR_CASE(Id::MkChapterFlagHidden); + RETURN_ELEMENT_FOR_CASE(Id::MkChapterDisplay); + RETURN_ELEMENT_FOR_CASE(Id::MkChapString); + RETURN_ELEMENT_FOR_CASE(Id::MkChapLanguage); } return std::make_unique(id, sizeLength, dataSize); } diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 7487889c..950bcbd7 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -86,6 +86,19 @@ namespace TagLib::EBML { MkSamplingFrequency = 0xB5, MkBitDepth = 0x6264, MkChannels = 0x9F, + MkChapters = 0x1043A770, + MkEditionEntry = 0x45B9, + MkEditionUID = 0x45BC, + MkEditionFlagDefault = 0x45DB, + MkEditionFlagOrdered = 0x45DD, + MkChapterAtom = 0xB6, + MkChapterUID = 0x73C4, + MkChapterTimeStart = 0x91, + MkChapterTimeEnd = 0x92, + MkChapterFlagHidden = 0x98, + MkChapterDisplay = 0x80, + MkChapString = 0x85, + MkChapLanguage = 0x437C, }; Element(Id id, int sizeLength, offset_t dataSize) : @@ -134,6 +147,7 @@ namespace TagLib::EBML { class MkTags; class MkAttachments; class MkSeekHead; + class MkChapters; class MkCues; class VoidElement; @@ -196,6 +210,19 @@ namespace TagLib::EBML { template <> struct GetElementTypeById { using type = UTF8StringElement; }; template <> struct GetElementTypeById { using type = FloatElement; }; template <> struct GetElementTypeById { using type = MkSeekHead; }; + template <> struct GetElementTypeById { using type = MkChapters; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; template <> struct GetElementTypeById { using type = VoidElement; }; template ::type> diff --git a/taglib/matroska/ebml/ebmlmkchapters.cpp b/taglib/matroska/ebml/ebmlmkchapters.cpp new file mode 100644 index 00000000..92c6258d --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkchapters.cpp @@ -0,0 +1,102 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "ebmlmkchapters.h" +#include "ebmlstringelement.h" +#include "ebmluintelement.h" +#include "matroskachapters.h" +#include "matroskachapteredition.h" + +using namespace TagLib; + +std::unique_ptr EBML::MkChapters::parse() +{ + auto chapters = std::make_unique(); + chapters->setOffset(offset); + chapters->setSize(getSize()); + + for(const auto &element : elements) { + if(element->getId() != Id::MkEditionEntry) + continue; + + List editionChapters; + Matroska::ChapterEdition::UID editionUid = 0; + bool editionIsDefault = false; + bool editionIsOrdered = false; + auto edition = element_cast(element); + for(const auto &editionChild : *edition) { + Id id = editionChild->getId(); + if(id == Id::MkEditionUID) + editionUid = element_cast(editionChild)->getValue(); + else if(id == Id::MkEditionFlagDefault) + editionIsDefault = element_cast(editionChild)->getValue() != 0; + else if(id == Id::MkEditionFlagOrdered) + editionIsOrdered = element_cast(editionChild)->getValue() != 0; + else if(id == Id::MkChapterAtom) { + Matroska::Chapter::UID chapterUid = 0; + Matroska::Chapter::Time chapterTimeStart = 0; + Matroska::Chapter::Time chapterTimeEnd = 0; + List chapterDisplays; + bool chapterHidden = false; + auto chapterAtom = element_cast(editionChild); + for(const auto &chapterChild : *chapterAtom) { + Id cid = chapterChild->getId(); + if(cid == Id::MkChapterUID) + chapterUid = element_cast(chapterChild)->getValue(); + else if(cid == Id::MkChapterTimeStart) + chapterTimeStart = element_cast(chapterChild)->getValue(); + else if(cid == Id::MkChapterTimeEnd) + chapterTimeEnd = element_cast(chapterChild)->getValue(); + else if(cid == Id::MkChapterFlagHidden) + chapterHidden = element_cast(chapterChild)->getValue() != 0; + else if(cid == Id::MkChapterDisplay) { + auto display = element_cast(chapterChild); + String displayString; + String displayLanguage; + for(const auto &displayChild : *display) { + Id did = displayChild->getId(); + if(did == Id::MkChapString) + displayString = element_cast(displayChild)->getValue(); + else if(did == Id::MkChapLanguage) + displayLanguage = element_cast(displayChild)->getValue(); + } + if(!displayString.isEmpty()) { + chapterDisplays.append(Matroska::Chapter::Display(displayString, displayLanguage)); + } + } + } + if(chapterUid) { + editionChapters.append(Matroska::Chapter( + chapterTimeStart, chapterTimeEnd, chapterDisplays, chapterUid, chapterHidden)); + } + } + } + if(!editionChapters.isEmpty()) { + chapters->addChapterEdition(Matroska::ChapterEdition( + editionChapters, editionIsDefault, editionIsOrdered, editionUid)); + } + } + return chapters; +} diff --git a/taglib/matroska/ebml/ebmlmkchapters.h b/taglib/matroska/ebml/ebmlmkchapters.h new file mode 100644 index 00000000..e8aba3a4 --- /dev/null +++ b/taglib/matroska/ebml/ebmlmkchapters.h @@ -0,0 +1,59 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EBMLMKCHAPTERS_H +#define TAGLIB_EBMLMKCHAPTERS_H +#ifndef DO_NOT_DOCUMENT + +#include "ebmlmasterelement.h" +#include "taglib.h" + +namespace TagLib { + namespace Matroska { + class Chapters; + } + namespace EBML { + class MkChapters : public MasterElement + { + public: + MkChapters(int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkChapters, sizeLength, dataSize, offset) + { + } + MkChapters(Id, int sizeLength, offset_t dataSize, offset_t offset) : + MasterElement(Id::MkChapters, sizeLength, dataSize, offset) + { + } + MkChapters() : + MasterElement(Id::MkChapters, 0, 0, 0) + { + } + std::unique_ptr parse(); + }; + } +} + +#endif +#endif diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index c8e068e2..1eb1e8de 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -19,15 +19,11 @@ ***************************************************************************/ #include "ebmlmksegment.h" -#include "ebmlmktags.h" -#include "ebmlmkattachments.h" -#include "ebmlmkseekhead.h" -#include "ebmlmkinfo.h" -#include "ebmlmktracks.h" #include "ebmlutils.h" #include "matroskafile.h" #include "matroskatag.h" #include "matroskaattachments.h" +#include "matroskachapters.h" #include "matroskacues.h" #include "matroskaseekhead.h" #include "matroskasegment.h" @@ -80,6 +76,11 @@ bool EBML::MkSegment::read(File &file) if(!attachments->read(file)) return false; } + else if(id == Id::MkChapters) { + chapters = element_cast(std::move(element)); + if(!chapters->read(file)) + return false; + } else { if(id == Id::VoidElement && seekHead @@ -103,6 +104,11 @@ std::unique_ptr EBML::MkSegment::parseAttachments() return attachments ? attachments->parse() : nullptr; } +std::unique_ptr EBML::MkSegment::parseChapters() +{ + return chapters ? chapters->parse() : nullptr; +} + std::unique_ptr EBML::MkSegment::parseSeekHead() { return seekHead ? seekHead->parse(segmentDataOffset()) : nullptr; diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index b2c3e95b..1b8b91ea 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -25,6 +25,7 @@ #include "ebmlmasterelement.h" #include "ebmlmktags.h" #include "ebmlmkattachments.h" +#include "ebmlmkchapters.h" #include "ebmlmkseekhead.h" #include "ebmlmkcues.h" #include "ebmlmkinfo.h" @@ -35,6 +36,7 @@ namespace TagLib { namespace Matroska { class Tag; class Attachments; + class Chapters; class SeekHead; class Segment; } @@ -55,6 +57,7 @@ namespace TagLib { bool read(File &file) override; std::unique_ptr parseTag(); std::unique_ptr parseAttachments(); + std::unique_ptr parseChapters(); std::unique_ptr parseSeekHead(); std::unique_ptr parseCues(); std::unique_ptr parseSegment(); @@ -64,6 +67,7 @@ namespace TagLib { private: std::unique_ptr tags; std::unique_ptr attachments; + std::unique_ptr chapters; std::unique_ptr seekHead; std::unique_ptr cues; std::unique_ptr info; diff --git a/taglib/matroska/matroskachapter.cpp b/taglib/matroska/matroskachapter.cpp new file mode 100644 index 00000000..6e4c1b4e --- /dev/null +++ b/taglib/matroska/matroskachapter.cpp @@ -0,0 +1,156 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** +* This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskachapter.h" +#include "tstring.h" +#include "tbytevector.h" + +using namespace TagLib; + +class Matroska::Chapter::Display::DisplayPrivate +{ +public: + DisplayPrivate() = default; + ~DisplayPrivate() = default; + String string; + String language; +}; + +class Matroska::Chapter::ChapterPrivate +{ +public: + ChapterPrivate() = default; + ~ChapterPrivate() = default; + UID uid = 0; + Time timeStart = 0; + Time timeEnd = 0; + List displayList; + bool hidden = false; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Matroska::Chapter::Chapter(Time timeStart, Time timeEnd, + const List &displayList, UID uid, bool hidden) : + d(std::make_unique()) +{ + d->uid = uid; + d->timeStart = timeStart; + d->timeEnd = timeEnd; + d->displayList = displayList; + d->hidden = hidden; +} + +Matroska::Chapter::Chapter(const Chapter &other) : + d(std::make_unique(*other.d)) +{ +} + +Matroska::Chapter::Chapter(Chapter &&other) noexcept = default; + +Matroska::Chapter::~Chapter() = default; + +Matroska::Chapter &Matroska::Chapter::operator=(Chapter &&other) noexcept = default; + +Matroska::Chapter &Matroska::Chapter::operator=(const Chapter &other) +{ + Chapter(other).swap(*this); + return *this; +} + +void Matroska::Chapter::swap(Chapter &other) noexcept +{ + using std::swap; + + swap(d, other.d); +} + +Matroska::Chapter::UID Matroska::Chapter::uid() const +{ + return d->uid; +} + +Matroska::Chapter::Time Matroska::Chapter::timeStart() const +{ + return d->timeStart; +} + +Matroska::Chapter::Time Matroska::Chapter::timeEnd() const +{ + return d->timeEnd; +} + +bool Matroska::Chapter::isHidden() const +{ + return d->hidden; +} + +const List& Matroska::Chapter::displayList() const +{ + return d->displayList; +} + +Matroska::Chapter::Display::Display(const String &string, const String &language) : + d(std::make_unique()) +{ + d->string = string; + d->language = language; +} + +Matroska::Chapter::Display::Display(const Display &other) : + d(std::make_unique(*other.d)) +{ +} + +Matroska::Chapter::Display::Display(Display &&other) noexcept = default; + +Matroska::Chapter::Display::~Display() = default; + +Matroska::Chapter::Display& Matroska::Chapter::Display::operator=(const Display &other) +{ + Display(other).swap(*this); + return *this; +} + +Matroska::Chapter::Display& Matroska::Chapter::Display::operator=(Display &&other) noexcept = default; + +void Matroska::Chapter::Display::swap(Display &other) noexcept +{ + using std::swap; + + swap(d, other.d); +} + +const String& Matroska::Chapter::Display::string() const +{ + return d->string; +} + +const String& Matroska::Chapter::Display::language() const +{ + return d->language; +} diff --git a/taglib/matroska/matroskachapter.h b/taglib/matroska/matroskachapter.h new file mode 100644 index 00000000..9135b54f --- /dev/null +++ b/taglib/matroska/matroskachapter.h @@ -0,0 +1,174 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MATROSKACHAPTER_H +#define TAGLIB_MATROSKACHAPTER_H + +#include +#include "taglib_export.h" +#include "tlist.h" + +namespace TagLib { + namespace EBML { + class MkChapters; + } + + class String; + class ByteVector; + namespace Matroska { + //! Matroska chapter. + class TAGLIB_EXPORT Chapter + { + public: + using UID = unsigned long long; + using Time = unsigned long long; + + /*! + * Contains all possible strings to use for the chapter display. + */ + class TAGLIB_EXPORT Display + { + public: + /*! + * Construct a chapter display. + */ + Display(const String &string, const String &language); + + /*! + * Construct a chapter display as a copy of \a other. + */ + Display(const Display &other); + + /*! + * Construct a chapter display moving from \a other. + */ + Display(Display &&other) noexcept; + + /*! + * Destroys this chapter display. + */ + ~Display(); + + /*! + * Copies the contents of \a other into this object. + */ + Display &operator=(const Display &other); + + /*! + * Moves the contents of \a other into this object. + */ + Display &operator=(Display &&other) noexcept; + + /*! + * Exchanges the content of the object with the content of \a other. + */ + void swap(Display &other) noexcept; + + /*! + * Returns string representing the chapter. + */ + const String &string() const; + + /*! + * Returns language corresponding to the string. + */ + const String &language() const; + + private: + class DisplayPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + + /*! + * Construct a chapter. + */ + Chapter(Time timeStart, Time timeEnd, const List &displayList, + UID uid, bool hidden = false); + + /*! + * Construct a chapter as a copy of \a other. + */ + Chapter(const Chapter &other); + + /*! + * Construct a chapter moving from \a other. + */ + Chapter(Chapter &&other) noexcept; + + /*! + * Destroys this chapter. + */ + ~Chapter(); + + /*! + * Copies the contents of \a other into this object. + */ + Chapter &operator=(const Chapter &other); + + /*! + * Moves the contents of \a other into this object. + */ + Chapter &operator=(Chapter &&other) noexcept; + + /*! + * Exchanges the content of the object with the content of \a other. + */ + void swap(Chapter &other) noexcept; + + /*! + * Returns the UID of the chapter. + */ + UID uid() const; + + /*! + * Returns the timestamp of the start of the chapter in nanoseconds. + */ + Time timeStart() const; + + /*! + * Returns the timestamp of the start of the chapter in nanoseconds. + */ + Time timeEnd() const; + + /*! + * Check if chapter is hidden. + */ + bool isHidden() const; + + /*! + * Returns strings with language. + */ + const List &displayList() const; + + private: + class ChapterPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + } +} + +#endif diff --git a/taglib/matroska/matroskachapteredition.cpp b/taglib/matroska/matroskachapteredition.cpp new file mode 100644 index 00000000..2a2293a5 --- /dev/null +++ b/taglib/matroska/matroskachapteredition.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** +* This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskachapter.h" +#include "matroskachapteredition.h" +#include "tstring.h" +#include "tbytevector.h" +#include "tlist.h" + +using namespace TagLib; + +class Matroska::ChapterEdition::ChapterEditionPrivate +{ +public: + ChapterEditionPrivate() = default; + ~ChapterEditionPrivate() = default; + List chapters; + UID uid = 0; + bool flagDefault = false; + bool flagOrdered = false; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Matroska::ChapterEdition::ChapterEdition(const List &chapterList, + bool isDefault, bool isOrdered, UID uid) : + d(std::make_unique()) +{ + d->chapters = chapterList; + d->uid = uid; + d->flagDefault = isDefault; + d->flagOrdered = isOrdered; +} + +Matroska::ChapterEdition::ChapterEdition(const ChapterEdition &other) : + d(std::make_unique(*other.d)) +{ +} + +Matroska::ChapterEdition::ChapterEdition(ChapterEdition&& other) noexcept = default; + +Matroska::ChapterEdition::~ChapterEdition() = default; + +Matroska::ChapterEdition &Matroska::ChapterEdition::operator=(ChapterEdition &&other) = default; + +Matroska::ChapterEdition &Matroska::ChapterEdition::operator=(const ChapterEdition &other) +{ + ChapterEdition(other).swap(*this); + return *this; +} + +void Matroska::ChapterEdition::swap(ChapterEdition &other) noexcept +{ + using std::swap; + + swap(d, other.d); +} + +Matroska::ChapterEdition::UID Matroska::ChapterEdition::uid() const +{ + return d->uid; +} + +bool Matroska::ChapterEdition::isDefault() const +{ + return d->flagDefault; +} + +bool Matroska::ChapterEdition::isOrdered() const +{ + return d->flagOrdered; +} + +const List &Matroska::ChapterEdition::chapterList() const +{ + return d->chapters; +} diff --git a/taglib/matroska/matroskachapteredition.h b/taglib/matroska/matroskachapteredition.h new file mode 100644 index 00000000..6ed4c789 --- /dev/null +++ b/taglib/matroska/matroskachapteredition.h @@ -0,0 +1,106 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MATROSKACHAPTEREDITION_H +#define TAGLIB_MATROSKACHAPTEREDITION_H + +#include "matroskachapter.h" + +namespace TagLib { + class String; + class ByteVector; + namespace Matroska { + //! Edition of chapters. + class TAGLIB_EXPORT ChapterEdition + { + public: + using UID = unsigned long long; + + /*! + * Construct an edition. + */ + ChapterEdition(const List &chapterList, + bool isDefault, bool isOrdered = false, UID uid = 0); + + /*! + * Construct an edition as a copy of \a other. + */ + ChapterEdition(const ChapterEdition &other); + + /*! + * Construct an edition moving from \a other. + */ + ChapterEdition(ChapterEdition &&other) noexcept; + + /*! + * Destroys this edition. + */ + ~ChapterEdition(); + + /*! + * Copies the contents of \a other into this object. + */ + ChapterEdition &operator=(const ChapterEdition &other); + + /*! + * Moves the contents of \a other into this object. + */ + ChapterEdition &operator=(ChapterEdition &&other); + + /*! + * Exchanges the content of the object with the content of \a other. + */ + void swap(ChapterEdition &other) noexcept; + + /*! + * Returns the UID of the edition. + */ + UID uid() const; + + /*! + * Check if this edition should be used as the default one. + */ + bool isDefault() const; + + /*! + * Check if the chapters can be defined multiple times and the order to + * play them is enforced. + */ + bool isOrdered() const; + + /*! + * Get the list of all chapters. + */ + const List &chapterList() const; + + private: + class ChapterEditionPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + } +} + +#endif diff --git a/taglib/matroska/matroskachapters.cpp b/taglib/matroska/matroskachapters.cpp new file mode 100644 index 00000000..8f94525e --- /dev/null +++ b/taglib/matroska/matroskachapters.cpp @@ -0,0 +1,152 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "matroskachapters.h" +#include +#include "matroskachapteredition.h" +#include "ebmlstringelement.h" +#include "ebmlbinaryelement.h" +#include "ebmlmkchapters.h" +#include "ebmluintelement.h" +#include "ebmlutils.h" +#include "tlist.h" +#include "tbytevector.h" + +using namespace TagLib; + +class Matroska::Chapters::ChaptersPrivate +{ +public: + ChaptersPrivate() = default; + ~ChaptersPrivate() = default; + ChaptersPrivate(const ChaptersPrivate &) = delete; + ChaptersPrivate &operator=(const ChaptersPrivate &) = delete; + ChapterEditionList editions; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Matroska::Chapters::Chapters() : + Element(static_cast(EBML::Element::Id::MkChapters)), + d(std::make_unique()) +{ +} + +Matroska::Chapters::~Chapters() = default; + +void Matroska::Chapters::addChapterEdition(const ChapterEdition& edition) +{ + d->editions.append(edition); + setNeedsRender(true); +} + +void Matroska::Chapters::removeChapterEdition(unsigned long long uid) +{ + auto it = std::find_if(d->editions.begin(), d->editions.end(), + [uid](const ChapterEdition& file) { + return file.uid() == uid; + }); + if(it != d->editions.end()) { + d->editions.erase(it); + setNeedsRender(true); + } +} + +void Matroska::Chapters::clear() +{ + d->editions.clear(); + setNeedsRender(true); +} + +const Matroska::Chapters::ChapterEditionList &Matroska::Chapters::chapterEditionList() const +{ + return d->editions; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// +ByteVector Matroska::Chapters::renderInternal() +{ + if(d->editions.isEmpty()) { + // Avoid writing a Chapters element without ChapterEdition element. + return {}; + } + + EBML::MkChapters chapters; + for(const auto &chapterEdition : std::as_const(d->editions)) { + auto chapterEditionElement = EBML::make_unique_element(); + + if(auto uid = chapterEdition.uid()) { + auto uidElement = EBML::make_unique_element(); + uidElement->setValue(uid); + chapterEditionElement->appendElement(std::move(uidElement)); + } + auto defaultElement = EBML::make_unique_element(); + defaultElement->setValue(chapterEdition.isDefault()); + chapterEditionElement->appendElement(std::move(defaultElement)); + auto orderedElement = EBML::make_unique_element(); + orderedElement->setValue(chapterEdition.isOrdered()); + chapterEditionElement->appendElement(std::move(orderedElement)); + + for(const auto &chapter : chapterEdition.chapterList()) { + auto chapterElement = EBML::make_unique_element(); + + auto cuidElement = EBML::make_unique_element(); + auto cuid = chapter.uid(); + cuidElement->setValue(cuid ? cuid : EBML::randomUID()); + chapterElement->appendElement(std::move(cuidElement)); + auto timeStartElement = EBML::make_unique_element(); + timeStartElement->setValue(chapter.timeStart()); + chapterElement->appendElement(std::move(timeStartElement)); + auto timeEndElement = EBML::make_unique_element(); + timeEndElement->setValue(chapter.timeEnd()); + chapterElement->appendElement(std::move(timeEndElement)); + auto hiddenElement = EBML::make_unique_element(); + hiddenElement->setValue(chapter.isHidden()); + chapterElement->appendElement(std::move(hiddenElement)); + + for(const auto& display : chapter.displayList()) { + auto displayElement = EBML::make_unique_element(); + + auto stringElement = EBML::make_unique_element(); + stringElement->setValue(display.string()); + displayElement->appendElement(std::move(stringElement)); + auto languageElement = EBML::make_unique_element(); + languageElement->setValue(display.language()); + displayElement->appendElement(std::move(languageElement)); + + chapterElement->appendElement(std::move(displayElement)); + } + + chapterEditionElement->appendElement(std::move(chapterElement)); + } + + chapters.appendElement(std::move(chapterEditionElement)); + } + return chapters.render(); +} diff --git a/taglib/matroska/matroskachapters.h b/taglib/matroska/matroskachapters.h new file mode 100644 index 00000000..c235b74f --- /dev/null +++ b/taglib/matroska/matroskachapters.h @@ -0,0 +1,83 @@ +/*************************************************************************** + copyright : (C) 2025 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MATROSKACHAPTERS_H +#define TAGLIB_MATROSKACHAPTERS_H + +#include +#include "taglib_export.h" +#include "tlist.h" +#include "matroskaelement.h" + +namespace TagLib { + class File; + namespace EBML { + class MkChapters; + } + namespace Matroska { + class ChapterEdition; + class File; + + //! Collection of chapter editions. + class TAGLIB_EXPORT Chapters +#ifndef DO_NOT_DOCUMENT + : private Element +#endif + { + public: + using ChapterEditionList = List; + //! Construct chapters. + Chapters(); + + //! Destroy chapters. + virtual ~Chapters(); + + //! Add a chapter edition. + void addChapterEdition(const ChapterEdition &edition); + + //! Remove a chapter edition. + void removeChapterEdition(unsigned long long uid); + + //! Remove all chapter editions. + void clear(); + + //! Get list of all chapter editions. + const ChapterEditionList &chapterEditionList() const; + + private: + friend class EBML::MkChapters; + friend class File; + class ChaptersPrivate; + + // private Element implementation + ByteVector renderInternal() override; + + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + } +} + +#endif diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index e0accda1..2cf83a4e 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -22,6 +22,9 @@ #include "matroskatag.h" #include "matroskaattachments.h" #include "matroskaattachedfile.h" +#include "matroskachapter.h" +#include "matroskachapteredition.h" +#include "matroskachapters.h" #include "matroskaseekhead.h" #include "matroskacues.h" #include "matroskasegment.h" @@ -50,6 +53,7 @@ public: std::unique_ptr tag; std::unique_ptr attachments; + std::unique_ptr chapters; std::unique_ptr seekHead; std::unique_ptr cues; std::unique_ptr segment; @@ -179,12 +183,64 @@ StringList Matroska::File::complexPropertyKeys() const } } } + if(d->chapters && !d->chapters->chapterEditionList().isEmpty()) { + keys.append("CHAPTERS"); + } return keys; } List Matroska::File::complexProperties(const String &key) const { List props = TagLib::File::complexProperties(key); + if(key.upper() == "CHAPTERS") { + if(d->chapters) { + for(const auto &edition : d->chapters->chapterEditionList()) { + VariantMap property; + if(auto uid = edition.uid()) { + property.insert("uid", uid); + } + if(auto isDefault = edition.isDefault()) { + property.insert("isDefault", isDefault); + } + if(auto isOrdered = edition.isOrdered()) { + property.insert("isOrdered", isOrdered); + } + if(auto chapters = edition.chapterList(); !chapters.isEmpty()) { + VariantList chaps; + for(const auto &chapter : chapters) { + VariantMap chap; + if(auto uid = chapter.uid()) { + chap.insert("uid", uid); + } + if(auto isHidden = chapter.isHidden()) { + chap.insert("isHidden", isHidden); + } + chap.insert("timeStart", chapter.timeStart()); + if(auto timeEnd = chapter.timeEnd()) { + chap.insert("timeEnd", timeEnd); + } + if(auto displays = chapter.displayList(); !displays.isEmpty()) { + VariantList disps; + for(const auto &display : displays) { + VariantMap disp; + if(auto str = display.string(); !str.isEmpty()) { + disp.insert("string", str); + } + if(auto language = display.language(); !language.isEmpty()) { + disp.insert("language", language); + } + disps.append(disp); + } + chap.insert("displays", disps); + } + chaps.append(chap); + } + property.insert("chapters", chaps); + } + props.append(property); + } + } + } if(d->attachments) { const auto &attachedFiles = d->attachments->attachedFileList(); for(const auto &attachedFile : attachedFiles) { @@ -208,6 +264,37 @@ bool Matroska::File::setComplexProperties(const String &key, const Listclear(); + for(const auto &ed : value) { + List editionChapters; + const auto chaps = ed.value("chapters").toList(); + for(const auto &chapVar : chaps) { + auto chap = chapVar.toMap(); + const auto disps = chap.value("displays").toList(); + List chapterDisplays; + for(const auto &dispVar : disps) { + auto disp = dispVar.toMap(); + chapterDisplays.append(Chapter::Display( + disp.value("string").toString(), + disp.value("language").toString())); + } + editionChapters.append(Chapter( + chap.value("timeStart").toULongLong(), + chap.value("timeEnd").toULongLong(), + chapterDisplays, + chap.value("uid", 0ULL).toULongLong(), + chap.value("isHidden", false).toBool())); + } + d->chapters->addChapterEdition(ChapterEdition( + editionChapters, + ed.value("isDefault", false).toBool(), + ed.value("isOrdered", false).toBool(), + ed.value("uid", 0ULL).toULongLong())); + } + return true; + } + List &files = attachments(true)->attachedFiles(); for(auto it = files.begin(); it != files.end();) { if(keyMatchesAttachedFile(key, *it)) { @@ -268,6 +355,13 @@ Matroska::Attachments *Matroska::File::attachments(bool create) const return d->attachments.get(); } +Matroska::Chapters *Matroska::File::chapters(bool create) const +{ + if(!d->chapters && create) + d->chapters = std::make_unique(); + return d->chapters.get(); +} + void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle) { offset_t fileLength = length(); @@ -312,6 +406,7 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle) d->cues = segment->parseCues(); d->tag = segment->parseTag(); d->attachments = segment->parseAttachments(); + d->chapters = segment->parseChapters(); if(readProperties) { d->properties = std::make_unique(this); @@ -355,8 +450,13 @@ bool Matroska::File::save() return false; } - // Do not create new attachments or tags and corresponding seek head entries - // if only empty objects were created. + // Do not create new attachments, chapters or tags and corresponding + // seek head entries if only empty objects were created. + if(d->chapters && d->chapters->chapterEditionList().isEmpty() && + d->chapters->size() == 0 && d->chapters->offset() == 0 && + d->chapters->data().isEmpty()) { + d->chapters.reset(); + } if(d->attachments && d->attachments->attachedFileList().isEmpty() && d->attachments->size() == 0 && d->attachments->offset() == 0 && d->attachments->data().isEmpty()) { @@ -373,6 +473,7 @@ bool Matroska::File::save() // List of all possible elements we can write List elements { + d->chapters.get(), d->attachments.get(), d->tag.get() }; @@ -381,7 +482,7 @@ bool Matroska::File::save() * to the end of the file. For new elements, * the order is from least likely to change, * to most likely to change: - * 1. Bookmarks (todo) + * 1. Chapters * 2. Attachments * 3. Tags */ diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index af57e5a6..71cd9e7c 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -30,6 +30,7 @@ namespace TagLib::Matroska { class Properties; class Tag; class Attachments; + class Chapters; /*! * Implementation of TagLib::File for Matroska. @@ -143,8 +144,26 @@ namespace TagLib::Matroska { */ bool save() override; + /*! + * Returns a pointer to the attachments of the file. + * + * If \a create is \c false this may return a null pointer if there are no + * attachments. + * If \a create is \c true it will create attachments if none exist and + * returns a valid pointer. + */ Attachments *attachments(bool create = false) const; + /*! + * Returns a pointer to the chapters of the file. + * + * If \a create is \c false this may return a null pointer if there are no + * chapters. + * If \a create is \c true it will create chapters if none exist and + * returns a valid pointer. + */ + Chapters *chapters(bool create = false) const; + /*! * Returns whether or not the given \a stream can be opened as a Matroska * file. diff --git a/tests/test_matroska.cpp b/tests/test_matroska.cpp index ca1cd0e0..a9d178b3 100644 --- a/tests/test_matroska.cpp +++ b/tests/test_matroska.cpp @@ -130,6 +130,9 @@ #include "matroskatag.h" #include "matroskaattachments.h" #include "matroskaattachedfile.h" +#include "matroskachapter.h" +#include "matroskachapteredition.h" +#include "matroskachapters.h" #include "matroskasimpletag.h" #include "plainfile.h" #include @@ -152,6 +155,7 @@ class TestMatroska : public CppUnit::TestFixture CPPUNIT_TEST(testComplexProperties); CPPUNIT_TEST(testOpenInvalid); CPPUNIT_TEST(testSegmentSizeChange); + CPPUNIT_TEST(testChapters); CPPUNIT_TEST_SUITE_END(); public: @@ -995,6 +999,208 @@ public: } } + void testChapters() + { + const Matroska::ChapterEdition edition1( + List{ + Matroska::Chapter( + 0, 40000, + List{ + Matroska::Chapter::Display("Chapter 1", "eng")}, + 1, false), + Matroska::Chapter( + 40000, 80000, + List{ + Matroska::Chapter::Display("Chapter 2", "eng"), + Matroska::Chapter::Display("Kapitel 2", "deu"), + }, + 2), + Matroska::Chapter( + 80000, 120000, + List{ + Matroska::Chapter::Display("Chapter 3", "und")}, + 3, true) + }, + true, false); + const VariantMap chapterEdition1 { + {"chapters", + VariantList{ + VariantMap{ + {"displays", VariantList{ + VariantMap{{"language", "eng"}, {"string", "Chapter 1"}}}}, + {"timeEnd", 40000ULL}, + {"timeStart", 0ULL}, + {"uid", 1ULL} + }, + VariantMap{ + {"displays", VariantList{ + VariantMap{{"language", "eng"}, {"string", "Chapter 2"}}, + VariantMap{{"language", "deu"}, {"string", "Kapitel 2"}}}}, + {"timeEnd", 80000ULL}, + {"timeStart", 40000ULL}, + {"uid", 2ULL} + }, + VariantMap{ + { + "displays", VariantList{ + VariantMap{{"language", "und"}, {"string", "Chapter 3"}}} + }, + {"isHidden", true}, + {"timeEnd", 120000ULL}, + {"timeStart", 80000ULL}, + {"uid", 3ULL} + } + } + }, + {"isDefault", true} + }; + const VariantMap chapterEdition2 { + {"chapters", + VariantList{ + VariantMap{ + {"displays", VariantList{ + VariantMap{{"string", "Chapter A"}}}}, + {"timeStart", 10000ULL}, + {"uid", 1234567890ULL} + }, + } + }, + {"isOrdered", true}, + {"uid", 321ULL} + }; + + ScopedFileCopy copy("tags-before-cues", ".mkv"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + CPPUNIT_ASSERT(!f.chapters(false)); + CPPUNIT_ASSERT_EQUAL(StringList({"DURATION"}), f.complexPropertyKeys()); + CPPUNIT_ASSERT(f.complexProperties("CHAPTERS").isEmpty()); + + f.chapters(true)->addChapterEdition(edition1); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + auto chapters = f.chapters(false); + CPPUNIT_ASSERT(chapters); + CPPUNIT_ASSERT_EQUAL(StringList({"DURATION", "CHAPTERS"}), f.complexPropertyKeys()); + auto chaptersProperties = f.complexProperties("CHAPTERS"); + CPPUNIT_ASSERT_EQUAL(1U, chaptersProperties.size()); + CPPUNIT_ASSERT_EQUAL(chapterEdition1, chaptersProperties.front()); + + CPPUNIT_ASSERT_EQUAL(1U, chapters->chapterEditionList().size()); + const auto &edition = chapters->chapterEditionList().front(); + CPPUNIT_ASSERT_EQUAL(true, edition.isDefault()); + CPPUNIT_ASSERT_EQUAL(false, edition.isOrdered()); + CPPUNIT_ASSERT_EQUAL(0ULL, edition.uid()); + const auto &chapterAtoms = edition.chapterList(); + CPPUNIT_ASSERT_EQUAL(3U, chapterAtoms.size()); + CPPUNIT_ASSERT_EQUAL(1ULL, chapterAtoms[0].uid()); + CPPUNIT_ASSERT_EQUAL(false, chapterAtoms[0].isHidden()); + CPPUNIT_ASSERT_EQUAL(0ULL, chapterAtoms[0].timeStart()); + CPPUNIT_ASSERT_EQUAL(40000ULL, chapterAtoms[0].timeEnd()); + CPPUNIT_ASSERT_EQUAL(1U, chapterAtoms[0].displayList().size()); + CPPUNIT_ASSERT_EQUAL(String("Chapter 1"), chapterAtoms[0].displayList()[0].string()); + CPPUNIT_ASSERT_EQUAL(String("eng"), chapterAtoms[0].displayList()[0].language()); + CPPUNIT_ASSERT_EQUAL(2ULL, chapterAtoms[1].uid()); + CPPUNIT_ASSERT_EQUAL(false, chapterAtoms[1].isHidden()); + CPPUNIT_ASSERT_EQUAL(40000ULL, chapterAtoms[1].timeStart()); + CPPUNIT_ASSERT_EQUAL(80000ULL, chapterAtoms[1].timeEnd()); + CPPUNIT_ASSERT_EQUAL(2U, chapterAtoms[1].displayList().size()); + CPPUNIT_ASSERT_EQUAL(String("Chapter 2"), chapterAtoms[1].displayList()[0].string()); + CPPUNIT_ASSERT_EQUAL(String("eng"), chapterAtoms[1].displayList()[0].language()); + CPPUNIT_ASSERT_EQUAL(String("Kapitel 2"), chapterAtoms[1].displayList()[1].string()); + CPPUNIT_ASSERT_EQUAL(String("deu"), chapterAtoms[1].displayList()[1].language()); + CPPUNIT_ASSERT_EQUAL(3ULL, chapterAtoms[2].uid()); + CPPUNIT_ASSERT_EQUAL(true, chapterAtoms[2].isHidden()); + CPPUNIT_ASSERT_EQUAL(80000ULL, chapterAtoms[2].timeStart()); + CPPUNIT_ASSERT_EQUAL(120000ULL, chapterAtoms[2].timeEnd()); + CPPUNIT_ASSERT_EQUAL(1U, chapterAtoms[2].displayList().size()); + CPPUNIT_ASSERT_EQUAL(String("Chapter 3"), chapterAtoms[2].displayList()[0].string()); + CPPUNIT_ASSERT_EQUAL(String("und"), chapterAtoms[2].displayList()[0].language()); + + CPPUNIT_ASSERT(f.setComplexProperties("CHAPTERS", {chapterEdition2})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + auto chapters = f.chapters(false); + CPPUNIT_ASSERT(chapters); + CPPUNIT_ASSERT_EQUAL(StringList({"DURATION", "CHAPTERS"}), f.complexPropertyKeys()); + auto chaptersProperties = f.complexProperties("CHAPTERS"); + CPPUNIT_ASSERT_EQUAL(1U, chaptersProperties.size()); + CPPUNIT_ASSERT_EQUAL(chapterEdition2, chaptersProperties.front()); + + CPPUNIT_ASSERT_EQUAL(1U, chapters->chapterEditionList().size()); + const auto &edition = chapters->chapterEditionList().front(); + CPPUNIT_ASSERT_EQUAL(false, edition.isDefault()); + CPPUNIT_ASSERT_EQUAL(true, edition.isOrdered()); + CPPUNIT_ASSERT_EQUAL(321ULL, edition.uid()); + const auto &chapterAtoms = edition.chapterList(); + CPPUNIT_ASSERT_EQUAL(1U, chapterAtoms.size()); + CPPUNIT_ASSERT_EQUAL(1234567890ULL, chapterAtoms[0].uid()); + CPPUNIT_ASSERT_EQUAL(false, chapterAtoms[0].isHidden()); + CPPUNIT_ASSERT_EQUAL(10000ULL, chapterAtoms[0].timeStart()); + CPPUNIT_ASSERT_EQUAL(0ULL, chapterAtoms[0].timeEnd()); + CPPUNIT_ASSERT_EQUAL(1U, chapterAtoms[0].displayList().size()); + CPPUNIT_ASSERT_EQUAL(String("Chapter A"), chapterAtoms[0].displayList()[0].string()); + CPPUNIT_ASSERT_EQUAL(String(), chapterAtoms[0].displayList()[0].language()); + + const Matroska::ChapterEdition edition2 = chapters->chapterEditionList().front(); + chapters->removeChapterEdition(321ULL); + chapters->addChapterEdition(edition1); + chapters->addChapterEdition(edition2); + + Matroska::AttachedFile attachedFile; + attachedFile.setFileName("folder.png"); + attachedFile.setMediaType("image/png"); + attachedFile.setDescription("Cover"); + attachedFile.setData(ByteVector("PNG data")); + attachedFile.setUID(1763187649ULL); + f.attachments(true)->addAttachedFile(attachedFile); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(f.attachments(false)); + CPPUNIT_ASSERT(f.chapters(false)); + + CPPUNIT_ASSERT_EQUAL(StringList({"DURATION", "PICTURE", "CHAPTERS"}), + f.complexPropertyKeys()); + auto chaptersProperties = f.complexProperties("CHAPTERS"); + CPPUNIT_ASSERT_EQUAL(2U, chaptersProperties.size()); + CPPUNIT_ASSERT_EQUAL(chapterEdition1, chaptersProperties.front()); + CPPUNIT_ASSERT_EQUAL(chapterEdition2, chaptersProperties.back()); + + f.attachments()->clear(); + f.chapters()->clear(); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + + // Check if file with initial tags is same as original file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("tags-before-cues.mkv")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMatroska); diff --git a/tests/test_sizes.cpp b/tests/test_sizes.cpp index 550919fc..aaae162d 100644 --- a/tests/test_sizes.cpp +++ b/tests/test_sizes.cpp @@ -158,6 +158,7 @@ #include "matroskaattachedfile.h" #include "matroskaattachments.h" +#include "matroskachapters.h" using namespace std; using namespace TagLib; @@ -312,6 +313,7 @@ public: CPPUNIT_ASSERT_EQUAL(classSize(3, true), sizeof(TagLib::Matroska::Tag)); CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::Matroska::Element)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Attachments)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Chapters)); CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::SimpleTag)); CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::AttachedFile)); #endif From 607cd5bdf46d43d8643a424ade1c9e85fb30b139 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 16 Nov 2025 16:12:54 +0100 Subject: [PATCH 28/31] Matroska: Fix issues reported by static analysis, formatting, docs --- taglib/matroska/ebml/ebmlbinaryelement.cpp | 1 + taglib/matroska/ebml/ebmlbinaryelement.h | 17 +- taglib/matroska/ebml/ebmlelement.cpp | 18 +- taglib/matroska/ebml/ebmlelement.h | 423 ++++++++++----------- taglib/matroska/ebml/ebmlfloatelement.cpp | 2 +- taglib/matroska/ebml/ebmlfloatelement.h | 14 +- taglib/matroska/ebml/ebmlmasterelement.cpp | 8 +- taglib/matroska/ebml/ebmlmasterelement.h | 68 ++-- taglib/matroska/ebml/ebmlmkattachments.cpp | 15 +- taglib/matroska/ebml/ebmlmkattachments.h | 16 +- taglib/matroska/ebml/ebmlmkchapters.cpp | 17 +- taglib/matroska/ebml/ebmlmkchapters.h | 15 +- taglib/matroska/ebml/ebmlmkcues.cpp | 14 +- taglib/matroska/ebml/ebmlmkcues.h | 17 +- taglib/matroska/ebml/ebmlmkinfo.cpp | 9 +- taglib/matroska/ebml/ebmlmkinfo.h | 16 +- taglib/matroska/ebml/ebmlmkseekhead.cpp | 11 +- taglib/matroska/ebml/ebmlmkseekhead.h | 14 +- taglib/matroska/ebml/ebmlmksegment.cpp | 23 +- taglib/matroska/ebml/ebmlmksegment.h | 21 +- taglib/matroska/ebml/ebmlmktags.cpp | 19 +- taglib/matroska/ebml/ebmlmktags.h | 17 +- taglib/matroska/ebml/ebmlmktracks.cpp | 12 +- taglib/matroska/ebml/ebmlmktracks.h | 16 +- taglib/matroska/ebml/ebmlstringelement.cpp | 18 +- taglib/matroska/ebml/ebmlstringelement.h | 42 +- taglib/matroska/ebml/ebmluintelement.cpp | 8 +- taglib/matroska/ebml/ebmluintelement.h | 16 +- taglib/matroska/ebml/ebmlutils.cpp | 17 +- taglib/matroska/ebml/ebmlutils.h | 21 +- taglib/matroska/ebml/ebmlvoidelement.cpp | 7 +- taglib/matroska/ebml/ebmlvoidelement.h | 12 +- taglib/matroska/matroskaattachedfile.cpp | 6 +- taglib/matroska/matroskaattachedfile.h | 7 +- taglib/matroska/matroskaattachments.cpp | 19 +- taglib/matroska/matroskaattachments.h | 6 +- taglib/matroska/matroskachapter.cpp | 13 +- taglib/matroska/matroskachapter.h | 8 +- taglib/matroska/matroskachapteredition.cpp | 7 +- taglib/matroska/matroskachapteredition.h | 4 +- taglib/matroska/matroskachapters.cpp | 13 +- taglib/matroska/matroskachapters.h | 3 + taglib/matroska/matroskacues.cpp | 23 +- taglib/matroska/matroskacues.h | 16 +- taglib/matroska/matroskaelement.cpp | 15 +- taglib/matroska/matroskaelement.h | 3 +- taglib/matroska/matroskafile.cpp | 44 +-- taglib/matroska/matroskafile.h | 2 - taglib/matroska/matroskaproperties.cpp | 4 +- taglib/matroska/matroskaseekhead.cpp | 58 +-- taglib/matroska/matroskaseekhead.h | 4 +- taglib/matroska/matroskasegment.cpp | 5 +- taglib/matroska/matroskasimpletag.cpp | 10 +- taglib/matroska/matroskasimpletag.h | 6 +- taglib/matroska/matroskatag.cpp | 50 ++- taglib/matroska/matroskatag.h | 29 +- tests/test_matroska.cpp | 2 +- 57 files changed, 624 insertions(+), 677 deletions(-) diff --git a/taglib/matroska/ebml/ebmlbinaryelement.cpp b/taglib/matroska/ebml/ebmlbinaryelement.cpp index 81e35cbc..c8377c70 100644 --- a/taglib/matroska/ebml/ebmlbinaryelement.cpp +++ b/taglib/matroska/ebml/ebmlbinaryelement.cpp @@ -20,6 +20,7 @@ #include "ebmlbinaryelement.h" #include "ebmlutils.h" +#include "tfile.h" using namespace TagLib; diff --git a/taglib/matroska/ebml/ebmlbinaryelement.h b/taglib/matroska/ebml/ebmlbinaryelement.h index bcf9fd70..f14b2b5b 100644 --- a/taglib/matroska/ebml/ebmlbinaryelement.h +++ b/taglib/matroska/ebml/ebmlbinaryelement.h @@ -23,6 +23,7 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlelement.h" +#include "tbytevector.h" namespace TagLib { class File; @@ -31,20 +32,14 @@ namespace TagLib { { public: BinaryElement(Id id, int sizeLength, offset_t dataSize) : - Element(id, sizeLength, dataSize) - { - } + Element(id, sizeLength, dataSize) {} BinaryElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - Element(id, sizeLength, dataSize) - { - } - + Element(id, sizeLength, dataSize) {} explicit BinaryElement(Id id) : - Element(id, 0, 0) - { - } + Element(id, 0, 0) {} + const ByteVector &getValue() const { return value; } - void setValue(const ByteVector &value) { this->value = value; } + void setValue(const ByteVector &val) { value = val; } bool read(File &file) override; ByteVector render() override; diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 3acef51e..46c3dd11 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -36,8 +36,6 @@ #include "tdebug.h" #include "tutils.h" -#include - using namespace TagLib; #define RETURN_ELEMENT_FOR_CASE(eid) \ @@ -46,7 +44,7 @@ using namespace TagLib; std::unique_ptr EBML::Element::factory(File &file) { // Get the element ID - offset_t offset = file.tell(); + const offset_t offset = file.tell(); unsigned int uintId = readId(file); if(!uintId) { debug("Failed to parse EMBL ElementID"); @@ -138,12 +136,12 @@ unsigned int EBML::Element::readId(File &file) debug("Failed to read VINT size"); return 0; } - unsigned int nb_bytes = VINTSizeLength<4>(*buffer.begin()); - if(!nb_bytes) + const unsigned int numBytes = VINTSizeLength<4>(*buffer.begin()); + if(!numBytes) return 0; - if(nb_bytes > 1) - buffer.append(file.readBlock(nb_bytes - 1)); - if(buffer.size() != nb_bytes) { + if(numBytes > 1) + buffer.append(file.readBlock(numBytes - 1)); + if(buffer.size() != numBytes) { debug("Failed to read VINT data"); return 0; } @@ -169,9 +167,9 @@ ByteVector EBML::Element::render() ByteVector EBML::Element::renderId() const { - int numBytes = idSize(id); + const int numBytes = idSize(id); static const auto byteOrder = Utils::systemByteOrder(); - auto uintId = static_cast(id); + const auto uintId = static_cast(id); uint32_t data = byteOrder == Utils::LittleEndian ? Utils::byteSwap(uintId) : uintId; return ByteVector(reinterpret_cast(&data) + (4 - numBytes), numBytes); } diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 950bcbd7..9c8144b8 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -22,233 +22,232 @@ #define TAGLIB_EBMLELEMENT_H #ifndef DO_NOT_DOCUMENT +#include #include -#include "tfile.h" -#include "tutils.h" #include "taglib.h" -namespace TagLib::EBML { - class Element - { - public: - enum class Id : unsigned int +namespace TagLib +{ + class File; + class ByteVector; + + namespace EBML { + + class Element { - EBMLHeader = 0x1A45DFA3, - DocType = 0x4282, - DocTypeVersion = 0x4287, - VoidElement = 0xEC, - MkSegment = 0x18538067, - MkTags = 0x1254C367, - MkTag = 0x7373, - MkTagTargets = 0x63C0, - MkTagTargetTypeValue = 0x68CA, - MkTagTrackUID = 0x63C5, - MkSimpleTag = 0x67C8, - MkTagName = 0x45A3, - MkTagLanguage = 0x447A, - MkTagBinary = 0x4485, - MkTagString = 0x4487, - MkTagsTagLanguage = 0x447A, - MkTagsLanguageDefault = 0x4484, - MkAttachments = 0x1941A469, - MkAttachedFile = 0x61A7, - MkAttachedFileDescription = 0x467E, - MkAttachedFileName = 0x466E, - MkAttachedFileMediaType = 0x4660, - MkAttachedFileData = 0x465C, - MkAttachedFileUID = 0x46AE, - MkSeekHead = 0x114D9B74, - MkSeek = 0x4DBB, - MkSeekID = 0x53AB, - MkSeekPosition = 0x53AC, - MkCluster = 0x1F43B675, - MkCodecState = 0xA4, - MkCues = 0x1C53BB6B, - MkCuePoint = 0xBB, - MkCueTime = 0xB3, - MkCueTrackPositions = 0xB7, - MkCueTrack = 0xF7, - MkCueClusterPosition = 0xF1, - MkCueRelativePosition = 0xF0, - MkCueDuration = 0xB2, - MkCueBlockNumber = 0x5378, - MkCueCodecState = 0xEA, - MkCueReference = 0xDB, - MkCueRefTime = 0x96, - MkInfo = 0x1549A966, - MkTimestampScale = 0x2AD7B1, - MkDuration = 0x4489, - MkTitle = 0x7BA9, - MkTracks = 0x1654AE6B, - MkTrackEntry = 0xAE, - MkCodecID = 0x86, - MkAudio = 0xE1, - MkSamplingFrequency = 0xB5, - MkBitDepth = 0x6264, - MkChannels = 0x9F, - MkChapters = 0x1043A770, - MkEditionEntry = 0x45B9, - MkEditionUID = 0x45BC, - MkEditionFlagDefault = 0x45DB, - MkEditionFlagOrdered = 0x45DD, - MkChapterAtom = 0xB6, - MkChapterUID = 0x73C4, - MkChapterTimeStart = 0x91, - MkChapterTimeEnd = 0x92, - MkChapterFlagHidden = 0x98, - MkChapterDisplay = 0x80, - MkChapString = 0x85, - MkChapLanguage = 0x437C, + public: + enum class Id : unsigned int + { + EBMLHeader = 0x1A45DFA3, + DocType = 0x4282, + DocTypeVersion = 0x4287, + VoidElement = 0xEC, + MkSegment = 0x18538067, + MkTags = 0x1254C367, + MkTag = 0x7373, + MkTagTargets = 0x63C0, + MkTagTargetTypeValue = 0x68CA, + MkTagTrackUID = 0x63C5, + MkSimpleTag = 0x67C8, + MkTagName = 0x45A3, + MkTagLanguage = 0x447A, + MkTagBinary = 0x4485, + MkTagString = 0x4487, + MkTagsTagLanguage = 0x447A, + MkTagsLanguageDefault = 0x4484, + MkAttachments = 0x1941A469, + MkAttachedFile = 0x61A7, + MkAttachedFileDescription = 0x467E, + MkAttachedFileName = 0x466E, + MkAttachedFileMediaType = 0x4660, + MkAttachedFileData = 0x465C, + MkAttachedFileUID = 0x46AE, + MkSeekHead = 0x114D9B74, + MkSeek = 0x4DBB, + MkSeekID = 0x53AB, + MkSeekPosition = 0x53AC, + MkCluster = 0x1F43B675, + MkCodecState = 0xA4, + MkCues = 0x1C53BB6B, + MkCuePoint = 0xBB, + MkCueTime = 0xB3, + MkCueTrackPositions = 0xB7, + MkCueTrack = 0xF7, + MkCueClusterPosition = 0xF1, + MkCueRelativePosition = 0xF0, + MkCueDuration = 0xB2, + MkCueBlockNumber = 0x5378, + MkCueCodecState = 0xEA, + MkCueReference = 0xDB, + MkCueRefTime = 0x96, + MkInfo = 0x1549A966, + MkTimestampScale = 0x2AD7B1, + MkDuration = 0x4489, + MkTitle = 0x7BA9, + MkTracks = 0x1654AE6B, + MkTrackEntry = 0xAE, + MkCodecID = 0x86, + MkAudio = 0xE1, + MkSamplingFrequency = 0xB5, + MkBitDepth = 0x6264, + MkChannels = 0x9F, + MkChapters = 0x1043A770, + MkEditionEntry = 0x45B9, + MkEditionUID = 0x45BC, + MkEditionFlagDefault = 0x45DB, + MkEditionFlagOrdered = 0x45DD, + MkChapterAtom = 0xB6, + MkChapterUID = 0x73C4, + MkChapterTimeStart = 0x91, + MkChapterTimeEnd = 0x92, + MkChapterFlagHidden = 0x98, + MkChapterDisplay = 0x80, + MkChapString = 0x85, + MkChapLanguage = 0x437C, + }; + + Element(Id id, int sizeLength, offset_t dataSize) : + id(id), sizeLength(sizeLength), dataSize(dataSize) {} + Element(Id id, int sizeLength, offset_t dataSize, offset_t) : + id(id), sizeLength(sizeLength), dataSize(dataSize) {} + virtual ~Element() = default; + virtual bool read(File &file) + { + skipData(file); + return true; + } + void skipData(File &file); + Id getId() const { return id; } + offset_t headSize() const; + offset_t getSize() const { return headSize() + dataSize; } + int getSizeLength() const { return sizeLength; } + int64_t getDataSize() const { return dataSize; } + ByteVector renderId() const; + virtual ByteVector render(); + static std::unique_ptr factory(File &file); + static unsigned int readId(File &file); + + protected: + Id id; + int sizeLength; + offset_t dataSize; }; - Element(Id id, int sizeLength, offset_t dataSize) : - id(id), sizeLength(sizeLength), dataSize(dataSize) + // Template specializations to ensure that elements for the different IDs + // are consistently created and cast. They provide a mapping between IDs + // and Element subclasses. The switch in factory() makes sure that the + // template for all IDs are instantiated, i.e. that every ID has its Element + // subclass mapped. + class MasterElement; + class UIntElement; + class BinaryElement; + class FloatElement; + class MkSegment; + class MkInfo; + class MkTracks; + class MkTags; + class MkAttachments; + class MkSeekHead; + class MkChapters; + class MkCues; + class VoidElement; + class UTF8StringElement; + class Latin1StringElement; + + template + struct GetElementTypeById; + + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = MkSegment; }; + template <> struct GetElementTypeById { using type = MkInfo; }; + template <> struct GetElementTypeById { using type = MkTracks; }; + template <> struct GetElementTypeById { using type = MkTags; }; + template <> struct GetElementTypeById { using type = MkAttachments; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = MkCues; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = BinaryElement; }; + template <> struct GetElementTypeById { using type = BinaryElement; }; + template <> struct GetElementTypeById { using type = BinaryElement; }; + template <> struct GetElementTypeById { using type = BinaryElement; }; + template <> struct GetElementTypeById { using type = FloatElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = FloatElement; }; + template <> struct GetElementTypeById { using type = MkSeekHead; }; + template <> struct GetElementTypeById { using type = MkChapters; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = UIntElement; }; + template <> struct GetElementTypeById { using type = MasterElement; }; + template <> struct GetElementTypeById { using type = UTF8StringElement; }; + template <> struct GetElementTypeById { using type = Latin1StringElement; }; + template <> struct GetElementTypeById { using type = VoidElement; }; + + template ::type> + const T *element_cast(const std::unique_ptr &ptr) { + return static_cast(ptr.get()); } - Element(Id id, int sizeLength, offset_t dataSize, offset_t) : - id(id), sizeLength(sizeLength), dataSize(dataSize) + + template ::type> + std::unique_ptr element_cast(std::unique_ptr &&ptr) { + return std::unique_ptr(static_cast(ptr.release())); } - virtual ~Element() = default; - virtual bool read(File &file) + + template ::type> + std::unique_ptr make_unique_element(Element::Id id, int sizeLength, offset_t dataSize, offset_t offset) { - skipData(file); - return true; + return std::make_unique(id, sizeLength, dataSize, offset); } - void skipData(File &file); - Id getId() const { return id; } - offset_t headSize() const; - offset_t getSize() const { return headSize() + dataSize; } - int getSizeLength() const { return sizeLength; } - int64_t getDataSize() const { return dataSize; } - ByteVector renderId() const; - virtual ByteVector render(); - static std::unique_ptr factory(File &file); - static unsigned int readId(File &file); - protected: - Id id; - int sizeLength; - offset_t dataSize; - }; + template ::type> + std::unique_ptr make_unique_element() + { + return std::make_unique(ID, 0, 0, 0); + } - // Template specializations to ensure that elements for the different IDs - // are consistently created and cast. They provide a mapping between IDs - // and Element subclasses. The switch in factory() makes sure that the - // template for all IDs are instantiated, i.e. that every ID has its Element - // subclass mapped. - class MasterElement; - class UIntElement; - class BinaryElement; - class FloatElement; - class MkSegment; - class MkInfo; - class MkTracks; - class MkTags; - class MkAttachments; - class MkSeekHead; - class MkChapters; - class MkCues; - class VoidElement; - - template - class StringElement; - using UTF8StringElement = StringElement; - using Latin1StringElement = StringElement; - - template - struct GetElementTypeById; - - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = Latin1StringElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = MkSegment; }; - template <> struct GetElementTypeById { using type = MkInfo; }; - template <> struct GetElementTypeById { using type = MkTracks; }; - template <> struct GetElementTypeById { using type = MkTags; }; - template <> struct GetElementTypeById { using type = MkAttachments; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = MkCues; }; - template <> struct GetElementTypeById { using type = UTF8StringElement; }; - template <> struct GetElementTypeById { using type = UTF8StringElement; }; - template <> struct GetElementTypeById { using type = UTF8StringElement; }; - template <> struct GetElementTypeById { using type = UTF8StringElement; }; - template <> struct GetElementTypeById { using type = Latin1StringElement; }; - template <> struct GetElementTypeById { using type = Latin1StringElement; }; - template <> struct GetElementTypeById { using type = Latin1StringElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = BinaryElement; }; - template <> struct GetElementTypeById { using type = BinaryElement; }; - template <> struct GetElementTypeById { using type = BinaryElement; }; - template <> struct GetElementTypeById { using type = BinaryElement; }; - template <> struct GetElementTypeById { using type = FloatElement; }; - template <> struct GetElementTypeById { using type = UTF8StringElement; }; - template <> struct GetElementTypeById { using type = FloatElement; }; - template <> struct GetElementTypeById { using type = MkSeekHead; }; - template <> struct GetElementTypeById { using type = MkChapters; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = UIntElement; }; - template <> struct GetElementTypeById { using type = MasterElement; }; - template <> struct GetElementTypeById { using type = UTF8StringElement; }; - template <> struct GetElementTypeById { using type = Latin1StringElement; }; - template <> struct GetElementTypeById { using type = VoidElement; }; - - template ::type> - const T *element_cast(const std::unique_ptr &ptr) - { - return static_cast(ptr.get()); } - - template ::type> - std::unique_ptr element_cast(std::unique_ptr &&ptr) - { - return std::unique_ptr(static_cast(ptr.release())); - } - - template ::type> - std::unique_ptr make_unique_element(Element::Id id, int sizeLength, offset_t dataSize, offset_t offset) - { - return std::make_unique(id, sizeLength, dataSize, offset); - } - - template ::type> - std::unique_ptr make_unique_element() - { - return std::make_unique(ID, 0, 0, 0); - } - } #endif diff --git a/taglib/matroska/ebml/ebmlfloatelement.cpp b/taglib/matroska/ebml/ebmlfloatelement.cpp index 4752d862..781aca16 100644 --- a/taglib/matroska/ebml/ebmlfloatelement.cpp +++ b/taglib/matroska/ebml/ebmlfloatelement.cpp @@ -44,7 +44,7 @@ double EBML::FloatElement::getValueAsDouble(double defaultValue) const bool EBML::FloatElement::read(File &file) { - ByteVector buffer = file.readBlock(dataSize); + const ByteVector buffer = file.readBlock(dataSize); if(buffer.size() != dataSize) { debug("Failed to read EBML Float element"); return false; diff --git a/taglib/matroska/ebml/ebmlfloatelement.h b/taglib/matroska/ebml/ebmlfloatelement.h index 2c9ef59c..34a2cab8 100644 --- a/taglib/matroska/ebml/ebmlfloatelement.h +++ b/taglib/matroska/ebml/ebmlfloatelement.h @@ -40,18 +40,12 @@ namespace TagLib { using FloatVariantType = std::variant; FloatElement(Id id, int sizeLength, offset_t dataSize) : - Element(id, sizeLength, dataSize) - { - } + Element(id, sizeLength, dataSize) {} FloatElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - Element(id, sizeLength, dataSize) - { - } - + Element(id, sizeLength, dataSize) {} explicit FloatElement(Id id) : - FloatElement(id, 0, 0) - { - } + FloatElement(id, 0, 0) {} + FloatVariantType getValue() const { return value; } double getValueAsDouble(double defaultValue = 0.0) const; void setValue(FloatVariantType val) { value = val; } diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp index fd0447dd..85d041e1 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.cpp +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -27,14 +27,14 @@ using namespace TagLib; EBML::MasterElement::~MasterElement() = default; -void EBML::MasterElement::appendElement(std::unique_ptr&& element) +void EBML::MasterElement::appendElement(std::unique_ptr &&element) { elements.push_back(std::move(element)); } bool EBML::MasterElement::read(File &file) { - offset_t maxOffset = file.tell() + dataSize; + const offset_t maxOffset = file.tell() + dataSize; std::unique_ptr element; while((element = findNextElement(file, maxOffset))) { if(!element->read(file)) @@ -54,8 +54,8 @@ ByteVector EBML::MasterElement::render() buffer.append(renderVINT(dataSize, 0)); buffer.append(data); if(minRenderSize) { - auto bufferSize = buffer.size(); - if(minRenderSize >= bufferSize + MIN_VOID_ELEMENT_SIZE) + if(const auto bufferSize = buffer.size(); + minRenderSize >= bufferSize + MIN_VOID_ELEMENT_SIZE) buffer.append(VoidElement::renderSize(minRenderSize - bufferSize)); } return buffer; diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h index c7f7a846..158bcf84 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.h +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -23,46 +23,46 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlelement.h" -#include "tbytevector.h" -#include "tlist.h" #include "taglib.h" +#include "tlist.h" -namespace TagLib::EBML { - class MasterElement : public Element - { - public: - MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset) : - Element(id, sizeLength, dataSize), offset(offset) +namespace TagLib +{ + class ByteVector; + + namespace EBML { + class MasterElement : public Element { - } + public: + MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset) : + Element(id, sizeLength, dataSize), offset(offset) {} + explicit MasterElement(Id id) : + Element(id, 0, 0), offset(0) {} + ~MasterElement() override; - explicit MasterElement(Id id) : - Element(id, 0, 0), offset(0) - { - } - ~MasterElement() override; - offset_t getOffset() const { return offset; } - bool read(File &file) override; - ByteVector render() override; - void appendElement(std::unique_ptr &&element); - std::list>::iterator begin() { return elements.begin(); } - std::list>::iterator end() { return elements.end(); } - std::list>::const_iterator begin() const { return elements.begin(); } - std::list>::const_iterator end() const { return elements.end(); } - std::list>::const_iterator cbegin() const { return elements.cbegin(); } - std::list>::const_iterator cend() const { return elements.cend(); } - offset_t getPadding() const { return padding; } - void setPadding(offset_t padding) { this->padding = padding; } - offset_t getMinRenderSize() const { return minRenderSize; } - void setMinRenderSize(offset_t minRenderSize) { this->minRenderSize = minRenderSize; } + offset_t getOffset() const { return offset; } + bool read(File &file) override; + ByteVector render() override; + void appendElement(std::unique_ptr &&element); + std::list>::iterator begin() { return elements.begin(); } + std::list>::iterator end() { return elements.end(); } + std::list>::const_iterator begin() const { return elements.begin(); } + std::list>::const_iterator end() const { return elements.end(); } + std::list>::const_iterator cbegin() const { return elements.cbegin(); } + std::list>::const_iterator cend() const { return elements.cend(); } + offset_t getPadding() const { return padding; } + void setPadding(offset_t numBytes) { padding = numBytes; } + offset_t getMinRenderSize() const { return minRenderSize; } + void setMinRenderSize(offset_t minimumSize) { minRenderSize = minimumSize; } - protected: - offset_t offset; - offset_t padding = 0; - offset_t minRenderSize = 0; - std::list> elements; - }; + protected: + offset_t offset; + offset_t padding = 0; + offset_t minRenderSize = 0; + std::list> elements; + }; + } } #endif diff --git a/taglib/matroska/ebml/ebmlmkattachments.cpp b/taglib/matroska/ebml/ebmlmkattachments.cpp index bc58b952..43c2efb9 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.cpp +++ b/taglib/matroska/ebml/ebmlmkattachments.cpp @@ -27,7 +27,7 @@ using namespace TagLib; -std::unique_ptr EBML::MkAttachments::parse() +std::unique_ptr EBML::MkAttachments::parse() const { auto attachments = std::make_unique(); attachments->setOffset(offset); @@ -42,17 +42,16 @@ std::unique_ptr EBML::MkAttachments::parse() const String *mediaType = nullptr; const ByteVector *data = nullptr; Matroska::AttachedFile::UID uid = 0; - auto attachedFile = element_cast(element); + const auto attachedFile = element_cast(element); for(const auto &attachedFileChild : *attachedFile) { - Id id = attachedFileChild->getId(); - if(id == Id::MkAttachedFileName) - filename = &(element_cast(attachedFileChild)->getValue()); + if(const Id id = attachedFileChild->getId(); id == Id::MkAttachedFileName) + filename = &element_cast(attachedFileChild)->getValue(); else if(id == Id::MkAttachedFileData) - data = &(element_cast(attachedFileChild)->getValue()); + data = &element_cast(attachedFileChild)->getValue(); else if(id == Id::MkAttachedFileDescription) - description = &(element_cast(attachedFileChild)->getValue()); + description = &element_cast(attachedFileChild)->getValue(); else if(id == Id::MkAttachedFileMediaType) - mediaType = &(element_cast(attachedFileChild)->getValue()); + mediaType = &element_cast(attachedFileChild)->getValue(); else if(id == Id::MkAttachedFileUID) uid = element_cast(attachedFileChild)->getValue(); } diff --git a/taglib/matroska/ebml/ebmlmkattachments.h b/taglib/matroska/ebml/ebmlmkattachments.h index 02f27648..cefc8e07 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.h +++ b/taglib/matroska/ebml/ebmlmkattachments.h @@ -23,7 +23,6 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlmasterelement.h" -#include "ebmlutils.h" #include "taglib.h" namespace TagLib { @@ -35,18 +34,13 @@ namespace TagLib { { public: MkAttachments(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) {} MkAttachments(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) {} MkAttachments() : - MasterElement(Id::MkAttachments, 0, 0, 0) - { - } - std::unique_ptr parse(); + MasterElement(Id::MkAttachments, 0, 0, 0) {} + + std::unique_ptr parse() const; }; } } diff --git a/taglib/matroska/ebml/ebmlmkchapters.cpp b/taglib/matroska/ebml/ebmlmkchapters.cpp index 92c6258d..26d4386c 100644 --- a/taglib/matroska/ebml/ebmlmkchapters.cpp +++ b/taglib/matroska/ebml/ebmlmkchapters.cpp @@ -31,7 +31,7 @@ using namespace TagLib; -std::unique_ptr EBML::MkChapters::parse() +std::unique_ptr EBML::MkChapters::parse() const { auto chapters = std::make_unique(); chapters->setOffset(offset); @@ -45,10 +45,9 @@ std::unique_ptr EBML::MkChapters::parse() Matroska::ChapterEdition::UID editionUid = 0; bool editionIsDefault = false; bool editionIsOrdered = false; - auto edition = element_cast(element); + const auto edition = element_cast(element); for(const auto &editionChild : *edition) { - Id id = editionChild->getId(); - if(id == Id::MkEditionUID) + if(const Id id = editionChild->getId(); id == Id::MkEditionUID) editionUid = element_cast(editionChild)->getValue(); else if(id == Id::MkEditionFlagDefault) editionIsDefault = element_cast(editionChild)->getValue() != 0; @@ -60,10 +59,9 @@ std::unique_ptr EBML::MkChapters::parse() Matroska::Chapter::Time chapterTimeEnd = 0; List chapterDisplays; bool chapterHidden = false; - auto chapterAtom = element_cast(editionChild); + const auto chapterAtom = element_cast(editionChild); for(const auto &chapterChild : *chapterAtom) { - Id cid = chapterChild->getId(); - if(cid == Id::MkChapterUID) + if(const Id cid = chapterChild->getId(); cid == Id::MkChapterUID) chapterUid = element_cast(chapterChild)->getValue(); else if(cid == Id::MkChapterTimeStart) chapterTimeStart = element_cast(chapterChild)->getValue(); @@ -72,12 +70,11 @@ std::unique_ptr EBML::MkChapters::parse() else if(cid == Id::MkChapterFlagHidden) chapterHidden = element_cast(chapterChild)->getValue() != 0; else if(cid == Id::MkChapterDisplay) { - auto display = element_cast(chapterChild); + const auto display = element_cast(chapterChild); String displayString; String displayLanguage; for(const auto &displayChild : *display) { - Id did = displayChild->getId(); - if(did == Id::MkChapString) + if(const Id did = displayChild->getId(); did == Id::MkChapString) displayString = element_cast(displayChild)->getValue(); else if(did == Id::MkChapLanguage) displayLanguage = element_cast(displayChild)->getValue(); diff --git a/taglib/matroska/ebml/ebmlmkchapters.h b/taglib/matroska/ebml/ebmlmkchapters.h index e8aba3a4..462b57a8 100644 --- a/taglib/matroska/ebml/ebmlmkchapters.h +++ b/taglib/matroska/ebml/ebmlmkchapters.h @@ -39,18 +39,13 @@ namespace TagLib { { public: MkChapters(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkChapters, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkChapters, sizeLength, dataSize, offset) {} MkChapters(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkChapters, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkChapters, sizeLength, dataSize, offset) {} MkChapters() : - MasterElement(Id::MkChapters, 0, 0, 0) - { - } - std::unique_ptr parse(); + MasterElement(Id::MkChapters, 0, 0, 0) {} + + std::unique_ptr parse() const; }; } } diff --git a/taglib/matroska/ebml/ebmlmkcues.cpp b/taglib/matroska/ebml/ebmlmkcues.cpp index 4ba50b3e..005337a0 100644 --- a/taglib/matroska/ebml/ebmlmkcues.cpp +++ b/taglib/matroska/ebml/ebmlmkcues.cpp @@ -24,7 +24,7 @@ using namespace TagLib; -std::unique_ptr EBML::MkCues::parse(offset_t segmentDataOffset) +std::unique_ptr EBML::MkCues::parse(offset_t segmentDataOffset) const { auto cues = std::make_unique(segmentDataOffset); cues->setOffset(offset); @@ -34,19 +34,17 @@ std::unique_ptr EBML::MkCues::parse(offset_t segmentDataOffset) for(const auto &cuesChild : elements) { if(cuesChild->getId() != Id::MkCuePoint) continue; - auto cuePointElement = element_cast(cuesChild); + const auto cuePointElement = element_cast(cuesChild); auto cuePoint = std::make_unique(); for(const auto &cuePointChild : *cuePointElement) { - Id id = cuePointChild->getId(); - if(id == Id::MkCueTime) + if(const Id id = cuePointChild->getId(); id == Id::MkCueTime) cuePoint->setTime(element_cast(cuePointChild)->getValue()); else if(id == Id::MkCueTrackPositions) { auto cueTrack = std::make_unique(); - auto cueTrackElement = element_cast(cuePointChild); + const auto cueTrackElement = element_cast(cuePointChild); for(const auto &cueTrackChild : *cueTrackElement) { - Id trackId = cueTrackChild->getId(); - if(trackId == Id::MkCueTrack) + if(const Id trackId = cueTrackChild->getId(); trackId == Id::MkCueTrack) cueTrack->setTrackNumber(element_cast(cueTrackChild)->getValue()); else if(trackId == Id::MkCueClusterPosition) cueTrack->setClusterPosition(element_cast(cueTrackChild)->getValue()); @@ -59,7 +57,7 @@ std::unique_ptr EBML::MkCues::parse(offset_t segmentDataOffset) else if(trackId == Id::MkCueCodecState) cueTrack->setCodecState(element_cast(cueTrackChild)->getValue()); else if(trackId == Id::MkCueReference) { - auto cueReference = element_cast(cueTrackChild); + const auto cueReference = element_cast(cueTrackChild); for(const auto &cueReferenceChild : *cueReference) { if(cueReferenceChild->getId() != Id::MkCueRefTime) continue; diff --git a/taglib/matroska/ebml/ebmlmkcues.h b/taglib/matroska/ebml/ebmlmkcues.h index edb2b428..388ec354 100644 --- a/taglib/matroska/ebml/ebmlmkcues.h +++ b/taglib/matroska/ebml/ebmlmkcues.h @@ -23,32 +23,25 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlmasterelement.h" -#include "ebmlutils.h" #include "taglib.h" namespace TagLib { namespace Matroska { class Cues; } - //class Matroska::Tag; + namespace EBML { class MkCues : public MasterElement { public: MkCues(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkCues, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkCues, sizeLength, dataSize, offset) {} MkCues(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkCues, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkCues, sizeLength, dataSize, offset) {} MkCues() : - MasterElement(Id::MkCues, 0, 0, 0) - { - } + MasterElement(Id::MkCues, 0, 0, 0) {} - std::unique_ptr parse(offset_t segmentDataOffset); + std::unique_ptr parse(offset_t segmentDataOffset) const; }; } } diff --git a/taglib/matroska/ebml/ebmlmkinfo.cpp b/taglib/matroska/ebml/ebmlmkinfo.cpp index d9228b40..b0ea7e63 100644 --- a/taglib/matroska/ebml/ebmlmkinfo.cpp +++ b/taglib/matroska/ebml/ebmlmkinfo.cpp @@ -31,7 +31,7 @@ using namespace TagLib; -void EBML::MkInfo::parse(Matroska::Properties *properties) +void EBML::MkInfo::parse(Matroska::Properties *properties) const { if(!properties) return; @@ -40,14 +40,13 @@ void EBML::MkInfo::parse(Matroska::Properties *properties) double duration = 0.0; String title; for(const auto &element : elements) { - Id id = element->getId(); - if (id == Id::MkTimestampScale) { + if(const Id id = element->getId(); id == Id::MkTimestampScale) { timestampScale = element_cast(element)->getValue(); } - else if (id == Id::MkDuration) { + else if(id == Id::MkDuration) { duration = element_cast(element)->getValueAsDouble(); } - else if (id == Id::MkTitle) { + else if(id == Id::MkTitle) { title = element_cast(element)->getValue(); } } diff --git a/taglib/matroska/ebml/ebmlmkinfo.h b/taglib/matroska/ebml/ebmlmkinfo.h index 672ea217..294342bb 100644 --- a/taglib/matroska/ebml/ebmlmkinfo.h +++ b/taglib/matroska/ebml/ebmlmkinfo.h @@ -28,7 +28,6 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlmasterelement.h" -#include "ebmlutils.h" #include "taglib.h" namespace TagLib { @@ -40,18 +39,13 @@ namespace TagLib { { public: MkInfo(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkInfo, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkInfo, sizeLength, dataSize, offset) {} MkInfo(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkInfo, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkInfo, sizeLength, dataSize, offset) {} MkInfo() : - MasterElement(Id::MkInfo, 0, 0, 0) - { - } - void parse(Matroska::Properties * properties); + MasterElement(Id::MkInfo, 0, 0, 0) {} + + void parse(Matroska::Properties * properties) const; }; } } diff --git a/taglib/matroska/ebml/ebmlmkseekhead.cpp b/taglib/matroska/ebml/ebmlmkseekhead.cpp index dccca140..71250cc3 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.cpp +++ b/taglib/matroska/ebml/ebmlmkseekhead.cpp @@ -25,7 +25,7 @@ using namespace TagLib; -std::unique_ptr EBML::MkSeekHead::parse(offset_t segmentDataOffset) +std::unique_ptr EBML::MkSeekHead::parse(offset_t segmentDataOffset) const { auto seekHead = std::make_unique(segmentDataOffset); seekHead->setOffset(offset); @@ -34,14 +34,13 @@ std::unique_ptr EBML::MkSeekHead::parse(offset_t segmentData for(const auto &element : elements) { if(element->getId() != Id::MkSeek) continue; - auto seekElement = element_cast(element); + const auto seekElement = element_cast(element); Matroska::Element::ID entryId = 0; offset_t offset = 0; for(const auto &seekElementChild : *seekElement) { - Id id = seekElementChild->getId(); - if(id == Id::MkSeekID && !entryId) { - auto data = element_cast(seekElementChild)->getValue(); - if(data.size() == 4) + if(const Id id = seekElementChild->getId(); id == Id::MkSeekID && !entryId) { + if(auto data = element_cast(seekElementChild)->getValue(); + data.size() == 4) entryId = data.toUInt(true); } else if(id == Id::MkSeekPosition && !offset) diff --git a/taglib/matroska/ebml/ebmlmkseekhead.h b/taglib/matroska/ebml/ebmlmkseekhead.h index da7b3071..c7c47acf 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.h +++ b/taglib/matroska/ebml/ebmlmkseekhead.h @@ -34,19 +34,13 @@ namespace TagLib { { public: MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) {} MkSeekHead(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) {} MkSeekHead() : - MasterElement(Id::MkSeekHead, 0, 0, 0) - { - } + MasterElement(Id::MkSeekHead, 0, 0, 0) {} - std::unique_ptr parse(offset_t segmentDataOffset); + std::unique_ptr parse(offset_t segmentDataOffset) const; }; } } diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index 1eb1e8de..1222360b 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -39,13 +39,12 @@ offset_t EBML::MkSegment::segmentDataOffset() const bool EBML::MkSegment::read(File &file) { - offset_t maxOffset = file.tell() + dataSize; + const offset_t maxOffset = file.tell() + dataSize; std::unique_ptr element; int i = 0; int seekHeadIndex = -1; while((element = findNextElement(file, maxOffset))) { - Id id = element->getId(); - if(id == Id::MkSeekHead) { + if(const Id id = element->getId(); id == Id::MkSeekHead) { seekHeadIndex = i; seekHead = element_cast(std::move(element)); if(!seekHead->read(file)) @@ -94,46 +93,46 @@ bool EBML::MkSegment::read(File &file) return true; } -std::unique_ptr EBML::MkSegment::parseTag() +std::unique_ptr EBML::MkSegment::parseTag() const { return tags ? tags->parse() : nullptr; } -std::unique_ptr EBML::MkSegment::parseAttachments() +std::unique_ptr EBML::MkSegment::parseAttachments() const { return attachments ? attachments->parse() : nullptr; } -std::unique_ptr EBML::MkSegment::parseChapters() +std::unique_ptr EBML::MkSegment::parseChapters() const { return chapters ? chapters->parse() : nullptr; } -std::unique_ptr EBML::MkSegment::parseSeekHead() +std::unique_ptr EBML::MkSegment::parseSeekHead() const { return seekHead ? seekHead->parse(segmentDataOffset()) : nullptr; } -std::unique_ptr EBML::MkSegment::parseCues() +std::unique_ptr EBML::MkSegment::parseCues() const { return cues ? cues->parse(segmentDataOffset()) : nullptr; } -std::unique_ptr EBML::MkSegment::parseSegment() +std::unique_ptr EBML::MkSegment::parseSegment() const { return std::make_unique(sizeLength, dataSize, offset + idSize(id)); } -void EBML::MkSegment::parseInfo(Matroska::Properties *properties) +void EBML::MkSegment::parseInfo(Matroska::Properties *properties) const { if(info) { info->parse(properties); } } -void EBML::MkSegment::parseTracks(Matroska::Properties *properties) +void EBML::MkSegment::parseTracks(Matroska::Properties *properties) const { - if (tracks) { + if(tracks) { tracks->parse(properties); } } diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index 1b8b91ea..f1164a49 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -49,20 +49,19 @@ namespace TagLib { { } MkSegment(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkSegment, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkSegment, sizeLength, dataSize, offset) {} ~MkSegment() override; + offset_t segmentDataOffset() const; bool read(File &file) override; - std::unique_ptr parseTag(); - std::unique_ptr parseAttachments(); - std::unique_ptr parseChapters(); - std::unique_ptr parseSeekHead(); - std::unique_ptr parseCues(); - std::unique_ptr parseSegment(); - void parseInfo(Matroska::Properties *properties); - void parseTracks(Matroska::Properties *properties); + std::unique_ptr parseTag() const; + std::unique_ptr parseAttachments() const; + std::unique_ptr parseChapters() const; + std::unique_ptr parseSeekHead() const; + std::unique_ptr parseCues() const; + std::unique_ptr parseSegment() const; + void parseInfo(Matroska::Properties *properties) const; + void parseTracks(Matroska::Properties *properties) const; private: std::unique_ptr tags; diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index ffee7e5c..339d80db 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -28,7 +28,7 @@ using namespace TagLib; -std::unique_ptr EBML::MkTags::parse() +std::unique_ptr EBML::MkTags::parse() const { auto mTag = std::make_unique(); mTag->setOffset(offset); @@ -39,14 +39,13 @@ std::unique_ptr EBML::MkTags::parse() for(const auto &tagsChild : elements) { if(tagsChild->getId() != Id::MkTag) continue; - auto tag = element_cast(tagsChild); + const auto tag = element_cast(tagsChild); List simpleTags; const MasterElement *targets = nullptr; // Identify the element and the elements for(const auto &tagChild : *tag) { - Id tagChildId = tagChild->getId(); - if(!targets && tagChildId == Id::MkTagTargets) + if(const Id tagChildId = tagChild->getId(); !targets && tagChildId == Id::MkTagTargets) targets = element_cast(tagChild); else if(tagChildId == Id::MkSimpleTag) simpleTags.append(element_cast(tagChild)); @@ -57,8 +56,7 @@ std::unique_ptr EBML::MkTags::parse() unsigned long long trackUid = 0; if(targets) { for(const auto &targetsChild : *targets) { - Id id = targetsChild->getId(); - if(id == Id::MkTagTargetTypeValue + if(const Id id = targetsChild->getId(); id == Id::MkTagTargetTypeValue && targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) { targetTypeValue = static_cast( element_cast(targetsChild)->getValue() @@ -71,7 +69,7 @@ std::unique_ptr EBML::MkTags::parse() } // Parse each - for(auto simpleTag : simpleTags) { + for(const auto simpleTag : simpleTags) { const String *tagValueString = nullptr; const ByteVector *tagValueBinary = nullptr; String tagName; @@ -79,13 +77,12 @@ std::unique_ptr EBML::MkTags::parse() bool defaultLanguageFlag = true; for(const auto &simpleTagChild : *simpleTag) { - Id id = simpleTagChild->getId(); - if(id == Id::MkTagName && tagName.isEmpty()) + if(const Id id = simpleTagChild->getId(); id == Id::MkTagName && tagName.isEmpty()) tagName = element_cast(simpleTagChild)->getValue(); else if(id == Id::MkTagString && !tagValueString) - tagValueString = &(element_cast(simpleTagChild)->getValue()); + tagValueString = &element_cast(simpleTagChild)->getValue(); else if(id == Id::MkTagBinary && !tagValueBinary) - tagValueBinary = &(element_cast(simpleTagChild)->getValue()); + tagValueBinary = &element_cast(simpleTagChild)->getValue(); else if(id == Id::MkTagsTagLanguage && language.isEmpty()) language = element_cast(simpleTagChild)->getValue(); else if(id == Id::MkTagsLanguageDefault) diff --git a/taglib/matroska/ebml/ebmlmktags.h b/taglib/matroska/ebml/ebmlmktags.h index dea4e2eb..cea61f80 100644 --- a/taglib/matroska/ebml/ebmlmktags.h +++ b/taglib/matroska/ebml/ebmlmktags.h @@ -23,32 +23,25 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlmasterelement.h" -#include "ebmlutils.h" #include "taglib.h" namespace TagLib { namespace Matroska { class Tag; } - //class Matroska::Tag; + namespace EBML { class MkTags : public MasterElement { public: MkTags(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkTags, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkTags, sizeLength, dataSize, offset) {} MkTags(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkTags, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkTags, sizeLength, dataSize, offset) {} MkTags() : - MasterElement(Id::MkTags, 0, 0, 0) - { - } + MasterElement(Id::MkTags, 0, 0, 0) {} - std::unique_ptr parse(); + std::unique_ptr parse() const; }; } } diff --git a/taglib/matroska/ebml/ebmlmktracks.cpp b/taglib/matroska/ebml/ebmlmktracks.cpp index febdaad2..b3491b71 100644 --- a/taglib/matroska/ebml/ebmlmktracks.cpp +++ b/taglib/matroska/ebml/ebmlmktracks.cpp @@ -31,7 +31,7 @@ using namespace TagLib; -void EBML::MkTracks::parse(Matroska::Properties *properties) +void EBML::MkTracks::parse(Matroska::Properties *properties) const { if(!properties) return; @@ -44,16 +44,14 @@ void EBML::MkTracks::parse(Matroska::Properties *properties) double samplingFrequency = 0.0; unsigned long long bitDepth = 0; unsigned long long channels = 0; - auto trackEntry = element_cast(element); + const auto trackEntry = element_cast(element); for(const auto &trackEntryChild : *trackEntry) { - Id trackEntryChildId = trackEntryChild->getId(); - if(trackEntryChildId == Id::MkCodecID) + if(const Id trackEntryChildId = trackEntryChild->getId(); trackEntryChildId == Id::MkCodecID) codecId = element_cast(trackEntryChild)->getValue(); else if(trackEntryChildId == Id::MkAudio) { - auto audio = element_cast(trackEntryChild); + const auto audio = element_cast(trackEntryChild); for(const auto &audioChild : *audio) { - Id audioChildId = audioChild->getId(); - if(audioChildId == Id::MkSamplingFrequency) + if(const Id audioChildId = audioChild->getId(); audioChildId == Id::MkSamplingFrequency) samplingFrequency = element_cast(audioChild)->getValueAsDouble(); else if(audioChildId == Id::MkBitDepth) bitDepth = element_cast(audioChild)->getValue(); diff --git a/taglib/matroska/ebml/ebmlmktracks.h b/taglib/matroska/ebml/ebmlmktracks.h index 1f8ebb2f..2ba2a4b1 100644 --- a/taglib/matroska/ebml/ebmlmktracks.h +++ b/taglib/matroska/ebml/ebmlmktracks.h @@ -28,7 +28,6 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlmasterelement.h" -#include "ebmlutils.h" #include "taglib.h" namespace TagLib { @@ -40,18 +39,13 @@ namespace TagLib { { public: MkTracks(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkTracks, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkTracks, sizeLength, dataSize, offset) {} MkTracks(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkTracks, sizeLength, dataSize, offset) - { - } + MasterElement(Id::MkTracks, sizeLength, dataSize, offset) {} MkTracks() : - MasterElement(Id::MkTracks, 0, 0, 0) - { - } - void parse(Matroska::Properties *properties); + MasterElement(Id::MkTracks, 0, 0, 0) {} + + void parse(Matroska::Properties *properties) const; }; } } diff --git a/taglib/matroska/ebml/ebmlstringelement.cpp b/taglib/matroska/ebml/ebmlstringelement.cpp index 77526c24..5d8fd9cf 100644 --- a/taglib/matroska/ebml/ebmlstringelement.cpp +++ b/taglib/matroska/ebml/ebmlstringelement.cpp @@ -21,15 +21,13 @@ #include "ebmlstringelement.h" #include #include "tfile.h" -#include "tstring.h" #include "tbytevector.h" #include "tdebug.h" #include "ebmlutils.h" using namespace TagLib; -template -bool EBML::StringElement::read(File &file) +bool EBML::StringElement::read(File &file) { ByteVector buffer = file.readBlock(dataSize); if(buffer.size() != dataSize) { @@ -39,24 +37,18 @@ bool EBML::StringElement::read(File &file) // The EBML strings aren't supposed to be null-terminated, // but we'll check for it and strip the null terminator if found - int nullByte = buffer.find('\0'); - if(nullByte >= 0) + if(const int nullByte = buffer.find('\0'); nullByte >= 0) buffer = ByteVector(buffer.data(), nullByte); - value = String(buffer, t); + value = String(buffer, encoding); return true; } -template bool EBML::StringElement::read(File &file); -template bool EBML::StringElement::read(File &file); -template -ByteVector EBML::StringElement::render() +ByteVector EBML::StringElement::render() { ByteVector buffer = renderId(); - std::string string = value.to8Bit(t == String::UTF8); + const std::string string = value.to8Bit(encoding == String::UTF8); dataSize = string.size(); buffer.append(renderVINT(dataSize, 0)); buffer.append(ByteVector(string.data(), static_cast(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 258eb553..2aca07e7 100644 --- a/taglib/matroska/ebml/ebmlstringelement.h +++ b/taglib/matroska/ebml/ebmlstringelement.h @@ -23,40 +23,48 @@ #ifndef DO_NOT_DOCUMENT #include "ebmlelement.h" -#include "tbytevector.h" #include "tstring.h" namespace TagLib { class File; + class ByteVector; + namespace EBML { - template class StringElement : public Element { public: - StringElement(Id id, int sizeLength, offset_t dataSize) : - Element(id, sizeLength, dataSize) - { - } - StringElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - Element(id, sizeLength, dataSize) - { - } + StringElement(String::Type stringEncoding, Id id, int sizeLength, offset_t dataSize) : + Element(id, sizeLength, dataSize), encoding(stringEncoding) {} - explicit StringElement(Id id) : - Element(id, 0, 0) - { - } const String &getValue() const { return value; } - void setValue(const String &value) { this->value = value; } + void setValue(const String &val) { value = val; } bool read(File &file) override; ByteVector render() override; private: String value; + String::Type encoding; }; - using UTF8StringElement = StringElement; - using Latin1StringElement = StringElement; + class UTF8StringElement : public StringElement { + public: + UTF8StringElement(Id id, int sizeLength, offset_t dataSize) : + StringElement(String::UTF8, id, sizeLength, dataSize) {} + UTF8StringElement(Id id, int sizeLength, offset_t dataSize, offset_t) : + UTF8StringElement(id, sizeLength, dataSize) {} + explicit UTF8StringElement(Id id) : + UTF8StringElement(id, 0, 0) {} + }; + + class Latin1StringElement : public StringElement { + public: + Latin1StringElement(Id id, int sizeLength, offset_t dataSize) : + StringElement(String::Latin1, id, sizeLength, dataSize) {} + Latin1StringElement(Id id, int sizeLength, offset_t dataSize, offset_t) : + Latin1StringElement(id, sizeLength, dataSize) {} + explicit Latin1StringElement(Id id) : + Latin1StringElement(id, 0, 0) {} + }; } } diff --git a/taglib/matroska/ebml/ebmluintelement.cpp b/taglib/matroska/ebml/ebmluintelement.cpp index 39c38f0f..b78473ab 100644 --- a/taglib/matroska/ebml/ebmluintelement.cpp +++ b/taglib/matroska/ebml/ebmluintelement.cpp @@ -28,7 +28,7 @@ using namespace TagLib; bool EBML::UIntElement::read(File &file) { - ByteVector buffer = file.readBlock(dataSize); + const ByteVector buffer = file.readBlock(dataSize); if(buffer.size() != dataSize) { debug("Failed to read EBML Uint element"); return false; @@ -59,11 +59,11 @@ ByteVector EBML::UIntElement::render() ByteVector buffer = renderId(); buffer.append(renderVINT(dataSize, 0)); - uint64_t value = this->value; + uint64_t val = value; static const auto byteOrder = Utils::systemByteOrder(); if(byteOrder == Utils::LittleEndian) - value = Utils::byteSwap(value); + val = Utils::byteSwap(val); - buffer.append(ByteVector(reinterpret_cast(&value) + (sizeof(value) - dataSize), dataSize)); + buffer.append(ByteVector(reinterpret_cast(&val) + (sizeof(val) - dataSize), dataSize)); return buffer; } diff --git a/taglib/matroska/ebml/ebmluintelement.h b/taglib/matroska/ebml/ebmluintelement.h index efd6f32f..72f2dc1c 100644 --- a/taglib/matroska/ebml/ebmluintelement.h +++ b/taglib/matroska/ebml/ebmluintelement.h @@ -32,20 +32,14 @@ namespace TagLib { { public: UIntElement(Id id, int sizeLength, offset_t dataSize) : - Element(id, sizeLength, dataSize) - { - } + Element(id, sizeLength, dataSize) {} UIntElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - Element(id, sizeLength, dataSize) - { - } - + Element(id, sizeLength, dataSize) {} explicit UIntElement(Id id) : - UIntElement(id, 0, 0) - { - } + UIntElement(id, 0, 0) {} + unsigned long long getValue() const { return value; } - void setValue(unsigned long long value) { this->value = value; } + void setValue(unsigned long long val) { value = val; } bool read(File &file) override; ByteVector render() override; diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index 149109a1..d7b3c578 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -18,16 +18,11 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include #include "ebmlutils.h" -#include "ebmlelement.h" +#include #include "tbytevector.h" #include "matroskafile.h" -#include "tdebug.h" -#include "tutils.h" -#include "taglib.h" - using namespace TagLib; std::unique_ptr EBML::findElement(File &file, Element::Id id, offset_t maxOffset) @@ -63,8 +58,8 @@ std::pair EBML::readVINT(File &file) if(nb_bytes > 1) buffer.append(file.readBlock(nb_bytes - 1)); - int bits_to_shift = static_cast(sizeof(T) * 8) - 7 * nb_bytes; - offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; + const int bitsToShift = static_cast(sizeof(T) * 8) - 7 * nb_bytes; + offset_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift; return { nb_bytes, static_cast(buffer.toLongLong(true)) & mask }; } namespace TagLib::EBML { @@ -82,8 +77,8 @@ std::pair EBML::parseVINT(const ByteVector &buffer) if(!numBytes) return {0, 0}; - int bits_to_shift = static_cast(sizeof(T) * 8) - 7 * numBytes; - offset_t mask = 0xFFFFFFFFFFFFFFFF >> bits_to_shift; + const int bitsToShift = static_cast(sizeof(T) * 8) - 7 * numBytes; + offset_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift; return { numBytes, static_cast(buffer.toLongLong(true)) & mask }; } namespace TagLib::EBML { @@ -93,7 +88,7 @@ namespace TagLib::EBML { ByteVector EBML::renderVINT(uint64_t number, int minSizeLength) { - int numBytes = std::max(minSizeLength, minSize(number)); + const int numBytes = std::max(minSizeLength, minSize(number)); number |= 1ULL << (numBytes * 7); static const auto byteOrder = Utils::systemByteOrder(); if(byteOrder == Utils::LittleEndian) diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h index c418d632..20fd6cf9 100644 --- a/taglib/matroska/ebml/ebmlutils.h +++ b/taglib/matroska/ebml/ebmlutils.h @@ -27,7 +27,6 @@ #include "tutils.h" #include "tdebug.h" #include "ebmlelement.h" -#include "tbytevector.h" namespace TagLib { class File; @@ -68,29 +67,27 @@ namespace TagLib { { if(data <= 0x7Fu) return 1; - else if(data <= 0x3FFFu) + if(data <= 0x3FFFu) return 2; - else if(data <= 0x1FFFFFu) + if(data <= 0x1FFFFFu) return 3; - else if(data <= 0xFFFFFFFu) + if(data <= 0xFFFFFFFu) return 4; - else if(data <= 0x7FFFFFFFFu) + if(data <= 0x7FFFFFFFFu) return 5; - else - return 0; + return 0; } constexpr int idSize(Element::Id id) { - auto uintId = static_cast(id); + const auto uintId = static_cast(id); if(uintId <= 0xFF) return 1; - else if(uintId <= 0xFFFF) + if(uintId <= 0xFFFF) return 2; - else if(uintId <= 0xFFFFFF) + if(uintId <= 0xFFFFFF) return 3; - else - return 4; + return 4; } } } diff --git a/taglib/matroska/ebml/ebmlvoidelement.cpp b/taglib/matroska/ebml/ebmlvoidelement.cpp index 3377c327..d764e4a2 100644 --- a/taglib/matroska/ebml/ebmlvoidelement.cpp +++ b/taglib/matroska/ebml/ebmlvoidelement.cpp @@ -19,11 +19,10 @@ ***************************************************************************/ #include "ebmlvoidelement.h" +#include #include "ebmlutils.h" #include "tbytevector.h" -#include - using namespace TagLib; ByteVector EBML::VoidElement::render() @@ -46,9 +45,9 @@ offset_t EBML::VoidElement::getTargetSize() const return targetSize; } -void EBML::VoidElement::setTargetSize(offset_t targetSize) +void EBML::VoidElement::setTargetSize(offset_t size) { - this->targetSize = std::max(targetSize, MIN_VOID_ELEMENT_SIZE); + this->targetSize = std::max(size, MIN_VOID_ELEMENT_SIZE); } ByteVector EBML::VoidElement::renderSize(offset_t targetSize) diff --git a/taglib/matroska/ebml/ebmlvoidelement.h b/taglib/matroska/ebml/ebmlvoidelement.h index 9b6864ac..d4e4e57f 100644 --- a/taglib/matroska/ebml/ebmlvoidelement.h +++ b/taglib/matroska/ebml/ebmlvoidelement.h @@ -33,17 +33,15 @@ namespace TagLib { { public: VoidElement(int sizeLength, offset_t dataSize) : - Element(Id::VoidElement, sizeLength, dataSize) - {} + Element(Id::VoidElement, sizeLength, dataSize) {} VoidElement(Id, int sizeLength, offset_t dataSize, offset_t) : - Element(Id::VoidElement, sizeLength, dataSize) - {} + Element(Id::VoidElement, sizeLength, dataSize) {} VoidElement() : - Element(Id::VoidElement, 0, 0) - {} + Element(Id::VoidElement, 0, 0) {} + ByteVector render() override; offset_t getTargetSize() const; - void setTargetSize(offset_t targetSize); + void setTargetSize(offset_t size); static ByteVector renderSize(offset_t targetSize); private: diff --git a/taglib/matroska/matroskaattachedfile.cpp b/taglib/matroska/matroskaattachedfile.cpp index 06c6f4f3..f9fdf9bf 100644 --- a/taglib/matroska/matroskaattachedfile.cpp +++ b/taglib/matroska/matroskaattachedfile.cpp @@ -1,5 +1,5 @@ /*************************************************************************** -* This library is free software; you can redistribute it and/or modify * + * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License version * * 2.1 as published by the Free Software Foundation. * * * @@ -50,11 +50,11 @@ Matroska::AttachedFile::AttachedFile(const AttachedFile &other) : { } -Matroska::AttachedFile::AttachedFile(AttachedFile&& other) noexcept = default; +Matroska::AttachedFile::AttachedFile(AttachedFile &&other) noexcept = default; Matroska::AttachedFile::~AttachedFile() = default; -Matroska::AttachedFile &Matroska::AttachedFile::operator=(AttachedFile &&other) = default; +Matroska::AttachedFile &Matroska::AttachedFile::operator=(AttachedFile &&other) noexcept = default; Matroska::AttachedFile &Matroska::AttachedFile::operator=(const AttachedFile &other) { diff --git a/taglib/matroska/matroskaattachedfile.h b/taglib/matroska/matroskaattachedfile.h index 14cb5475..b7afcd16 100644 --- a/taglib/matroska/matroskaattachedfile.h +++ b/taglib/matroska/matroskaattachedfile.h @@ -32,7 +32,12 @@ namespace TagLib { class TAGLIB_EXPORT AttachedFile { public: + //! Unique identifier. using UID = unsigned long long; + + /*! + * Construct an attached file. + */ AttachedFile(); /*! @@ -58,7 +63,7 @@ namespace TagLib { /*! * Moves the contents of \a other into this object. */ - AttachedFile &operator=(AttachedFile &&other); + AttachedFile &operator=(AttachedFile &&other) noexcept; /*! * Exchanges the content of the object with the content of \a other. diff --git a/taglib/matroska/matroskaattachments.cpp b/taglib/matroska/matroskaattachments.cpp index 2a0ad9d7..5bdee484 100644 --- a/taglib/matroska/matroskaattachments.cpp +++ b/taglib/matroska/matroskaattachments.cpp @@ -1,5 +1,5 @@ /*************************************************************************** -* This library is free software; you can redistribute it and/or modify * + * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License version * * 2.1 as published by the Free Software Foundation. * * * @@ -19,13 +19,13 @@ ***************************************************************************/ #include "matroskaattachments.h" -#include #include "matroskaattachedfile.h" #include "ebmlmkattachments.h" #include "ebmlmasterelement.h" #include "ebmlstringelement.h" #include "ebmlbinaryelement.h" #include "ebmluintelement.h" +#include "ebmlutils.h" #include "tlist.h" #include "tbytevector.h" @@ -53,7 +53,7 @@ Matroska::Attachments::Attachments() : Matroska::Attachments::~Attachments() = default; -void Matroska::Attachments::addAttachedFile(const AttachedFile& file) +void Matroska::Attachments::addAttachedFile(const AttachedFile &file) { d->files.append(file); setNeedsRender(true); @@ -61,8 +61,8 @@ void Matroska::Attachments::addAttachedFile(const AttachedFile& file) void Matroska::Attachments::removeAttachedFile(unsigned long long uid) { - auto it = std::find_if(d->files.begin(), d->files.end(), - [uid](const AttachedFile& file) { + const auto it = std::find_if(d->files.begin(), d->files.end(), + [uid](const AttachedFile &file) { return file.uid() == uid; }); if(it != d->files.end()) { @@ -109,14 +109,15 @@ ByteVector Matroska::Attachments::renderInternal() attachedFileElement->appendElement(std::move(fileNameElement)); // Media/MIME type - auto mediaTypeElement = EBML::make_unique_element(); + auto mediaTypeElement = + EBML::make_unique_element(); mediaTypeElement->setValue(attachedFile.mediaType()); attachedFileElement->appendElement(std::move(mediaTypeElement)); // Description - const String &description = attachedFile.description(); - if(!description.isEmpty()) { - auto descriptionElement = EBML::make_unique_element(); + if(const String &description = attachedFile.description(); !description.isEmpty()) { + auto descriptionElement = + EBML::make_unique_element(); descriptionElement->setValue(description); attachedFileElement->appendElement(std::move(descriptionElement)); } diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index d7296057..ba586684 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -42,11 +42,13 @@ namespace TagLib { #endif { public: + //! List of attached files. using AttachedFileList = List; + //! Construct attachments. Attachments(); - //! Destroy attachements. + //! Destroy attachments. virtual ~Attachments(); //! Add an attached file. @@ -55,7 +57,7 @@ namespace TagLib { //! Remove an attached file. void removeAttachedFile(unsigned long long uid); - //! Remove an attached file. + //! Remove all attached files. void clear(); //! Get list of all attached files. diff --git a/taglib/matroska/matroskachapter.cpp b/taglib/matroska/matroskachapter.cpp index 6e4c1b4e..4867ec25 100644 --- a/taglib/matroska/matroskachapter.cpp +++ b/taglib/matroska/matroskachapter.cpp @@ -4,7 +4,7 @@ ***************************************************************************/ /*************************************************************************** -* This library is free software; you can redistribute it and/or modify * + * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License version * * 2.1 as published by the Free Software Foundation. * * * @@ -109,7 +109,7 @@ bool Matroska::Chapter::isHidden() const return d->hidden; } -const List& Matroska::Chapter::displayList() const +const List &Matroska::Chapter::displayList() const { return d->displayList; } @@ -130,13 +130,14 @@ Matroska::Chapter::Display::Display(Display &&other) noexcept = default; Matroska::Chapter::Display::~Display() = default; -Matroska::Chapter::Display& Matroska::Chapter::Display::operator=(const Display &other) +Matroska::Chapter::Display &Matroska::Chapter::Display::operator=(const Display &other) { Display(other).swap(*this); return *this; } -Matroska::Chapter::Display& Matroska::Chapter::Display::operator=(Display &&other) noexcept = default; +Matroska::Chapter::Display &Matroska::Chapter::Display::operator=( + Display &&other) noexcept = default; void Matroska::Chapter::Display::swap(Display &other) noexcept { @@ -145,12 +146,12 @@ void Matroska::Chapter::Display::swap(Display &other) noexcept swap(d, other.d); } -const String& Matroska::Chapter::Display::string() const +const String &Matroska::Chapter::Display::string() const { return d->string; } -const String& Matroska::Chapter::Display::language() const +const String &Matroska::Chapter::Display::language() const { return d->language; } diff --git a/taglib/matroska/matroskachapter.h b/taglib/matroska/matroskachapter.h index 9135b54f..df030d8a 100644 --- a/taglib/matroska/matroskachapter.h +++ b/taglib/matroska/matroskachapter.h @@ -31,18 +31,22 @@ #include "tlist.h" namespace TagLib { + class String; + class ByteVector; + namespace EBML { class MkChapters; } - class String; - class ByteVector; namespace Matroska { //! Matroska chapter. class TAGLIB_EXPORT Chapter { public: + //! Unique identifier. using UID = unsigned long long; + + //! Timestamp in nanoseconds. using Time = unsigned long long; /*! diff --git a/taglib/matroska/matroskachapteredition.cpp b/taglib/matroska/matroskachapteredition.cpp index 2a2293a5..2e9f733c 100644 --- a/taglib/matroska/matroskachapteredition.cpp +++ b/taglib/matroska/matroskachapteredition.cpp @@ -4,7 +4,7 @@ ***************************************************************************/ /*************************************************************************** -* This library is free software; you can redistribute it and/or modify * + * This library is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License version * * 2.1 as published by the Free Software Foundation. * * * @@ -61,11 +61,12 @@ Matroska::ChapterEdition::ChapterEdition(const ChapterEdition &other) : { } -Matroska::ChapterEdition::ChapterEdition(ChapterEdition&& other) noexcept = default; +Matroska::ChapterEdition::ChapterEdition(ChapterEdition &&other) noexcept = default; Matroska::ChapterEdition::~ChapterEdition() = default; -Matroska::ChapterEdition &Matroska::ChapterEdition::operator=(ChapterEdition &&other) = default; +Matroska::ChapterEdition &Matroska::ChapterEdition::operator=( + ChapterEdition &&other) noexcept = default; Matroska::ChapterEdition &Matroska::ChapterEdition::operator=(const ChapterEdition &other) { diff --git a/taglib/matroska/matroskachapteredition.h b/taglib/matroska/matroskachapteredition.h index 6ed4c789..3d8758f3 100644 --- a/taglib/matroska/matroskachapteredition.h +++ b/taglib/matroska/matroskachapteredition.h @@ -31,11 +31,13 @@ namespace TagLib { class String; class ByteVector; + namespace Matroska { //! Edition of chapters. class TAGLIB_EXPORT ChapterEdition { public: + //! Unique identifier. using UID = unsigned long long; /*! @@ -67,7 +69,7 @@ namespace TagLib { /*! * Moves the contents of \a other into this object. */ - ChapterEdition &operator=(ChapterEdition &&other); + ChapterEdition &operator=(ChapterEdition &&other) noexcept; /*! * Exchanges the content of the object with the content of \a other. diff --git a/taglib/matroska/matroskachapters.cpp b/taglib/matroska/matroskachapters.cpp index 8f94525e..4b88cecc 100644 --- a/taglib/matroska/matroskachapters.cpp +++ b/taglib/matroska/matroskachapters.cpp @@ -24,7 +24,6 @@ ***************************************************************************/ #include "matroskachapters.h" -#include #include "matroskachapteredition.h" #include "ebmlstringelement.h" #include "ebmlbinaryelement.h" @@ -58,7 +57,7 @@ Matroska::Chapters::Chapters() : Matroska::Chapters::~Chapters() = default; -void Matroska::Chapters::addChapterEdition(const ChapterEdition& edition) +void Matroska::Chapters::addChapterEdition(const ChapterEdition &edition) { d->editions.append(edition); setNeedsRender(true); @@ -66,8 +65,8 @@ void Matroska::Chapters::addChapterEdition(const ChapterEdition& edition) void Matroska::Chapters::removeChapterEdition(unsigned long long uid) { - auto it = std::find_if(d->editions.begin(), d->editions.end(), - [uid](const ChapterEdition& file) { + const auto it = std::find_if(d->editions.begin(), d->editions.end(), + [uid](const ChapterEdition &file) { return file.uid() == uid; }); if(it != d->editions.end()) { @@ -101,7 +100,7 @@ ByteVector Matroska::Chapters::renderInternal() for(const auto &chapterEdition : std::as_const(d->editions)) { auto chapterEditionElement = EBML::make_unique_element(); - if(auto uid = chapterEdition.uid()) { + if(const auto uid = chapterEdition.uid()) { auto uidElement = EBML::make_unique_element(); uidElement->setValue(uid); chapterEditionElement->appendElement(std::move(uidElement)); @@ -117,7 +116,7 @@ ByteVector Matroska::Chapters::renderInternal() auto chapterElement = EBML::make_unique_element(); auto cuidElement = EBML::make_unique_element(); - auto cuid = chapter.uid(); + const auto cuid = chapter.uid(); cuidElement->setValue(cuid ? cuid : EBML::randomUID()); chapterElement->appendElement(std::move(cuidElement)); auto timeStartElement = EBML::make_unique_element(); @@ -130,7 +129,7 @@ ByteVector Matroska::Chapters::renderInternal() hiddenElement->setValue(chapter.isHidden()); chapterElement->appendElement(std::move(hiddenElement)); - for(const auto& display : chapter.displayList()) { + for(const auto &display : chapter.displayList()) { auto displayElement = EBML::make_unique_element(); auto stringElement = EBML::make_unique_element(); diff --git a/taglib/matroska/matroskachapters.h b/taglib/matroska/matroskachapters.h index c235b74f..f033340f 100644 --- a/taglib/matroska/matroskachapters.h +++ b/taglib/matroska/matroskachapters.h @@ -33,6 +33,7 @@ namespace TagLib { class File; + namespace EBML { class MkChapters; } @@ -47,7 +48,9 @@ namespace TagLib { #endif { public: + //! List of chapter editions. using ChapterEditionList = List; + //! Construct chapters. Chapters(); diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp index d4390a5c..6be02904 100644 --- a/taglib/matroska/matroskacues.cpp +++ b/taglib/matroska/matroskacues.cpp @@ -38,7 +38,7 @@ Matroska::Cues::Cues(offset_t segmentDataOffset) : ByteVector Matroska::Cues::renderInternal() { - auto beforeSize = sizeRenderedOrWritten(); + const auto beforeSize = sizeRenderedOrWritten(); EBML::MkCues cues; cues.setMinRenderSize(beforeSize); for(const auto &cuePoint : cuePoints) { @@ -90,10 +90,9 @@ ByteVector Matroska::Cues::renderInternal() } // Reference times - auto referenceTimes = cueTrack->referenceTimes(); - if(!referenceTimes.isEmpty()) { + if(auto referenceTimes = cueTrack->referenceTimes(); !referenceTimes.isEmpty()) { auto cueReference = EBML::make_unique_element(); - for(auto reference : referenceTimes) { + for(const auto reference : referenceTimes) { auto refTime = EBML::make_unique_element(); refTime->setValue(reference); cueReference->appendElement(std::move(refTime)); @@ -107,7 +106,7 @@ ByteVector Matroska::Cues::renderInternal() return cues.render(); } -void Matroska::Cues::write(TagLib::File& file) +void Matroska::Cues::write(TagLib::File &file) { if(!data().isEmpty()) Element::write(file); @@ -119,8 +118,8 @@ bool Matroska::Cues::sizeChanged(Element &caller, offset_t delta) if(!Element::sizeChanged(caller, delta)) return false; - offset_t offset = caller.offset() - segmentDataOffset; - for(auto &cuePoint : cuePoints) { + const offset_t offset = caller.offset() - segmentDataOffset; + for(const auto &cuePoint : cuePoints) { if(cuePoint->adjustOffset(offset, delta)) { setNeedsRender(true); } @@ -142,9 +141,7 @@ void Matroska::Cues::addCuePoint(std::unique_ptr &&cuePoint) cuePoints.push_back(std::move(cuePoint)); } -Matroska::CuePoint::CuePoint() -{ -} +Matroska::CuePoint::CuePoint() = default; bool Matroska::CuePoint::isValid(TagLib::File &file, offset_t segmentDataOffset) const { @@ -163,7 +160,7 @@ void Matroska::CuePoint::addCueTrack(std::unique_ptr &&cueTrack) bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta) { bool ret = false; - for(auto &cueTrack : cueTracks) + for(const auto &cueTrack : cueTracks) ret |= cueTrack->adjustOffset(offset, delta); return ret; @@ -201,8 +198,8 @@ bool Matroska::CueTrack::adjustOffset(offset_t offset, offset_t delta) clusterPosition += delta; ret = true; } - offset_t codecStateValue; - if(codecState.has_value() && (codecStateValue = codecState.value()) != 0 && + if(offset_t codecStateValue; + codecState.has_value() && (codecStateValue = codecState.value()) != 0 && codecStateValue > offset) { codecState = codecStateValue + delta; ret = true; diff --git a/taglib/matroska/matroskacues.h b/taglib/matroska/matroskacues.h index ee971d04..7241f8f3 100644 --- a/taglib/matroska/matroskacues.h +++ b/taglib/matroska/matroskacues.h @@ -29,6 +29,7 @@ namespace TagLib { class File; + namespace EBML { class MkCues; } @@ -36,6 +37,7 @@ namespace TagLib { namespace Matroska { class CuePoint; class CueTrack; + class Cues : public Element { public: @@ -66,7 +68,7 @@ namespace TagLib { bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; void addCueTrack(std::unique_ptr &&cueTrack); const CueTrackList &cueTrackList() const { return cueTracks; } - void setTime(Time time) { this->time = time; } + void setTime(Time timestamp) { time = timestamp; } Time getTime() const { return time; } bool adjustOffset(offset_t offset, offset_t delta); @@ -82,17 +84,17 @@ namespace TagLib { CueTrack() = default; ~CueTrack() = default; bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; - void setTrackNumber(unsigned long long trackNumber) { this->trackNumber = trackNumber; } + void setTrackNumber(unsigned long long trackNr) { trackNumber = trackNr; } unsigned long long getTrackNumber() const { return trackNumber; } - void setClusterPosition(offset_t clusterPosition) { this->clusterPosition = clusterPosition; } + void setClusterPosition(offset_t clusterPos) { clusterPosition = clusterPos; } offset_t getClusterPosition() const { return clusterPosition; } - void setRelativePosition(std::optional relativePosition) { this->relativePosition = relativePosition; } + void setRelativePosition(std::optional relativePos) { relativePosition = relativePos; } std::optional getRelativePosition() const { return relativePosition; } - void setCodecState(std::optional codecState) { this->codecState = codecState; } + void setCodecState(std::optional codecStatePos) { codecState = codecStatePos; } std::optional getCodecState() const { return codecState; } - void setBlockNumber(std::optional blockNumber) { this->blockNumber = blockNumber; } + void setBlockNumber(std::optional blockNr) { blockNumber = blockNr; } std::optional getBlockNumber() const { return blockNumber; } - void setDuration(std::optional duration) { this->duration = duration; } + void setDuration(std::optional segmentTicks) { duration = segmentTicks; } std::optional getDuration() const { return duration; } void addReferenceTime(unsigned long long refTime) { refTimes.append(refTime); } const ReferenceTimeList &referenceTimes() const { return refTimes; } diff --git a/taglib/matroska/matroskaelement.cpp b/taglib/matroska/matroskaelement.cpp index 17c978d1..f1f6b63a 100644 --- a/taglib/matroska/matroskaelement.cpp +++ b/taglib/matroska/matroskaelement.cpp @@ -17,8 +17,9 @@ * License Version 1.1. You may obtain a copy of the License at * * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include + #include "matroskaelement.h" +#include #include "tlist.h" #include "tfile.h" #include "tbytevector.h" @@ -48,6 +49,7 @@ Matroska::Element::Element(ID id) : { e->id = id; } + Matroska::Element::~Element() = default; offset_t Matroska::Element::size() const @@ -110,11 +112,10 @@ bool Matroska::Element::render() if(!needsRender()) return true; - auto beforeSize = sizeRenderedOrWritten(); - auto data = renderInternal(); + const auto beforeSize = sizeRenderedOrWritten(); + const auto data = renderInternal(); setNeedsRender(false); - auto afterSize = data.size(); - if(afterSize != beforeSize) { + if(const auto afterSize = data.size(); afterSize != beforeSize) { if(!emitSizeChanged(afterSize - beforeSize)) { return false; } @@ -136,7 +137,7 @@ bool Matroska::Element::needsRender() const bool Matroska::Element::emitSizeChanged(offset_t delta) { - for(auto element : e->sizeListeners) { + for(const auto element : e->sizeListeners) { if(!element->sizeChanged(*this, delta)) return false; } @@ -156,7 +157,7 @@ bool Matroska::Element::sizeChanged(Element &caller, offset_t delta) offset_t Matroska::Element::sizeRenderedOrWritten() const { - offset_t dataSize = e->data.size(); + const offset_t dataSize = e->data.size(); return dataSize != 0 ? dataSize : e->size; } diff --git a/taglib/matroska/matroskaelement.h b/taglib/matroska/matroskaelement.h index f78f8f56..ad5efdc8 100644 --- a/taglib/matroska/matroskaelement.h +++ b/taglib/matroska/matroskaelement.h @@ -25,11 +25,12 @@ #include #include "taglib_export.h" #include "taglib.h" -#include "tbytevector.h" #include "tlist.h" namespace TagLib { class File; + class ByteVector; + namespace Matroska { class TAGLIB_EXPORT Element { diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index 2cf83a4e..e27f389d 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -19,6 +19,7 @@ ***************************************************************************/ #include "matroskafile.h" +#include #include "matroskatag.h" #include "matroskaattachments.h" #include "matroskaattachedfile.h" @@ -38,8 +39,6 @@ #include "tagutils.h" #include "tpropertymap.h" -#include - using namespace TagLib; class Matroska::File::FilePrivate @@ -177,8 +176,8 @@ StringList Matroska::File::complexPropertyKeys() const if(d->attachments) { const auto &attachedFiles = d->attachments->attachedFileList(); for(const auto &attachedFile : attachedFiles) { - String key = keyForAttachedFile(attachedFile); - if(!key.isEmpty() && !keys.contains(key)) { + if(String key = keyForAttachedFile(attachedFile); + !key.isEmpty() && !keys.contains(key)) { keys.append(key); } } @@ -196,27 +195,27 @@ List Matroska::File::complexProperties(const String &key) const if(d->chapters) { for(const auto &edition : d->chapters->chapterEditionList()) { VariantMap property; - if(auto uid = edition.uid()) { + if(const auto uid = edition.uid()) { property.insert("uid", uid); } - if(auto isDefault = edition.isDefault()) { + if(const auto isDefault = edition.isDefault()) { property.insert("isDefault", isDefault); } - if(auto isOrdered = edition.isOrdered()) { + if(const auto isOrdered = edition.isOrdered()) { property.insert("isOrdered", isOrdered); } if(auto chapters = edition.chapterList(); !chapters.isEmpty()) { VariantList chaps; for(const auto &chapter : chapters) { VariantMap chap; - if(auto uid = chapter.uid()) { + if(const auto uid = chapter.uid()) { chap.insert("uid", uid); } - if(auto isHidden = chapter.isHidden()) { + if(const auto isHidden = chapter.isHidden()) { chap.insert("isHidden", isHidden); } chap.insert("timeStart", chapter.timeStart()); - if(auto timeEnd = chapter.timeEnd()) { + if(const auto timeEnd = chapter.timeEnd()) { chap.insert("timeEnd", timeEnd); } if(auto displays = chapter.displayList(); !displays.isEmpty()) { @@ -313,7 +312,6 @@ bool Matroska::File::setComplexProperties(const String &key, const List(); auto uid = property.value("uid").value(); bool ok; - unsigned long long uidKey; if(key.upper() == "PICTURE" && !mimeType.startsWith("image/")) { mimeType = data.startsWith("\x89PNG\x0d\x0a\x1a\x0a") ? "image/png" : "image/jpeg"; @@ -324,11 +322,12 @@ bool Matroska::File::setComplexProperties(const String &key, const List( + const auto head = EBML::element_cast( EBML::Element::factory(*this)); if(!head || head->getId() != EBML::Element::Id::EBMLHeader) { debug("Failed to find EBML head"); @@ -382,7 +381,7 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle) } // Find the Matroska segment in the file - std::unique_ptr segment( + const std::unique_ptr segment( EBML::element_cast( EBML::findElement(*this, EBML::Element::Id::MkSegment, fileLength - tell()) ) @@ -412,12 +411,11 @@ void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle) d->properties = std::make_unique(this); for(const auto &element : *head) { - auto id = element->getId(); - if (id == EBML::Element::Id::DocType) { + if(const auto id = element->getId(); id == EBML::Element::Id::DocType) { d->properties->setDocType( EBML::element_cast(element)->getValue()); } - else if (id == EBML::Element::Id::DocTypeVersion) { + else if(id == EBML::Element::Id::DocTypeVersion) { d->properties->setDocTypeVersion(static_cast( EBML::element_cast(element)->getValue())); } @@ -505,8 +503,8 @@ bool Matroska::File::save() // Add our new elements to the Seek Head (if the file has one) if(d->seekHead) { - auto segmentDataOffset = d->segment->dataOffset(); - for(auto element : newElements) + const auto segmentDataOffset = d->segment->dataOffset(); + for(const auto element : newElements) d->seekHead->addEntry(element->id(), element->offset() - segmentDataOffset); d->seekHead->sort(); } @@ -545,7 +543,7 @@ bool Matroska::File::save() bool rendering = true; while(rendering && renderRound < 5) { rendering = false; - for(auto element : renderList) { + for(const auto element : renderList) { if(element->needsRender()) { rendering = true; if(!element->render()) { @@ -558,7 +556,7 @@ bool Matroska::File::save() // Write out to file renderList.sort(sortAscending); - for(auto element : renderList) + for(const auto element : renderList) element->write(*this); return true; diff --git a/taglib/matroska/matroskafile.h b/taglib/matroska/matroskafile.h index 71cd9e7c..559be5aa 100644 --- a/taglib/matroska/matroskafile.h +++ b/taglib/matroska/matroskafile.h @@ -23,11 +23,9 @@ #include "taglib_export.h" #include "tfile.h" -#include "tag.h" #include "matroskaproperties.h" namespace TagLib::Matroska { - class Properties; class Tag; class Attachments; class Chapters; diff --git a/taglib/matroska/matroskaproperties.cpp b/taglib/matroska/matroskaproperties.cpp index c1c2a3c5..fe37defb 100644 --- a/taglib/matroska/matroskaproperties.cpp +++ b/taglib/matroska/matroskaproperties.cpp @@ -69,7 +69,7 @@ int Matroska::Properties::lengthInMilliseconds() const int Matroska::Properties::bitrate() const { - if (d->bitrate == -1) { + if(d->bitrate == -1) { d->bitrate = d->length != 0 ? static_cast(d->file->length() * 8 / d->length) : 0; } return d->bitrate; @@ -149,7 +149,7 @@ void Matroska::Properties::setCodecName(const String &codecName) d->codecName = codecName; } -void Matroska::Properties::setTitle(const String& title) +void Matroska::Properties::setTitle(const String &title) { d->title = title; } diff --git a/taglib/matroska/matroskaseekhead.cpp b/taglib/matroska/matroskaseekhead.cpp index f297335e..88ad1d7e 100644 --- a/taglib/matroska/matroskaseekhead.cpp +++ b/taglib/matroska/matroskaseekhead.cpp @@ -23,6 +23,8 @@ #include "ebmlbinaryelement.h" #include "ebmluintelement.h" #include "ebmlmasterelement.h" +#include "tfile.h" +#include "tutils.h" #include "tdebug.h" using namespace TagLib; @@ -34,7 +36,7 @@ Matroska::SeekHead::SeekHead(offset_t segmentDataOffset) : setNeedsRender(false); } -bool Matroska::SeekHead::isValid(TagLib::File& file) const +bool Matroska::SeekHead::isValid(TagLib::File &file) const { bool result = true; for(const auto &[id, offset] : entries) { @@ -62,7 +64,7 @@ void Matroska::SeekHead::addEntry(ID id, offset_t offset) ByteVector Matroska::SeekHead::renderInternal() { - auto beforeSize = sizeRenderedOrWritten(); + const auto beforeSize = sizeRenderedOrWritten(); EBML::MkSeekHead seekHead; seekHead.setMinRenderSize(beforeSize); for(const auto &[id, position] : entries) { @@ -98,34 +100,32 @@ bool Matroska::SeekHead::sizeChanged(Element &caller, offset_t delta) adjustOffset(delta); return true; } - else { - // The equal case is needed when multiple new elements are added - // (e.g. Attachments and Tags), they will start with the same offset - // and are updated via size change handling. - offset_t offset = caller.offset() - segmentDataOffset; - auto it = entries.begin(); - while(it != entries.end()) { - it = std::find_if(it, - entries.end(), - [offset, callerID](const auto &a) { - return a.second >= offset && a.first != callerID; - } - ); - if(it != entries.end()) { - it->second += delta; - setNeedsRender(true); - ++it; + // The equal case is needed when multiple new elements are added + // (e.g. Attachments and Tags), they will start with the same offset + // and are updated via size change handling. + offset_t offset = caller.offset() - segmentDataOffset; + auto it = entries.begin(); + while(it != entries.end()) { + it = std::find_if(it, + entries.end(), + [offset, callerID](const auto &a) { + return a.second >= offset && a.first != callerID; } + ); + if(it != entries.end()) { + it->second += delta; + setNeedsRender(true); + ++it; } - - if(caller.data().isEmpty() && caller.size() + delta == 0) { - // The caller element is removed, remove it from the seek head. - it = std::find_if(entries.begin(), entries.end(), - [callerID](const auto &a){ return a.first == callerID; }); - if(it != entries.end()) { - entries.erase(it); - } - } - return true; } + + if(caller.data().isEmpty() && caller.size() + delta == 0) { + // The caller element is removed, remove it from the seek head. + it = std::find_if(entries.begin(), entries.end(), + [callerID](const auto &a){ return a.first == callerID; }); + if(it != entries.end()) { + entries.erase(it); + } + } + return true; } diff --git a/taglib/matroska/matroskaseekhead.h b/taglib/matroska/matroskaseekhead.h index c9c23034..0775cf1e 100644 --- a/taglib/matroska/matroskaseekhead.h +++ b/taglib/matroska/matroskaseekhead.h @@ -23,17 +23,19 @@ #ifndef DO_NOT_DOCUMENT #include "matroskaelement.h" -#include "tbytevector.h" #include "tlist.h" namespace TagLib { class File; + class ByteVector; + namespace Matroska { class SeekHead : public Element { public: explicit SeekHead(offset_t segmentDataOffset); ~SeekHead() override = default; + bool isValid(TagLib::File &file) const; void addEntry(const Element &element); void addEntry(ID id, offset_t offset); diff --git a/taglib/matroska/matroskasegment.cpp b/taglib/matroska/matroskasegment.cpp index 19c32800..1c0e91b5 100644 --- a/taglib/matroska/matroskasegment.cpp +++ b/taglib/matroska/matroskasegment.cpp @@ -38,11 +38,10 @@ ByteVector Matroska::Segment::renderInternal() bool Matroska::Segment::render() { - auto beforeSize = sizeLength; + const auto beforeSize = sizeLength; auto data = renderInternal(); setNeedsRender(false); - auto afterSize = data.size(); - if(afterSize != beforeSize) { + if(auto afterSize = data.size(); afterSize != beforeSize) { sizeLength = 8; data = renderInternal(); setNeedsRender(false); diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp index 2c5209fa..a76833bd 100644 --- a/taglib/matroska/matroskasimpletag.cpp +++ b/taglib/matroska/matroskasimpletag.cpp @@ -21,7 +21,6 @@ #include "matroskasimpletag.h" #include #include "matroskatag.h" -#include "tstring.h" #include "tbytevector.h" using namespace TagLib; @@ -29,16 +28,17 @@ using namespace TagLib; class Matroska::SimpleTag::SimpleTagPrivate { public: - explicit SimpleTagPrivate(const String &name, const String& value, + explicit SimpleTagPrivate(const String &name, const String &value, TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage, unsigned long long trackUid) : value(value), name(name), language(language), trackUid(trackUid), targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {} - explicit SimpleTagPrivate(const String &name, const ByteVector& value, + explicit SimpleTagPrivate(const String &name, const ByteVector &value, TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage, unsigned long long trackUid) : value(value), name(name), language(language), trackUid(trackUid), targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {} + const std::variant value; const String name; const String language; @@ -74,11 +74,11 @@ Matroska::SimpleTag::SimpleTag(const SimpleTag &other) : { } -Matroska::SimpleTag::SimpleTag(SimpleTag&& other) noexcept = default; +Matroska::SimpleTag::SimpleTag(SimpleTag &&other) noexcept = default; Matroska::SimpleTag::~SimpleTag() = default; -Matroska::SimpleTag &Matroska::SimpleTag::operator=(SimpleTag &&other) = default; +Matroska::SimpleTag &Matroska::SimpleTag::operator=(SimpleTag &&other) noexcept = default; Matroska::SimpleTag &Matroska::SimpleTag::operator=(const SimpleTag &other) { diff --git a/taglib/matroska/matroskasimpletag.h b/taglib/matroska/matroskasimpletag.h index faff96e0..4edd8b45 100644 --- a/taglib/matroska/matroskasimpletag.h +++ b/taglib/matroska/matroskasimpletag.h @@ -22,11 +22,13 @@ #define TAGLIB_MATROSKASIMPLETAG_H #include -#include "tag.h" + +#include "tstring.h" namespace TagLib { class String; class ByteVector; + namespace Matroska { //! Attribute of Matroska metadata. class TAGLIB_EXPORT SimpleTag @@ -89,7 +91,7 @@ namespace TagLib { /*! * Moves the contents of \a other into this item. */ - SimpleTag &operator=(SimpleTag &&other); + SimpleTag &operator=(SimpleTag &&other) noexcept; /*! * Exchanges the content of the simple tag with the content of \a other. diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index d2b5b6be..c9d260e7 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -22,7 +22,6 @@ #include #include #include -#include "matroskasimpletag.h" #include "ebmlmasterelement.h" #include "ebmlstringelement.h" #include "ebmlbinaryelement.h" @@ -30,7 +29,6 @@ #include "ebmluintelement.h" #include "ebmlutils.h" #include "tpropertymap.h" -#include "tlist.h" using namespace TagLib; @@ -95,7 +93,7 @@ void Matroska::Tag::removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue, unsigned long long trackUid) { - auto it = std::find_if(d->tags.begin(), d->tags.end(), + const auto it = std::find_if(d->tags.begin(), d->tags.end(), [&name, targetTypeValue, trackUid](const SimpleTag &t) { return t.name() == name && t.targetTypeValue() == targetTypeValue && t.trackUid() == trackUid; @@ -118,14 +116,14 @@ const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsList() const return d->tags; } -void Matroska::Tag::setSegmentTitle(const String& title) +void Matroska::Tag::setSegmentTitle(const String &title) { d->segmentTitle = title; } bool Matroska::Tag::setTag(const String &key, const String &value) { - bool found = d->setTag(key, value); + const bool found = d->setTag(key, value); if(found) { setNeedsRender(true); } @@ -198,7 +196,7 @@ String Matroska::Tag::genre() const unsigned int Matroska::Tag::year() const { - auto value = d->getTag("DATE"); + const auto value = d->getTag("DATE"); if(value.isEmpty()) return 0; auto list = value.split("-"); @@ -207,7 +205,7 @@ unsigned int Matroska::Tag::year() const unsigned int Matroska::Tag::track() const { - auto value = d->getTag("TRACKNUMBER"); + const auto value = d->getTag("TRACKNUMBER"); if(value.isEmpty()) return 0; auto list = value.split("-"); @@ -251,8 +249,8 @@ ByteVector Matroska::Tag::renderInternal() } for(const auto &list : targetList) { const auto &frontTag = list.front(); - auto targetTypeValue = frontTag.targetTypeValue(); - auto trackUid = frontTag.trackUid(); + const auto targetTypeValue = frontTag.targetTypeValue(); + const auto trackUid = frontTag.trackUid(); auto tag = EBML::make_unique_element(); // Build element @@ -326,7 +324,6 @@ namespace std::tuple("TRACKTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("DISCTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Album, true), std::tuple("DATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album, false), - // Todo - original date std::tuple("TITLESORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album, true), std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Track, false), @@ -360,14 +357,14 @@ namespace ); if(it != simpleTagsTranslation.end()) return { std::get<1>(*it), std::get<2>(*it), std::get<3>(*it) }; - if (!key.isEmpty()) + if(!key.isEmpty()) return { key, Matroska::SimpleTag::TargetTypeValue::Track, false }; return { String(), Matroska::SimpleTag::TargetTypeValue::None, false }; } String translateTag(const String &name, Matroska::SimpleTag::TargetTypeValue targetTypeValue) { - auto it = std::find_if(simpleTagsTranslation.cbegin(), + const auto it = std::find_if(simpleTagsTranslation.cbegin(), simpleTagsTranslation.cend(), [&name, targetTypeValue](const auto &t) { return name == std::get<1>(t) @@ -378,8 +375,8 @@ namespace ); return it != simpleTagsTranslation.end() ? String(std::get<0>(*it), String::UTF8) - : (targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track || - targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) + : targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track || + targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None ? name : String(); } @@ -414,7 +411,7 @@ String Matroska::Tag::TagPrivate::getTag(const String &key) const bool strict = std::get<2>(tpl); if(name.isEmpty()) return {}; - auto it = std::find_if(tags.begin(), tags.end(), + const auto it = std::find_if(tags.begin(), tags.end(), [&name, targetTypeValue, strict] (const SimpleTag &t) { return t.name() == name && t.type() == SimpleTag::StringType @@ -431,8 +428,8 @@ PropertyMap Matroska::Tag::properties() const PropertyMap properties; for(const auto &simpleTag : std::as_const(d->tags)) { if(simpleTag.type() == SimpleTag::StringType && simpleTag.trackUid() == 0) { - String key = translateTag(simpleTag.name(), simpleTag.targetTypeValue()); - if(!key.isEmpty()) + if(String key = translateTag(simpleTag.name(), simpleTag.targetTypeValue()); + !key.isEmpty()) properties[key].append(simpleTag.toString()); else properties.addUnsupportedData(simpleTag.name()); @@ -445,10 +442,9 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) { // Remove all simple tags which would be returned in properties() for(auto it = d->tags.begin(); it != d->tags.end();) { - String key; if(it->type() == SimpleTag::StringType && it->trackUid() == 0 && - !(key = translateTag(it->name(), it->targetTypeValue())).isEmpty()) { + !translateTag(it->name(), it->targetTypeValue()).isEmpty()) { it = d->tags.erase(it); setNeedsRender(true); } @@ -474,10 +470,10 @@ PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) return unsupportedProperties; } -void Matroska::Tag::removeUnsupportedProperties(const StringList& properties) +void Matroska::Tag::removeUnsupportedProperties(const StringList &properties) { if(d->removeSimpleTags( - [&properties](const SimpleTag& t) { + [&properties](const SimpleTag &t) { return properties.contains(t.name()); }) > 0) { setNeedsRender(true); @@ -497,7 +493,7 @@ StringList Matroska::Tag::complexPropertyKeys() const return keys; } -List Matroska::Tag::complexProperties(const String& key) const +List Matroska::Tag::complexProperties(const String &key) const { List props; if(key.upper() != "PICTURE") { // Pictures are handled at the file level @@ -529,7 +525,7 @@ List Matroska::Tag::complexProperties(const String& key) const return props; } -bool Matroska::Tag::setComplexProperties(const String& key, const List& value) +bool Matroska::Tag::setComplexProperties(const String &key, const List &value) { if(key.upper() == "PICTURE") { // Pictures are handled at the file level @@ -549,8 +545,8 @@ bool Matroska::Tag::setComplexProperties(const String& key, const List() == key && (property.contains("data") || property.contains("value") )) { SimpleTag::TargetTypeValue targetTypeValue; - Variant targetTypeValueVar = property.value("targetTypeValue", 0); - switch(targetTypeValueVar.type()) { + switch(Variant targetTypeValueVar = property.value("targetTypeValue", 0); + targetTypeValueVar.type()) { case Variant::UInt: targetTypeValue = static_cast(targetTypeValueVar.value()); break; @@ -564,8 +560,8 @@ bool Matroska::Tag::setComplexProperties(const String& key, const List(targetTypeValueVar.value()); } auto language = property.value("language").value(); - bool defaultLanguage = property.value("defaultLanguage", true).value(); - auto trackUid = property.value("trackUid", 0ULL).value(); + const bool defaultLanguage = property.value("defaultLanguage", true).value(); + const auto trackUid = property.value("trackUid", 0ULL).value(); d->tags.append(property.contains("data") ? SimpleTag(key, property.value("data").value(), targetTypeValue, language, defaultLanguage, trackUid) diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 7af36d41..57aba7d7 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -24,27 +24,33 @@ #include #include "tag.h" -#include "tstring.h" #include "tlist.h" -#include "matroskafile.h" #include "matroskaelement.h" #include "matroskasimpletag.h" namespace TagLib { class File; + namespace EBML { class MkTags; } namespace Matroska { + //! List of tag attributes. using SimpleTagsList = List; + + //! Matroska tag implementation. class TAGLIB_EXPORT Tag : public TagLib::Tag #ifndef DO_NOT_DOCUMENT , private Element #endif { public: + /*! + * Constructs a Matroska tag. + */ Tag(); + ~Tag() override; String title() const override; String artist() const override; @@ -63,7 +69,7 @@ namespace TagLib { bool isEmpty() const override; PropertyMap properties() const override; PropertyMap setProperties(const PropertyMap &propertyMap) override; - void removeUnsupportedProperties(const StringList& properties) override; + void removeUnsupportedProperties(const StringList &properties) override; /*! * Returns the names of the binary simple tags. @@ -86,12 +92,27 @@ namespace TagLib { * * Returns \c true if \c key can be stored as binary simple tags. */ - bool setComplexProperties(const String& key, const List& value) override; + bool setComplexProperties(const String &key, const List &value) override; + /*! + * Add a tag attribute. + */ void addSimpleTag(const SimpleTag &tag); + + /*! + * Remove a tag attribute. + */ void removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue, unsigned long long trackUid = 0); + + /*! + * Remove all tag attributes. + */ void clearSimpleTags(); + + /*! + * Get list of all tag attributes. + */ const SimpleTagsList &simpleTagsList() const; private: diff --git a/tests/test_matroska.cpp b/tests/test_matroska.cpp index a9d178b3..f932039f 100644 --- a/tests/test_matroska.cpp +++ b/tests/test_matroska.cpp @@ -1002,7 +1002,7 @@ public: void testChapters() { const Matroska::ChapterEdition edition1( - List{ + List{ Matroska::Chapter( 0, 40000, List{ From 68a514f4a056869ecb252ab31576f661336dd2bb Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sat, 22 Nov 2025 12:34:48 +0100 Subject: [PATCH 29/31] Matroska: Avoid inlined header methods, only install public headers --- taglib/CMakeLists.txt | 11 ++- taglib/matroska/ebml/ebmlbinaryelement.cpp | 26 ++++++ taglib/matroska/ebml/ebmlbinaryelement.h | 14 ++-- taglib/matroska/ebml/ebmlelement.cpp | 38 +++++++++ taglib/matroska/ebml/ebmlelement.h | 23 ++--- taglib/matroska/ebml/ebmlfloatelement.cpp | 23 +++++ taglib/matroska/ebml/ebmlfloatelement.h | 13 ++- taglib/matroska/ebml/ebmlmasterelement.cpp | 65 ++++++++++++++ taglib/matroska/ebml/ebmlmasterelement.h | 31 ++++--- taglib/matroska/ebml/ebmlmkattachments.cpp | 15 ++++ taglib/matroska/ebml/ebmlmkattachments.h | 10 +-- taglib/matroska/ebml/ebmlmkchapters.cpp | 15 ++++ taglib/matroska/ebml/ebmlmkchapters.h | 10 +-- taglib/matroska/ebml/ebmlmkcues.cpp | 15 ++++ taglib/matroska/ebml/ebmlmkcues.h | 9 +- taglib/matroska/ebml/ebmlmkinfo.cpp | 15 ++++ taglib/matroska/ebml/ebmlmkinfo.h | 10 +-- taglib/matroska/ebml/ebmlmkseekhead.cpp | 15 ++++ taglib/matroska/ebml/ebmlmkseekhead.h | 10 +-- taglib/matroska/ebml/ebmlmksegment.cpp | 10 +++ taglib/matroska/ebml/ebmlmksegment.h | 9 +- taglib/matroska/ebml/ebmlmktags.cpp | 15 ++++ taglib/matroska/ebml/ebmlmktags.h | 9 +- taglib/matroska/ebml/ebmlmktracks.cpp | 15 ++++ taglib/matroska/ebml/ebmlmktracks.h | 10 +-- taglib/matroska/ebml/ebmlstringelement.cpp | 46 ++++++++++ taglib/matroska/ebml/ebmlstringelement.h | 25 ++---- taglib/matroska/ebml/ebmluintelement.cpp | 26 ++++++ taglib/matroska/ebml/ebmluintelement.h | 13 ++- taglib/matroska/ebml/ebmlutils.cpp | 30 +++++++ taglib/matroska/ebml/ebmlutils.h | 30 ++----- taglib/matroska/ebml/ebmlvoidelement.cpp | 15 ++++ taglib/matroska/ebml/ebmlvoidelement.h | 9 +- taglib/matroska/matroskaattachedfile.h | 1 + taglib/matroska/matroskaattachments.h | 2 + taglib/matroska/matroskachapters.h | 1 + taglib/matroska/matroskacues.cpp | 98 ++++++++++++++++++++++ taglib/matroska/matroskacues.h | 44 +++++----- taglib/matroska/matroskaproperties.h | 1 + taglib/matroska/matroskaseekhead.cpp | 2 + taglib/matroska/matroskaseekhead.h | 2 +- taglib/matroska/matroskasegment.cpp | 8 ++ taglib/matroska/matroskasegment.h | 4 +- tests/test_sizes.cpp | 11 ++- 44 files changed, 624 insertions(+), 180 deletions(-) diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 24fb4a7d..ce6197aa 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -72,6 +72,7 @@ if(WITH_MATROSKA) endif() include_directories(${tag_HDR_DIRS}) +set(tag_PRIVATE_HDRS) set(tag_HDRS tag.h fileref.h @@ -233,14 +234,13 @@ if(WITH_MATROSKA) matroska/matroskachapter.h matroska/matroskachapteredition.h matroska/matroskachapters.h - matroska/matroskacues.h matroska/matroskaelement.h matroska/matroskafile.h matroska/matroskaproperties.h - matroska/matroskaseekhead.h - matroska/matroskasegment.h matroska/matroskasimpletag.h matroska/matroskatag.h + ) + set(tag_PRIVATE_HDRS ${tag_PRIVATE_HDRS} matroska/ebml/ebmlbinaryelement.h matroska/ebml/ebmlelement.h matroska/ebml/ebmlmasterelement.h @@ -257,6 +257,9 @@ if(WITH_MATROSKA) matroska/ebml/ebmlfloatelement.h matroska/ebml/ebmlutils.h matroska/ebml/ebmlvoidelement.h + matroska/matroskacues.h + matroska/matroskaseekhead.h + matroska/matroskasegment.h ) endif() @@ -517,7 +520,7 @@ set(tag_LIB_SRCS tagutils.cpp ) -add_library(tag ${tag_LIB_SRCS} ${tag_HDRS}) +add_library(tag ${tag_LIB_SRCS} ${tag_HDRS} ${tag_PRIVATE_HDRS}) target_include_directories(tag INTERFACE $ diff --git a/taglib/matroska/ebml/ebmlbinaryelement.cpp b/taglib/matroska/ebml/ebmlbinaryelement.cpp index c8377c70..98100502 100644 --- a/taglib/matroska/ebml/ebmlbinaryelement.cpp +++ b/taglib/matroska/ebml/ebmlbinaryelement.cpp @@ -21,9 +21,35 @@ #include "ebmlbinaryelement.h" #include "ebmlutils.h" #include "tfile.h" +#include "tdebug.h" using namespace TagLib; +EBML::BinaryElement::BinaryElement(Id id, int sizeLength, offset_t dataSize): + Element(id, sizeLength, dataSize) +{ +} + +EBML::BinaryElement::BinaryElement(Id id, int sizeLength, offset_t dataSize, offset_t): + Element(id, sizeLength, dataSize) +{ +} + +EBML::BinaryElement::BinaryElement(Id id): + Element(id, 0, 0) +{ +} + +const ByteVector& EBML::BinaryElement::getValue() const +{ + return value; +} + +void EBML::BinaryElement::setValue(const ByteVector& val) +{ + value = val; +} + bool EBML::BinaryElement::read(File &file) { value = file.readBlock(dataSize); diff --git a/taglib/matroska/ebml/ebmlbinaryelement.h b/taglib/matroska/ebml/ebmlbinaryelement.h index f14b2b5b..f6a662fc 100644 --- a/taglib/matroska/ebml/ebmlbinaryelement.h +++ b/taglib/matroska/ebml/ebmlbinaryelement.h @@ -27,19 +27,17 @@ namespace TagLib { class File; + namespace EBML { class BinaryElement : public Element { public: - BinaryElement(Id id, int sizeLength, offset_t dataSize) : - Element(id, sizeLength, dataSize) {} - BinaryElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - Element(id, sizeLength, dataSize) {} - explicit BinaryElement(Id id) : - Element(id, 0, 0) {} + BinaryElement(Id id, int sizeLength, offset_t dataSize); + BinaryElement(Id id, int sizeLength, offset_t dataSize, offset_t); + explicit BinaryElement(Id id); - const ByteVector &getValue() const { return value; } - void setValue(const ByteVector &val) { value = val; } + const ByteVector &getValue() const; + void setValue(const ByteVector &val); bool read(File &file) override; ByteVector render() override; diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 46c3dd11..691b6479 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -148,16 +148,54 @@ unsigned int EBML::Element::readId(File &file) return buffer.toUInt(true); } +EBML::Element::Element(Id id, int sizeLength, offset_t dataSize): + id(id), sizeLength(sizeLength), dataSize(dataSize) +{ +} + +EBML::Element::Element(Id id, int sizeLength, offset_t dataSize, offset_t): + id(id), sizeLength(sizeLength), dataSize(dataSize) +{ +} + +EBML::Element::~Element() = default; + +bool EBML::Element::read(File& file) +{ + skipData(file); + return true; +} + void EBML::Element::skipData(File &file) { file.seek(dataSize, File::Position::Current); } +EBML::Element::Id EBML::Element::getId() const +{ + return id; +} + offset_t EBML::Element::headSize() const { return idSize(id) + sizeLength; } +offset_t EBML::Element::getSize() const +{ + return headSize() + dataSize; +} + +int EBML::Element::getSizeLength() const +{ + return sizeLength; +} + +int64_t EBML::Element::getDataSize() const +{ + return dataSize; +} + ByteVector EBML::Element::render() { ByteVector buffer = renderId(); diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 9c8144b8..8bccf469 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -106,22 +106,17 @@ namespace TagLib MkChapLanguage = 0x437C, }; - Element(Id id, int sizeLength, offset_t dataSize) : - id(id), sizeLength(sizeLength), dataSize(dataSize) {} - Element(Id id, int sizeLength, offset_t dataSize, offset_t) : - id(id), sizeLength(sizeLength), dataSize(dataSize) {} - virtual ~Element() = default; - virtual bool read(File &file) - { - skipData(file); - return true; - } + Element(Id id, int sizeLength, offset_t dataSize); + Element(Id id, int sizeLength, offset_t dataSize, offset_t); + virtual ~Element(); + + virtual bool read(File &file); void skipData(File &file); - Id getId() const { return id; } + Id getId() const; offset_t headSize() const; - offset_t getSize() const { return headSize() + dataSize; } - int getSizeLength() const { return sizeLength; } - int64_t getDataSize() const { return dataSize; } + offset_t getSize() const; + int getSizeLength() const; + int64_t getDataSize() const; ByteVector renderId() const; virtual ByteVector render(); static std::unique_ptr factory(File &file); diff --git a/taglib/matroska/ebml/ebmlfloatelement.cpp b/taglib/matroska/ebml/ebmlfloatelement.cpp index 781aca16..1460de8e 100644 --- a/taglib/matroska/ebml/ebmlfloatelement.cpp +++ b/taglib/matroska/ebml/ebmlfloatelement.cpp @@ -31,6 +31,24 @@ using namespace TagLib; +EBML::FloatElement::FloatElement(Id id, int sizeLength, offset_t dataSize): + Element(id, sizeLength, dataSize) +{ +} + +EBML::FloatElement::FloatElement(Id id, int sizeLength, offset_t dataSize, offset_t): + Element(id, sizeLength, dataSize) +{ +} + +EBML::FloatElement::FloatElement(Id id): + FloatElement(id, 0, 0) +{ +} + +EBML::FloatElement::FloatVariantType EBML::FloatElement::getValue() const +{ return value; } + double EBML::FloatElement::getValueAsDouble(double defaultValue) const { if(std::holds_alternative(value)) { @@ -42,6 +60,11 @@ double EBML::FloatElement::getValueAsDouble(double defaultValue) const return defaultValue; } +void EBML::FloatElement::setValue(FloatVariantType val) +{ + value = val; +} + bool EBML::FloatElement::read(File &file) { const ByteVector buffer = file.readBlock(dataSize); diff --git a/taglib/matroska/ebml/ebmlfloatelement.h b/taglib/matroska/ebml/ebmlfloatelement.h index 34a2cab8..92307919 100644 --- a/taglib/matroska/ebml/ebmlfloatelement.h +++ b/taglib/matroska/ebml/ebmlfloatelement.h @@ -39,16 +39,13 @@ namespace TagLib { public: using FloatVariantType = std::variant; - FloatElement(Id id, int sizeLength, offset_t dataSize) : - Element(id, sizeLength, dataSize) {} - FloatElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - Element(id, sizeLength, dataSize) {} - explicit FloatElement(Id id) : - FloatElement(id, 0, 0) {} + FloatElement(Id id, int sizeLength, offset_t dataSize); + FloatElement(Id id, int sizeLength, offset_t dataSize, offset_t); + explicit FloatElement(Id id); - FloatVariantType getValue() const { return value; } + FloatVariantType getValue() const; double getValueAsDouble(double defaultValue = 0.0) const; - void setValue(FloatVariantType val) { value = val; } + void setValue(FloatVariantType val); bool read(File &file) override; ByteVector render() override; diff --git a/taglib/matroska/ebml/ebmlmasterelement.cpp b/taglib/matroska/ebml/ebmlmasterelement.cpp index 85d041e1..22e5d9b7 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.cpp +++ b/taglib/matroska/ebml/ebmlmasterelement.cpp @@ -25,13 +25,78 @@ using namespace TagLib; +EBML::MasterElement::MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset): + Element(id, sizeLength, dataSize), offset(offset) +{ +} + +EBML::MasterElement::MasterElement(Id id): + Element(id, 0, 0), offset(0) +{ +} + EBML::MasterElement::~MasterElement() = default; +offset_t EBML::MasterElement::getOffset() const +{ + return offset; +} + void EBML::MasterElement::appendElement(std::unique_ptr &&element) { elements.push_back(std::move(element)); } +std::list>::iterator EBML::MasterElement::begin() +{ + return elements.begin(); +} + +std::list>::iterator EBML::MasterElement::end() +{ + return elements.end(); +} + +std::list>::const_iterator EBML::MasterElement::begin() const +{ + return elements.begin(); +} + +std::list>::const_iterator EBML::MasterElement::end() const +{ + return elements.end(); +} + +std::list>::const_iterator EBML::MasterElement::cbegin() const +{ + return elements.cbegin(); +} + +std::list>::const_iterator EBML::MasterElement::cend() const +{ + return elements.cend(); +} + +offset_t EBML::MasterElement::getPadding() const +{ + return padding; +} + +void EBML::MasterElement::setPadding(offset_t numBytes) +{ + padding = numBytes; +} + +offset_t EBML::MasterElement::getMinRenderSize() const +{ + return minRenderSize; +} + +void EBML::MasterElement::setMinRenderSize(offset_t minimumSize) +{ + minRenderSize = minimumSize; +} + bool EBML::MasterElement::read(File &file) { const offset_t maxOffset = file.tell() + dataSize; diff --git a/taglib/matroska/ebml/ebmlmasterelement.h b/taglib/matroska/ebml/ebmlmasterelement.h index 158bcf84..52cfe526 100644 --- a/taglib/matroska/ebml/ebmlmasterelement.h +++ b/taglib/matroska/ebml/ebmlmasterelement.h @@ -22,9 +22,10 @@ #define TAGLIB_EBMLMASTERELEMENT_H #ifndef DO_NOT_DOCUMENT +#include + #include "ebmlelement.h" #include "taglib.h" -#include "tlist.h" namespace TagLib { @@ -34,26 +35,24 @@ namespace TagLib class MasterElement : public Element { public: - MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset) : - Element(id, sizeLength, dataSize), offset(offset) {} - explicit MasterElement(Id id) : - Element(id, 0, 0), offset(0) {} + MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset); + explicit MasterElement(Id id); ~MasterElement() override; - offset_t getOffset() const { return offset; } + offset_t getOffset() const; bool read(File &file) override; ByteVector render() override; void appendElement(std::unique_ptr &&element); - std::list>::iterator begin() { return elements.begin(); } - std::list>::iterator end() { return elements.end(); } - std::list>::const_iterator begin() const { return elements.begin(); } - std::list>::const_iterator end() const { return elements.end(); } - std::list>::const_iterator cbegin() const { return elements.cbegin(); } - std::list>::const_iterator cend() const { return elements.cend(); } - offset_t getPadding() const { return padding; } - void setPadding(offset_t numBytes) { padding = numBytes; } - offset_t getMinRenderSize() const { return minRenderSize; } - void setMinRenderSize(offset_t minimumSize) { minRenderSize = minimumSize; } + std::list>::iterator begin(); + std::list>::iterator end(); + std::list>::const_iterator begin() const; + std::list>::const_iterator end() const; + std::list>::const_iterator cbegin() const; + std::list>::const_iterator cend() const; + offset_t getPadding() const; + void setPadding(offset_t numBytes); + offset_t getMinRenderSize() const; + void setMinRenderSize(offset_t minimumSize); protected: offset_t offset; diff --git a/taglib/matroska/ebml/ebmlmkattachments.cpp b/taglib/matroska/ebml/ebmlmkattachments.cpp index 43c2efb9..f57c9492 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.cpp +++ b/taglib/matroska/ebml/ebmlmkattachments.cpp @@ -27,6 +27,21 @@ using namespace TagLib; +EBML::MkAttachments::MkAttachments(int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) +{ +} + +EBML::MkAttachments::MkAttachments(Id, int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) +{ +} + +EBML::MkAttachments::MkAttachments(): + MasterElement(Id::MkAttachments, 0, 0, 0) +{ +} + std::unique_ptr EBML::MkAttachments::parse() const { auto attachments = std::make_unique(); diff --git a/taglib/matroska/ebml/ebmlmkattachments.h b/taglib/matroska/ebml/ebmlmkattachments.h index cefc8e07..7c2404ea 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.h +++ b/taglib/matroska/ebml/ebmlmkattachments.h @@ -29,16 +29,14 @@ namespace TagLib { namespace Matroska { class Attachments; } + namespace EBML { class MkAttachments : public MasterElement { public: - MkAttachments(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) {} - MkAttachments(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkAttachments, sizeLength, dataSize, offset) {} - MkAttachments() : - MasterElement(Id::MkAttachments, 0, 0, 0) {} + MkAttachments(int sizeLength, offset_t dataSize, offset_t offset); + MkAttachments(Id, int sizeLength, offset_t dataSize, offset_t offset); + MkAttachments(); std::unique_ptr parse() const; }; diff --git a/taglib/matroska/ebml/ebmlmkchapters.cpp b/taglib/matroska/ebml/ebmlmkchapters.cpp index 26d4386c..d8d3c136 100644 --- a/taglib/matroska/ebml/ebmlmkchapters.cpp +++ b/taglib/matroska/ebml/ebmlmkchapters.cpp @@ -31,6 +31,21 @@ using namespace TagLib; +EBML::MkChapters::MkChapters(int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkChapters, sizeLength, dataSize, offset) +{ +} + +EBML::MkChapters::MkChapters(Id, int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkChapters, sizeLength, dataSize, offset) +{ +} + +EBML::MkChapters::MkChapters(): + MasterElement(Id::MkChapters, 0, 0, 0) +{ +} + std::unique_ptr EBML::MkChapters::parse() const { auto chapters = std::make_unique(); diff --git a/taglib/matroska/ebml/ebmlmkchapters.h b/taglib/matroska/ebml/ebmlmkchapters.h index 462b57a8..af7e63ba 100644 --- a/taglib/matroska/ebml/ebmlmkchapters.h +++ b/taglib/matroska/ebml/ebmlmkchapters.h @@ -34,16 +34,14 @@ namespace TagLib { namespace Matroska { class Chapters; } + namespace EBML { class MkChapters : public MasterElement { public: - MkChapters(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkChapters, sizeLength, dataSize, offset) {} - MkChapters(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkChapters, sizeLength, dataSize, offset) {} - MkChapters() : - MasterElement(Id::MkChapters, 0, 0, 0) {} + MkChapters(int sizeLength, offset_t dataSize, offset_t offset); + MkChapters(Id, int sizeLength, offset_t dataSize, offset_t offset); + MkChapters(); std::unique_ptr parse() const; }; diff --git a/taglib/matroska/ebml/ebmlmkcues.cpp b/taglib/matroska/ebml/ebmlmkcues.cpp index 005337a0..1d60f8c7 100644 --- a/taglib/matroska/ebml/ebmlmkcues.cpp +++ b/taglib/matroska/ebml/ebmlmkcues.cpp @@ -24,6 +24,21 @@ using namespace TagLib; +EBML::MkCues::MkCues(int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkCues, sizeLength, dataSize, offset) +{ +} + +EBML::MkCues::MkCues(Id, int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkCues, sizeLength, dataSize, offset) +{ +} + +EBML::MkCues::MkCues(): + MasterElement(Id::MkCues, 0, 0, 0) +{ +} + std::unique_ptr EBML::MkCues::parse(offset_t segmentDataOffset) const { auto cues = std::make_unique(segmentDataOffset); diff --git a/taglib/matroska/ebml/ebmlmkcues.h b/taglib/matroska/ebml/ebmlmkcues.h index 388ec354..0ce3c7ab 100644 --- a/taglib/matroska/ebml/ebmlmkcues.h +++ b/taglib/matroska/ebml/ebmlmkcues.h @@ -34,12 +34,9 @@ namespace TagLib { class MkCues : public MasterElement { public: - MkCues(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkCues, sizeLength, dataSize, offset) {} - MkCues(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkCues, sizeLength, dataSize, offset) {} - MkCues() : - MasterElement(Id::MkCues, 0, 0, 0) {} + MkCues(int sizeLength, offset_t dataSize, offset_t offset); + MkCues(Id, int sizeLength, offset_t dataSize, offset_t offset); + MkCues(); std::unique_ptr parse(offset_t segmentDataOffset) const; }; diff --git a/taglib/matroska/ebml/ebmlmkinfo.cpp b/taglib/matroska/ebml/ebmlmkinfo.cpp index b0ea7e63..a2e4e910 100644 --- a/taglib/matroska/ebml/ebmlmkinfo.cpp +++ b/taglib/matroska/ebml/ebmlmkinfo.cpp @@ -31,6 +31,21 @@ using namespace TagLib; +EBML::MkInfo::MkInfo(int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkInfo, sizeLength, dataSize, offset) +{ +} + +EBML::MkInfo::MkInfo(Id, int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkInfo, sizeLength, dataSize, offset) +{ +} + +EBML::MkInfo::MkInfo(): + MasterElement(Id::MkInfo, 0, 0, 0) +{ +} + void EBML::MkInfo::parse(Matroska::Properties *properties) const { if(!properties) diff --git a/taglib/matroska/ebml/ebmlmkinfo.h b/taglib/matroska/ebml/ebmlmkinfo.h index 294342bb..c381b46a 100644 --- a/taglib/matroska/ebml/ebmlmkinfo.h +++ b/taglib/matroska/ebml/ebmlmkinfo.h @@ -34,16 +34,14 @@ namespace TagLib { namespace Matroska { class Properties; } + namespace EBML { class MkInfo : public MasterElement { public: - MkInfo(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkInfo, sizeLength, dataSize, offset) {} - MkInfo(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkInfo, sizeLength, dataSize, offset) {} - MkInfo() : - MasterElement(Id::MkInfo, 0, 0, 0) {} + MkInfo(int sizeLength, offset_t dataSize, offset_t offset); + MkInfo(Id, int sizeLength, offset_t dataSize, offset_t offset); + MkInfo(); void parse(Matroska::Properties * properties) const; }; diff --git a/taglib/matroska/ebml/ebmlmkseekhead.cpp b/taglib/matroska/ebml/ebmlmkseekhead.cpp index 71250cc3..c3d6ca8f 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.cpp +++ b/taglib/matroska/ebml/ebmlmkseekhead.cpp @@ -25,6 +25,21 @@ using namespace TagLib; +EBML::MkSeekHead::MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) +{ +} + +EBML::MkSeekHead::MkSeekHead(Id, int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) +{ +} + +EBML::MkSeekHead::MkSeekHead(): + MasterElement(Id::MkSeekHead, 0, 0, 0) +{ +} + std::unique_ptr EBML::MkSeekHead::parse(offset_t segmentDataOffset) const { auto seekHead = std::make_unique(segmentDataOffset); diff --git a/taglib/matroska/ebml/ebmlmkseekhead.h b/taglib/matroska/ebml/ebmlmkseekhead.h index c7c47acf..13007841 100644 --- a/taglib/matroska/ebml/ebmlmkseekhead.h +++ b/taglib/matroska/ebml/ebmlmkseekhead.h @@ -29,16 +29,14 @@ namespace TagLib { namespace Matroska { class SeekHead; } + namespace EBML { class MkSeekHead : public MasterElement { public: - MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) {} - MkSeekHead(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset) {} - MkSeekHead() : - MasterElement(Id::MkSeekHead, 0, 0, 0) {} + MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset); + MkSeekHead(Id, int sizeLength, offset_t dataSize, offset_t offset); + MkSeekHead(); std::unique_ptr parse(offset_t segmentDataOffset) const; }; diff --git a/taglib/matroska/ebml/ebmlmksegment.cpp b/taglib/matroska/ebml/ebmlmksegment.cpp index 1222360b..4b9ba4e5 100644 --- a/taglib/matroska/ebml/ebmlmksegment.cpp +++ b/taglib/matroska/ebml/ebmlmksegment.cpp @@ -30,6 +30,16 @@ using namespace TagLib; +EBML::MkSegment::MkSegment(int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkSegment, sizeLength, dataSize, offset) +{ +} + +EBML::MkSegment::MkSegment(Id, int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkSegment, sizeLength, dataSize, offset) +{ +} + EBML::MkSegment::~MkSegment() = default; offset_t EBML::MkSegment::segmentDataOffset() const diff --git a/taglib/matroska/ebml/ebmlmksegment.h b/taglib/matroska/ebml/ebmlmksegment.h index f1164a49..13000f3e 100644 --- a/taglib/matroska/ebml/ebmlmksegment.h +++ b/taglib/matroska/ebml/ebmlmksegment.h @@ -40,16 +40,13 @@ namespace TagLib { class SeekHead; class Segment; } + namespace EBML { class MkSegment : public MasterElement { public: - MkSegment(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkSegment, sizeLength, dataSize, offset) - { - } - MkSegment(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkSegment, sizeLength, dataSize, offset) {} + MkSegment(int sizeLength, offset_t dataSize, offset_t offset); + MkSegment(Id, int sizeLength, offset_t dataSize, offset_t offset); ~MkSegment() override; offset_t segmentDataOffset() const; diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index 339d80db..e732e26e 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -28,6 +28,21 @@ using namespace TagLib; +EBML::MkTags::MkTags(int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkTags, sizeLength, dataSize, offset) +{ +} + +EBML::MkTags::MkTags(Id, int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkTags, sizeLength, dataSize, offset) +{ +} + +EBML::MkTags::MkTags(): + MasterElement(Id::MkTags, 0, 0, 0) +{ +} + std::unique_ptr EBML::MkTags::parse() const { auto mTag = std::make_unique(); diff --git a/taglib/matroska/ebml/ebmlmktags.h b/taglib/matroska/ebml/ebmlmktags.h index cea61f80..82b9f33f 100644 --- a/taglib/matroska/ebml/ebmlmktags.h +++ b/taglib/matroska/ebml/ebmlmktags.h @@ -34,12 +34,9 @@ namespace TagLib { class MkTags : public MasterElement { public: - MkTags(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkTags, sizeLength, dataSize, offset) {} - MkTags(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkTags, sizeLength, dataSize, offset) {} - MkTags() : - MasterElement(Id::MkTags, 0, 0, 0) {} + MkTags(int sizeLength, offset_t dataSize, offset_t offset); + MkTags(Id, int sizeLength, offset_t dataSize, offset_t offset); + MkTags(); std::unique_ptr parse() const; }; diff --git a/taglib/matroska/ebml/ebmlmktracks.cpp b/taglib/matroska/ebml/ebmlmktracks.cpp index b3491b71..14cf7a00 100644 --- a/taglib/matroska/ebml/ebmlmktracks.cpp +++ b/taglib/matroska/ebml/ebmlmktracks.cpp @@ -31,6 +31,21 @@ using namespace TagLib; +EBML::MkTracks::MkTracks(int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkTracks, sizeLength, dataSize, offset) +{ +} + +EBML::MkTracks::MkTracks(Id, int sizeLength, offset_t dataSize, offset_t offset): + MasterElement(Id::MkTracks, sizeLength, dataSize, offset) +{ +} + +EBML::MkTracks::MkTracks(): + MasterElement(Id::MkTracks, 0, 0, 0) +{ +} + void EBML::MkTracks::parse(Matroska::Properties *properties) const { if(!properties) diff --git a/taglib/matroska/ebml/ebmlmktracks.h b/taglib/matroska/ebml/ebmlmktracks.h index 2ba2a4b1..440ac21d 100644 --- a/taglib/matroska/ebml/ebmlmktracks.h +++ b/taglib/matroska/ebml/ebmlmktracks.h @@ -34,16 +34,14 @@ namespace TagLib { namespace Matroska { class Properties; } + namespace EBML { class MkTracks : public MasterElement { public: - MkTracks(int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkTracks, sizeLength, dataSize, offset) {} - MkTracks(Id, int sizeLength, offset_t dataSize, offset_t offset) : - MasterElement(Id::MkTracks, sizeLength, dataSize, offset) {} - MkTracks() : - MasterElement(Id::MkTracks, 0, 0, 0) {} + MkTracks(int sizeLength, offset_t dataSize, offset_t offset); + MkTracks(Id, int sizeLength, offset_t dataSize, offset_t offset); + MkTracks(); void parse(Matroska::Properties *properties) const; }; diff --git a/taglib/matroska/ebml/ebmlstringelement.cpp b/taglib/matroska/ebml/ebmlstringelement.cpp index 5d8fd9cf..01699db3 100644 --- a/taglib/matroska/ebml/ebmlstringelement.cpp +++ b/taglib/matroska/ebml/ebmlstringelement.cpp @@ -27,6 +27,22 @@ using namespace TagLib; +EBML::StringElement::StringElement( + String::Type stringEncoding, Id id, int sizeLength, offset_t dataSize): + Element(id, sizeLength, dataSize), encoding(stringEncoding) +{ +} + +const String& EBML::StringElement::getValue() const +{ + return value; +} + +void EBML::StringElement::setValue(const String& val) +{ + value = val; +} + bool EBML::StringElement::read(File &file) { ByteVector buffer = file.readBlock(dataSize); @@ -52,3 +68,33 @@ ByteVector EBML::StringElement::render() buffer.append(ByteVector(string.data(), static_cast(dataSize))); return buffer; } + +EBML::UTF8StringElement::UTF8StringElement(Id id, int sizeLength, offset_t dataSize): + StringElement(String::UTF8, id, sizeLength, dataSize) +{ +} + +EBML::UTF8StringElement::UTF8StringElement(Id id, int sizeLength, offset_t dataSize, offset_t): + UTF8StringElement(id, sizeLength, dataSize) +{ +} + +EBML::UTF8StringElement::UTF8StringElement(Id id): + UTF8StringElement(id, 0, 0) +{ +} + +EBML::Latin1StringElement::Latin1StringElement(Id id, int sizeLength, offset_t dataSize): + StringElement(String::Latin1, id, sizeLength, dataSize) +{ +} + +EBML::Latin1StringElement::Latin1StringElement(Id id, int sizeLength, offset_t dataSize, offset_t): + Latin1StringElement(id, sizeLength, dataSize) +{ +} + +EBML::Latin1StringElement::Latin1StringElement(Id id): + Latin1StringElement(id, 0, 0) +{ +} diff --git a/taglib/matroska/ebml/ebmlstringelement.h b/taglib/matroska/ebml/ebmlstringelement.h index 2aca07e7..51d46950 100644 --- a/taglib/matroska/ebml/ebmlstringelement.h +++ b/taglib/matroska/ebml/ebmlstringelement.h @@ -33,11 +33,10 @@ namespace TagLib { class StringElement : public Element { public: - StringElement(String::Type stringEncoding, Id id, int sizeLength, offset_t dataSize) : - Element(id, sizeLength, dataSize), encoding(stringEncoding) {} + StringElement(String::Type stringEncoding, Id id, int sizeLength, offset_t dataSize); - const String &getValue() const { return value; } - void setValue(const String &val) { value = val; } + const String &getValue() const; + void setValue(const String &val); bool read(File &file) override; ByteVector render() override; @@ -48,22 +47,16 @@ namespace TagLib { class UTF8StringElement : public StringElement { public: - UTF8StringElement(Id id, int sizeLength, offset_t dataSize) : - StringElement(String::UTF8, id, sizeLength, dataSize) {} - UTF8StringElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - UTF8StringElement(id, sizeLength, dataSize) {} - explicit UTF8StringElement(Id id) : - UTF8StringElement(id, 0, 0) {} + UTF8StringElement(Id id, int sizeLength, offset_t dataSize); + UTF8StringElement(Id id, int sizeLength, offset_t dataSize, offset_t); + explicit UTF8StringElement(Id id); }; class Latin1StringElement : public StringElement { public: - Latin1StringElement(Id id, int sizeLength, offset_t dataSize) : - StringElement(String::Latin1, id, sizeLength, dataSize) {} - Latin1StringElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - Latin1StringElement(id, sizeLength, dataSize) {} - explicit Latin1StringElement(Id id) : - Latin1StringElement(id, 0, 0) {} + Latin1StringElement(Id id, int sizeLength, offset_t dataSize); + Latin1StringElement(Id id, int sizeLength, offset_t dataSize, offset_t); + explicit Latin1StringElement(Id id); }; } } diff --git a/taglib/matroska/ebml/ebmluintelement.cpp b/taglib/matroska/ebml/ebmluintelement.cpp index b78473ab..e7d790c7 100644 --- a/taglib/matroska/ebml/ebmluintelement.cpp +++ b/taglib/matroska/ebml/ebmluintelement.cpp @@ -22,10 +22,36 @@ #include "ebmlutils.h" #include "tbytevector.h" #include "tfile.h" +#include "tutils.h" #include "tdebug.h" using namespace TagLib; +EBML::UIntElement::UIntElement(Id id, int sizeLength, offset_t dataSize): + Element(id, sizeLength, dataSize) +{ +} + +EBML::UIntElement::UIntElement(Id id, int sizeLength, offset_t dataSize, offset_t): + Element(id, sizeLength, dataSize) +{ +} + +EBML::UIntElement::UIntElement(Id id): + UIntElement(id, 0, 0) +{ +} + +unsigned long long EBML::UIntElement::getValue() const +{ + return value; +} + +void EBML::UIntElement::setValue(unsigned long long val) +{ + value = val; +} + bool EBML::UIntElement::read(File &file) { const ByteVector buffer = file.readBlock(dataSize); diff --git a/taglib/matroska/ebml/ebmluintelement.h b/taglib/matroska/ebml/ebmluintelement.h index 72f2dc1c..5529c434 100644 --- a/taglib/matroska/ebml/ebmluintelement.h +++ b/taglib/matroska/ebml/ebmluintelement.h @@ -31,15 +31,12 @@ namespace TagLib { class UIntElement : public Element { public: - UIntElement(Id id, int sizeLength, offset_t dataSize) : - Element(id, sizeLength, dataSize) {} - UIntElement(Id id, int sizeLength, offset_t dataSize, offset_t) : - Element(id, sizeLength, dataSize) {} - explicit UIntElement(Id id) : - UIntElement(id, 0, 0) {} + UIntElement(Id id, int sizeLength, offset_t dataSize); + UIntElement(Id id, int sizeLength, offset_t dataSize, offset_t); + explicit UIntElement(Id id); - unsigned long long getValue() const { return value; } - void setValue(unsigned long long val) { value = val; } + unsigned long long getValue() const; + void setValue(unsigned long long val); bool read(File &file) override; ByteVector render() override; diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index d7b3c578..e3baf7ee 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -22,6 +22,8 @@ #include #include "tbytevector.h" #include "matroskafile.h" +#include "tutils.h" +#include "tdebug.h" using namespace TagLib; @@ -43,6 +45,32 @@ std::unique_ptr EBML::findNextElement(File &file, offset_t maxOff return file.tell() < maxOffset ? Element::factory(file) : nullptr; } +template +unsigned int EBML::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; +} + +namespace TagLib::EBML { + template unsigned int VINTSizeLength<4>(uint8_t firstByte); + template unsigned int VINTSizeLength<8>(uint8_t firstByte); +} + template std::pair EBML::readVINT(File &file) { @@ -62,6 +90,7 @@ std::pair EBML::readVINT(File &file) offset_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift; return { nb_bytes, static_cast(buffer.toLongLong(true)) & mask }; } + namespace TagLib::EBML { template std::pair readVINT(File &file); template std::pair readVINT(File &file); @@ -81,6 +110,7 @@ std::pair EBML::parseVINT(const ByteVector &buffer) offset_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift; return { numBytes, static_cast(buffer.toLongLong(true)) & mask }; } + namespace TagLib::EBML { template std::pair parseVINT(const ByteVector &buffer); template std::pair parseVINT(const ByteVector &buffer); diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h index 20fd6cf9..7ad23157 100644 --- a/taglib/matroska/ebml/ebmlutils.h +++ b/taglib/matroska/ebml/ebmlutils.h @@ -24,8 +24,6 @@ #include #include "taglib.h" -#include "tutils.h" -#include "tdebug.h" #include "ebmlelement.h" namespace TagLib { @@ -33,34 +31,20 @@ namespace TagLib { class ByteVector; namespace EBML { + std::unique_ptr findElement(File &file, Element::Id id, offset_t maxOffset); + std::unique_ptr findNextElement(File &file, offset_t maxOffset); + 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; - } + unsigned int VINTSizeLength(uint8_t firstByte); template std::pair readVINT(File &file); + template std::pair parseVINT(const ByteVector &buffer); - std::unique_ptr findElement(File &file, Element::Id id, offset_t maxOffset); - std::unique_ptr findNextElement(File &file, offset_t maxOffset); + ByteVector renderVINT(uint64_t number, int minSizeLength); + unsigned long long randomUID(); constexpr int minSize(uint64_t data) diff --git a/taglib/matroska/ebml/ebmlvoidelement.cpp b/taglib/matroska/ebml/ebmlvoidelement.cpp index d764e4a2..3a841c2e 100644 --- a/taglib/matroska/ebml/ebmlvoidelement.cpp +++ b/taglib/matroska/ebml/ebmlvoidelement.cpp @@ -25,6 +25,21 @@ using namespace TagLib; +EBML::VoidElement::VoidElement(int sizeLength, offset_t dataSize): + Element(Id::VoidElement, sizeLength, dataSize) +{ +} + +EBML::VoidElement::VoidElement(Id, int sizeLength, offset_t dataSize, offset_t): + Element(Id::VoidElement, sizeLength, dataSize) +{ +} + +EBML::VoidElement::VoidElement(): + Element(Id::VoidElement, 0, 0) +{ +} + ByteVector EBML::VoidElement::render() { offset_t bytesNeeded = targetSize; diff --git a/taglib/matroska/ebml/ebmlvoidelement.h b/taglib/matroska/ebml/ebmlvoidelement.h index d4e4e57f..24dd2a34 100644 --- a/taglib/matroska/ebml/ebmlvoidelement.h +++ b/taglib/matroska/ebml/ebmlvoidelement.h @@ -32,12 +32,9 @@ namespace TagLib { class VoidElement : public Element { public: - VoidElement(int sizeLength, offset_t dataSize) : - Element(Id::VoidElement, sizeLength, dataSize) {} - VoidElement(Id, int sizeLength, offset_t dataSize, offset_t) : - Element(Id::VoidElement, sizeLength, dataSize) {} - VoidElement() : - Element(Id::VoidElement, 0, 0) {} + VoidElement(int sizeLength, offset_t dataSize); + VoidElement(Id, int sizeLength, offset_t dataSize, offset_t); + VoidElement(); ByteVector render() override; offset_t getTargetSize() const; diff --git a/taglib/matroska/matroskaattachedfile.h b/taglib/matroska/matroskaattachedfile.h index b7afcd16..7df5f5e7 100644 --- a/taglib/matroska/matroskaattachedfile.h +++ b/taglib/matroska/matroskaattachedfile.h @@ -27,6 +27,7 @@ namespace TagLib { class String; class ByteVector; + namespace Matroska { //! Attached file embedded into a Matroska file. class TAGLIB_EXPORT AttachedFile diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index ba586684..84e6f150 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -28,9 +28,11 @@ namespace TagLib { class File; + namespace EBML { class MkAttachments; } + namespace Matroska { class AttachedFile; class File; diff --git a/taglib/matroska/matroskachapters.h b/taglib/matroska/matroskachapters.h index f033340f..895c7603 100644 --- a/taglib/matroska/matroskachapters.h +++ b/taglib/matroska/matroskachapters.h @@ -37,6 +37,7 @@ namespace TagLib { namespace EBML { class MkChapters; } + namespace Matroska { class ChapterEdition; class File; diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp index 6be02904..2c4a4f63 100644 --- a/taglib/matroska/matroskacues.cpp +++ b/taglib/matroska/matroskacues.cpp @@ -36,6 +36,8 @@ Matroska::Cues::Cues(offset_t segmentDataOffset) : setNeedsRender(false); } +Matroska::Cues::~Cues() = default; + ByteVector Matroska::Cues::renderInternal() { const auto beforeSize = sizeRenderedOrWritten(); @@ -141,8 +143,15 @@ void Matroska::Cues::addCuePoint(std::unique_ptr &&cuePoint) cuePoints.push_back(std::move(cuePoint)); } +const Matroska::Cues::CuePointList &Matroska::Cues::cuePointList() +{ + return cuePoints; +} + Matroska::CuePoint::CuePoint() = default; +Matroska::CuePoint::~CuePoint() = default; + bool Matroska::CuePoint::isValid(TagLib::File &file, offset_t segmentDataOffset) const { for(const auto &track : cueTracks) { @@ -157,6 +166,21 @@ void Matroska::CuePoint::addCueTrack(std::unique_ptr &&cueTrack) cueTracks.push_back(std::move(cueTrack)); } +const Matroska::CuePoint::CueTrackList &Matroska::CuePoint::cueTrackList() const +{ + return cueTracks; +} + +void Matroska::CuePoint::setTime(Time timestamp) +{ + time = timestamp; +} + +Matroska::CuePoint::Time Matroska::CuePoint::getTime() const +{ + return time; +} + bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta) { bool ret = false; @@ -166,6 +190,10 @@ bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta) return ret; } +Matroska::CueTrack::CueTrack() = default; + +Matroska::CueTrack::~CueTrack() = default; + bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) const { if(!trackNumber) { @@ -191,6 +219,76 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) return true; } +void Matroska::CueTrack::setTrackNumber(unsigned long long trackNr) +{ + trackNumber = trackNr; +} + +unsigned long long Matroska::CueTrack::getTrackNumber() const +{ + return trackNumber; +} + +void Matroska::CueTrack::setClusterPosition(offset_t clusterPos) +{ + clusterPosition = clusterPos; +} + +offset_t Matroska::CueTrack::getClusterPosition() const +{ + return clusterPosition; +} + +void Matroska::CueTrack::setRelativePosition(std::optional relativePos) +{ + relativePosition = relativePos; +} + +std::optional Matroska::CueTrack::getRelativePosition() const +{ + return relativePosition; +} + +void Matroska::CueTrack::setCodecState(std::optional codecStatePos) +{ + codecState = codecStatePos; +} + +std::optional Matroska::CueTrack::getCodecState() const +{ + return codecState; +} + +void Matroska::CueTrack::setBlockNumber(std::optional blockNr) +{ + blockNumber = blockNr; +} + +std::optional Matroska::CueTrack::getBlockNumber() const +{ + return blockNumber; +} + +void Matroska::CueTrack::setDuration(std::optional segmentTicks) +{ + duration = segmentTicks; +} + +std::optional Matroska::CueTrack::getDuration() const +{ + return duration; +} + +void Matroska::CueTrack::addReferenceTime(unsigned long long refTime) +{ + refTimes.append(refTime); +} + +const Matroska::CueTrack::ReferenceTimeList &Matroska::CueTrack::referenceTimes() const +{ + return refTimes; +} + bool Matroska::CueTrack::adjustOffset(offset_t offset, offset_t delta) { bool ret = false; diff --git a/taglib/matroska/matroskacues.h b/taglib/matroska/matroskacues.h index 7241f8f3..e9285f69 100644 --- a/taglib/matroska/matroskacues.h +++ b/taglib/matroska/matroskacues.h @@ -43,10 +43,10 @@ namespace TagLib { public: using CuePointList = std::list>; explicit Cues(offset_t segmentDataOffset); - ~Cues() override = default; + ~Cues() override; bool isValid(TagLib::File &file) const; void addCuePoint(std::unique_ptr &&cuePoint); - const CuePointList &cuePointList() { return cuePoints; } + const CuePointList &cuePointList(); bool sizeChanged(Element &caller, offset_t delta) override; void write(TagLib::File &file) override; @@ -64,12 +64,12 @@ namespace TagLib { using CueTrackList = std::list>; using Time = unsigned long long; CuePoint(); - ~CuePoint() = default; + ~CuePoint(); bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; void addCueTrack(std::unique_ptr &&cueTrack); - const CueTrackList &cueTrackList() const { return cueTracks; } - void setTime(Time timestamp) { time = timestamp; } - Time getTime() const { return time; } + const CueTrackList &cueTrackList() const; + void setTime(Time timestamp); + Time getTime() const; bool adjustOffset(offset_t offset, offset_t delta); private: @@ -81,23 +81,23 @@ namespace TagLib { { public: using ReferenceTimeList = List; - CueTrack() = default; - ~CueTrack() = default; + CueTrack(); + ~CueTrack(); bool isValid(TagLib::File &file, offset_t segmentDataOffset) const; - void setTrackNumber(unsigned long long trackNr) { trackNumber = trackNr; } - unsigned long long getTrackNumber() const { return trackNumber; } - void setClusterPosition(offset_t clusterPos) { clusterPosition = clusterPos; } - offset_t getClusterPosition() const { return clusterPosition; } - void setRelativePosition(std::optional relativePos) { relativePosition = relativePos; } - std::optional getRelativePosition() const { return relativePosition; } - void setCodecState(std::optional codecStatePos) { codecState = codecStatePos; } - std::optional getCodecState() const { return codecState; } - void setBlockNumber(std::optional blockNr) { blockNumber = blockNr; } - std::optional getBlockNumber() const { return blockNumber; } - void setDuration(std::optional segmentTicks) { duration = segmentTicks; } - std::optional getDuration() const { return duration; } - void addReferenceTime(unsigned long long refTime) { refTimes.append(refTime); } - const ReferenceTimeList &referenceTimes() const { return refTimes; } + void setTrackNumber(unsigned long long trackNr); + unsigned long long getTrackNumber() const; + void setClusterPosition(offset_t clusterPos); + offset_t getClusterPosition() const; + void setRelativePosition(std::optional relativePos); + std::optional getRelativePosition() const; + void setCodecState(std::optional codecStatePos); + std::optional getCodecState() const; + void setBlockNumber(std::optional blockNr); + std::optional getBlockNumber() const; + void setDuration(std::optional segmentTicks); + std::optional getDuration() const; + void addReferenceTime(unsigned long long refTime); + const ReferenceTimeList &referenceTimes() const; bool adjustOffset(offset_t offset, offset_t delta); private: diff --git a/taglib/matroska/matroskaproperties.h b/taglib/matroska/matroskaproperties.h index 0481ffc4..b81c7826 100644 --- a/taglib/matroska/matroskaproperties.h +++ b/taglib/matroska/matroskaproperties.h @@ -33,6 +33,7 @@ namespace TagLib::EBML { class MkTracks; class MkInfo; } + namespace TagLib::Matroska { class File; diff --git a/taglib/matroska/matroskaseekhead.cpp b/taglib/matroska/matroskaseekhead.cpp index 88ad1d7e..bc8bb0a1 100644 --- a/taglib/matroska/matroskaseekhead.cpp +++ b/taglib/matroska/matroskaseekhead.cpp @@ -36,6 +36,8 @@ Matroska::SeekHead::SeekHead(offset_t segmentDataOffset) : setNeedsRender(false); } +Matroska::SeekHead::~SeekHead() = default; + bool Matroska::SeekHead::isValid(TagLib::File &file) const { bool result = true; diff --git a/taglib/matroska/matroskaseekhead.h b/taglib/matroska/matroskaseekhead.h index 0775cf1e..56fe6f4c 100644 --- a/taglib/matroska/matroskaseekhead.h +++ b/taglib/matroska/matroskaseekhead.h @@ -34,7 +34,7 @@ namespace TagLib { { public: explicit SeekHead(offset_t segmentDataOffset); - ~SeekHead() override = default; + ~SeekHead() override; bool isValid(TagLib::File &file) const; void addEntry(const Element &element); diff --git a/taglib/matroska/matroskasegment.cpp b/taglib/matroska/matroskasegment.cpp index 1c0e91b5..d4d52c1a 100644 --- a/taglib/matroska/matroskasegment.cpp +++ b/taglib/matroska/matroskasegment.cpp @@ -20,6 +20,7 @@ #include "matroskasegment.h" #include "ebmlutils.h" +#include "tbytevector.h" using namespace TagLib; @@ -31,6 +32,8 @@ Matroska::Segment::Segment(offset_t sizeLength, offset_t dataSize, offset_t leng setSize(sizeLength); } +Matroska::Segment::~Segment() = default; + ByteVector Matroska::Segment::renderInternal() { return EBML::renderVINT(dataSize, static_cast(sizeLength)); @@ -61,3 +64,8 @@ bool Matroska::Segment::sizeChanged(Element &, offset_t delta) setNeedsRender(true); return true; } + +offset_t Matroska::Segment::dataOffset() const +{ + return offset() + sizeLength; +} diff --git a/taglib/matroska/matroskasegment.h b/taglib/matroska/matroskasegment.h index 6e6d88bf..cd6952dc 100644 --- a/taglib/matroska/matroskasegment.h +++ b/taglib/matroska/matroskasegment.h @@ -29,10 +29,10 @@ namespace TagLib::Matroska { { public: Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset); - ~Segment() override = default; + ~Segment() override; bool render() override; bool sizeChanged(Element &caller, offset_t delta) override; - offset_t dataOffset() const { return offset() + sizeLength; } + offset_t dataOffset() const; private: ByteVector renderInternal() override; diff --git a/tests/test_sizes.cpp b/tests/test_sizes.cpp index aaae162d..ebc36a3a 100644 --- a/tests/test_sizes.cpp +++ b/tests/test_sizes.cpp @@ -149,6 +149,10 @@ #include "trueaudioproperties.h" #endif #ifdef TAGLIB_WITH_MATROSKA +#include "matroskaattachedfile.h" +#include "matroskaattachments.h" +#include "matroskachapteredition.h" +#include "matroskachapters.h" #include "matroskafile.h" #include "matroskaproperties.h" #include "matroskatag.h" @@ -156,10 +160,6 @@ #include -#include "matroskaattachedfile.h" -#include "matroskaattachments.h" -#include "matroskachapters.h" - using namespace std; using namespace TagLib; @@ -314,6 +314,9 @@ public: CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::Matroska::Element)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Attachments)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Chapters)); + CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::ChapterEdition)); + CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::Chapter)); + CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::Chapter::Display)); CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::SimpleTag)); CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::AttachedFile)); #endif From f4e7a742c322b2d50e1e1eaef4741273ae6edc75 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Fri, 2 Jan 2026 09:17:12 +0100 Subject: [PATCH 30/31] Improve Matroska API Make AttachedFile immutable. This is consistent with SimpleTag and Chapter and avoids using attached files which do not have all required attributes. Provide methods to insert and remove a single simple tag, so that they can be modified without setting all of them while still not exposing internal lists to the API. Use DATE_RECORDED instead of DATE_RELEASED for year() and the "DATE" property. This is more consistent with other tag formats, e.g. for ID3v2 "TDRC" is used, which is the recording time. --- examples/matroskawriter.cpp | 8 +-- taglib/matroska/ebml/ebmlmkattachments.cpp | 14 +---- taglib/matroska/matroskaattachedfile.cpp | 37 +++--------- taglib/matroska/matroskaattachedfile.h | 30 ++-------- taglib/matroska/matroskafile.cpp | 10 +--- taglib/matroska/matroskasimpletag.cpp | 4 +- taglib/matroska/matroskatag.cpp | 33 +++++++++- taglib/matroska/matroskatag.h | 15 +++++ taglib/toolkit/propertymapping.dox | 2 +- tests/test_matroska.cpp | 70 ++++++++++------------ 10 files changed, 99 insertions(+), 124 deletions(-) diff --git a/examples/matroskawriter.cpp b/examples/matroskawriter.cpp index 70ad8a80..211cfe3d 100644 --- a/examples/matroskawriter.cpp +++ b/examples/matroskawriter.cpp @@ -36,12 +36,8 @@ int main(int argc, char *argv[]) TagLib::FileStream image(argv[2]); auto data = image.readBlock(image.length()); auto attachments = file.attachments(true); - TagLib::Matroska::AttachedFile attachedFile; - attachedFile.setFileName("cover.jpg"); - attachedFile.setMediaType("image/jpeg"); - attachedFile.setData(data); - //attachedFile.setUID(5081000385627515072ull); - attachments->addAttachedFile(attachedFile); + attachments->addAttachedFile(TagLib::Matroska::AttachedFile( + data, "cover.jpg", "image/jpeg")); file.save(); diff --git a/taglib/matroska/ebml/ebmlmkattachments.cpp b/taglib/matroska/ebml/ebmlmkattachments.cpp index f57c9492..34941833 100644 --- a/taglib/matroska/ebml/ebmlmkattachments.cpp +++ b/taglib/matroska/ebml/ebmlmkattachments.cpp @@ -73,17 +73,9 @@ std::unique_ptr EBML::MkAttachments::parse() const if(!(filename && data)) continue; - Matroska::AttachedFile file; - file.setFileName(*filename); - file.setData(*data); - if(description) - file.setDescription(*description); - if(mediaType) - file.setMediaType(*mediaType); - if(uid) - file.setUID(uid); - - attachments->addAttachedFile(file); + attachments->addAttachedFile(Matroska::AttachedFile( + *data, *filename, mediaType ? *mediaType : String(), + uid, description ? *description : String())); } return attachments; } diff --git a/taglib/matroska/matroskaattachedfile.cpp b/taglib/matroska/matroskaattachedfile.cpp index f9fdf9bf..7957e86e 100644 --- a/taglib/matroska/matroskaattachedfile.cpp +++ b/taglib/matroska/matroskaattachedfile.cpp @@ -19,7 +19,6 @@ ***************************************************************************/ #include "matroskaattachedfile.h" -#include "tstring.h" #include "tbytevector.h" using namespace TagLib; @@ -27,7 +26,10 @@ using namespace TagLib; class Matroska::AttachedFile::AttachedFilePrivate { public: - AttachedFilePrivate() = default; + AttachedFilePrivate(const ByteVector &data, const String &fileName, + const String &mediaType, UID uid, const String &description) : + fileName(fileName), description(description), mediaType(mediaType), + data(data), uid(uid) {} ~AttachedFilePrivate() = default; String fileName; String description; @@ -40,8 +42,10 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Matroska::AttachedFile::AttachedFile() : - d(std::make_unique()) +Matroska::AttachedFile::AttachedFile(const ByteVector &data, + const String &fileName, const String &mediaType, UID uid, + const String &description) : + d(std::make_unique(data, fileName, mediaType, uid, description)) { } @@ -69,51 +73,26 @@ void Matroska::AttachedFile::swap(AttachedFile &other) noexcept swap(d, other.d); } -void Matroska::AttachedFile::setFileName(const String &fileName) -{ - d->fileName = fileName; -} - const String &Matroska::AttachedFile::fileName() const { return d->fileName; } -void Matroska::AttachedFile::setDescription(const String &description) -{ - d->description = description; -} - const String &Matroska::AttachedFile::description() const { return d->description; } -void Matroska::AttachedFile::setMediaType(const String &mediaType) -{ - d->mediaType = mediaType; -} - const String &Matroska::AttachedFile::mediaType() const { return d->mediaType; } -void Matroska::AttachedFile::setData(const ByteVector &data) -{ - d->data = data; -} - const ByteVector &Matroska::AttachedFile::data() const { return d->data; } -void Matroska::AttachedFile::setUID(UID uid) -{ - d->uid = uid; -} - Matroska::AttachedFile::UID Matroska::AttachedFile::uid() const { return d->uid; diff --git a/taglib/matroska/matroskaattachedfile.h b/taglib/matroska/matroskaattachedfile.h index 7df5f5e7..2b7a2964 100644 --- a/taglib/matroska/matroskaattachedfile.h +++ b/taglib/matroska/matroskaattachedfile.h @@ -22,6 +22,7 @@ #define TAGLIB_MATROSKAATTACHEDFILE_H #include +#include "tstring.h" #include "taglib_export.h" namespace TagLib { @@ -39,7 +40,9 @@ namespace TagLib { /*! * Construct an attached file. */ - AttachedFile(); + AttachedFile(const ByteVector &data, const String &fileName, + const String &mediaType, UID uid = 0, + const String &description = String()); /*! * Construct an attached file as a copy of \a other. @@ -71,51 +74,26 @@ namespace TagLib { */ void swap(AttachedFile &other) noexcept; - /*! - * Set the \a fileName of the attached file. - */ - void setFileName(const String &fileName); - /*! * Returns the filename of the attached file. */ const String &fileName() const; - /*! - * Set a human-friendly \a description for the attached file. - */ - void setDescription(const String &description); - /*! * Returns the human-friendly description for the attached file. */ const String &description() const; - /*! - * Set the \a mediaType of the attached file. - */ - void setMediaType(const String &mediaType); - /*! * Returns the media type of the attached file. */ const String &mediaType() const; - /*! - * Set the data of the attached file. - */ - void setData(const ByteVector &data); - /*! * Returns the data of the attached file. */ const ByteVector &data() const; - /*! - * Set the \a uid representing the file, as random as possible. - */ - void setUID(UID uid); - /*! * Returns the UID of the attached file. */ diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index e27f389d..e21278be 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -335,13 +335,9 @@ bool Matroska::File::setComplexProperties(const String &key, const List()); - attachedFile.setFileName(fileName); - attachedFile.setUID(uid); - d->attachments->addAttachedFile(attachedFile); + d->attachments->addAttachedFile(AttachedFile( + data, fileName, mimeType, uid, + property.value("description").value())); } } return true; diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp index a76833bd..1eb9a381 100644 --- a/taglib/matroska/matroskasimpletag.cpp +++ b/taglib/matroska/matroskasimpletag.cpp @@ -28,12 +28,12 @@ using namespace TagLib; class Matroska::SimpleTag::SimpleTagPrivate { public: - explicit SimpleTagPrivate(const String &name, const String &value, + SimpleTagPrivate(const String &name, const String &value, TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage, unsigned long long trackUid) : value(value), name(name), language(language), trackUid(trackUid), targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {} - explicit SimpleTagPrivate(const String &name, const ByteVector &value, + SimpleTagPrivate(const String &name, const ByteVector &value, TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage, unsigned long long trackUid) : value(value), name(name), language(language), trackUid(trackUid), diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index c9d260e7..eaec2da7 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -89,6 +89,35 @@ void Matroska::Tag::addSimpleTag(const SimpleTag &tag) setNeedsRender(true); } +void Matroska::Tag::addSimpleTags(const SimpleTagsList& simpleTags) +{ + d->tags.append(simpleTags); + setNeedsRender(true); +} + +void Matroska::Tag::insertSimpleTag(unsigned int index, const SimpleTag &tag) +{ + if(index < d->tags.size()) { + auto it = d->tags.begin(); + std::advance(it, index); + d->tags.insert(it, tag); + } + else { + d->tags.append(tag); + } + setNeedsRender(true); +} + +void Matroska::Tag::removeSimpleTag(unsigned int index) +{ + if(index < d->tags.size()) { + auto it = d->tags.begin(); + std::advance(it, index); + d->tags.erase(it); + setNeedsRender(true); + } +} + void Matroska::Tag::removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue, unsigned long long trackUid) @@ -323,7 +352,7 @@ namespace std::tuple("DISCNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Album, true), std::tuple("TRACKTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("DISCTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Album, true), - std::tuple("DATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album, false), + std::tuple("DATE", "DATE_RECORDED", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("TITLESORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album, true), std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Track, false), @@ -334,7 +363,7 @@ namespace std::tuple("DJMIXER", "MIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("REMIXER", "REMIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("INITIALKEY", "INITIAL_KEY", Matroska::SimpleTag::TargetTypeValue::Track, false), - std::tuple("RELEASEDATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Track, false), + std::tuple("RELEASEDATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album, false), std::tuple("ENCODINGTIME", "DATE_ENCODED", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("TAGGINGDATE", "DATE_TAGGED", Matroska::SimpleTag::TargetTypeValue::Track, false), std::tuple("ENCODEDBY", "ENCODER", Matroska::SimpleTag::TargetTypeValue::Track, false), diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 57aba7d7..21696cc0 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -99,6 +99,21 @@ namespace TagLib { */ void addSimpleTag(const SimpleTag &tag); + /*! + * Add multiple tag attributes. + */ + void addSimpleTags(const SimpleTagsList &simpleTags); + + /*! + * Insert a tag attribute at position \a index. + */ + void insertSimpleTag(unsigned int index, const SimpleTag &tag); + + /*! + * Remove a tag attribute at position \a index. + */ + void removeSimpleTag(unsigned int index); + /*! * Remove a tag attribute. */ diff --git a/taglib/toolkit/propertymapping.dox b/taglib/toolkit/propertymapping.dox index b3fcc68e..43f32bd3 100644 --- a/taglib/toolkit/propertymapping.dox +++ b/taglib/toolkit/propertymapping.dox @@ -34,7 +34,7 @@ | CONDUCTOR | TPE3 | | \----:com.apple.iTunes:CONDUCTOR | | WM/Conductor | | | COPYRIGHT | TCOP | ICOP | cprt | | | | | COPYRIGHTURL | WCOP | | | | | | -| DATE | TDRC | ICRD | ©day | YEAR | WM/Year | DATE_RELEASED/50 | +| DATE | TDRC | ICRD | ©day | YEAR | WM/Year | DATE_RECORDED/30 | | DISCNUMBER | TPOS | | disk | DISC | WM/PartOfSet | PART_NUMBER/50 | | DISCSUBTITLE | TSST | PRT1 | \----:com.apple.iTunes:DISCSUBTITLE | | WM/SetSubTitle | | | DISCTOTAL | | | | | | TOTAL_PARTS/50 | diff --git a/tests/test_matroska.cpp b/tests/test_matroska.cpp index f932039f..b3fd23d7 100644 --- a/tests/test_matroska.cpp +++ b/tests/test_matroska.cpp @@ -104,7 +104,7 @@ * * - Set simple tag with target type in a Matroska file: * examples/tagwriter -C PART_NUMBER \ - * name=PART_NUMBER,targetTypeValue=20,value=2 file.mka + * 'name=PART_NUMBER,targetTypeValue=20,value="2"' file.mka * * - Set simple tag with binary value in a Matroska file: * examples/tagwriter -C BINARY \ @@ -218,23 +218,22 @@ public: CPPUNIT_ASSERT(!f.attachments(false)); auto tag = f.tag(true); CPPUNIT_ASSERT(tag->isEmpty()); - tag->addSimpleTag(Matroska::SimpleTag( - "Test Name 1", String("Test Value 1"), - Matroska::SimpleTag::TargetTypeValue::Track, "en")); tag->addSimpleTag(Matroska::SimpleTag( "Test Name 2", String("Test Value 2"), Matroska::SimpleTag::TargetTypeValue::Album)); + tag->insertSimpleTag(0, Matroska::SimpleTag( + "Test Name 1", String("Test Value 1"), + Matroska::SimpleTag::TargetTypeValue::Track, "en")); + tag->insertSimpleTag(1, Matroska::SimpleTag( + "Test Name 3", String("Test Value 3"))); + tag->removeSimpleTag(1); tag->setTitle("Test title"); tag->setArtist("Test artist"); tag->setYear(1969); auto attachments = f.attachments(true); - Matroska::AttachedFile attachedFile; - attachedFile.setFileName("cover.jpg"); - attachedFile.setMediaType("image/jpeg"); - attachedFile.setDescription("Cover"); - attachedFile.setData(ByteVector("JPEG data")); - attachedFile.setUID(5081000385627515072ULL); - attachments->addAttachedFile(attachedFile); + attachments->addAttachedFile(Matroska::AttachedFile( + "JPEG data", "cover.jpg", "image/jpeg", 5081000385627515072ULL, + "Cover")); CPPUNIT_ASSERT(f.save()); } { @@ -285,17 +284,17 @@ public: CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[2].type()); CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[3].language()); - CPPUNIT_ASSERT_EQUAL(String("Test Name 2"), simpleTags[3].name()); - CPPUNIT_ASSERT_EQUAL(String("Test Value 2"), simpleTags[3].toString()); + CPPUNIT_ASSERT_EQUAL(String("DATE_RECORDED"), simpleTags[3].name()); + CPPUNIT_ASSERT_EQUAL(String("1969"), simpleTags[3].toString()); CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[3].toByteVector()); CPPUNIT_ASSERT_EQUAL(true, simpleTags[3].defaultLanguageFlag()); - CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Album, simpleTags[3].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[3].targetTypeValue()); CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[3].trackUid()); CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[3].type()); CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[4].language()); - CPPUNIT_ASSERT_EQUAL(String("DATE_RELEASED"), simpleTags[4].name()); - CPPUNIT_ASSERT_EQUAL(String("1969"), simpleTags[4].toString()); + CPPUNIT_ASSERT_EQUAL(String("Test Name 2"), simpleTags[4].name()); + CPPUNIT_ASSERT_EQUAL(String("Test Value 2"), simpleTags[4].toString()); CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[4].toByteVector()); CPPUNIT_ASSERT_EQUAL(true, simpleTags[4].defaultLanguageFlag()); CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Album, simpleTags[4].targetTypeValue()); @@ -383,7 +382,8 @@ public: Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); CPPUNIT_ASSERT(f.isValid()); CPPUNIT_ASSERT(!f.tag(false)); - f.attachments(true)->addAttachedFile(Matroska::AttachedFile()); + f.attachments(true)->addAttachedFile(Matroska::AttachedFile( + ByteVector(), "", "")); CPPUNIT_ASSERT(f.save()); } { @@ -487,7 +487,7 @@ public: PropertyMap initialProps; initialProps["ARTIST"] = StringList("Actors"); - initialProps["DATE"] = StringList("2023"); + initialProps["RELEASEDATE"] = StringList("2023"); initialProps["DESCRIPTION"] = StringList("Description"); initialProps["DIRECTOR"] = StringList("Director"); initialProps["ENCODEDBY"] = StringList("Lavf59.27.100"); @@ -562,7 +562,6 @@ public: CPPUNIT_ASSERT_EQUAL(String("handbrake"), tag->title()); CPPUNIT_ASSERT_EQUAL(String("Actors"), tag->artist()); - CPPUNIT_ASSERT_EQUAL(2023U, tag->year()); CPPUNIT_ASSERT_EQUAL(String(""), tag->album()); CPPUNIT_ASSERT_EQUAL(String(""), tag->comment()); CPPUNIT_ASSERT_EQUAL(String("Genre"), tag->genre()); @@ -695,15 +694,14 @@ public: } const StringList expectedSimpleTagNames { "BINARY", "TITLE", "ARTIST", "ARTISTSORT", "TITLESORT", - "DATE_RELEASED", "PART_NUMBER", "TOTAL_PARTS", - "MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMID", - "MUSICBRAINZ_RELEASEGROUPID", "ARTIST", "ARTISTS", "ARTISTSORT", + "PART_NUMBER", "TOTAL_PARTS", "MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMID", + "MUSICBRAINZ_RELEASEGROUPID", "DATE_RELEASED", "ARTIST", "ARTISTS", "ARTISTSORT", "ASIN", "BARCODE", "CATALOG_NUMBER", "CATALOG_NUMBER", "COMMENT", - "MIXED_BY", "ENCODER", "ENCODER_SETTINGS", "DATE_ENCODED", "GENRE", - "INITIAL_KEY", "ISRC", "LABEL_CODE", "LABEL_CODE", + "DATE_RECORDED", "MIXED_BY", "ENCODER", "ENCODER_SETTINGS", "DATE_ENCODED", + "GENRE", "INITIAL_KEY", "ISRC", "LABEL_CODE", "LABEL_CODE", "ORIGINAL_MEDIA_TYPE", "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ_RELEASETRACKID", "MUSICBRAINZ_TRACKID", "ORIGINALDATE", - "PURCHASE_OWNER", "RELEASECOUNTRY", "DATE_RELEASED", "RELEASESTATUS", + "PURCHASE_OWNER", "RELEASECOUNTRY", "RELEASESTATUS", "RELEASETYPE", "REMIXED_BY", "SCRIPT", "DATE_TAGGED", "TITLE", "PART_NUMBER", "TOTAL_PARTS" }; @@ -960,14 +958,10 @@ public: CPPUNIT_ASSERT(f.tag(false)); CPPUNIT_ASSERT(!f.attachments(false)); auto attachments = f.attachments(true); - Matroska::AttachedFile attachedFile; - attachedFile.setFileName("cover.jpg"); - attachedFile.setMediaType("image/jpeg"); - attachedFile.setDescription("Cover"); // Large enough for emitSizeChanged() from Matroska::Segment::render() - attachedFile.setData(ByteVector(20000, 'x')); - attachedFile.setUID(5081000385627515072ULL); - attachments->addAttachedFile(attachedFile); + attachments->addAttachedFile(Matroska::AttachedFile( + ByteVector(20000, 'x'), "cover.jpg", "image/jpeg", + 5081000385627515072ULL, "Cover")); CPPUNIT_ASSERT(f.save()); } { @@ -979,11 +973,11 @@ public: CPPUNIT_ASSERT(attachments); CPPUNIT_ASSERT(PropertyMap(SimplePropertyMap{ {"ARTIST", {"Actors"}}, - {"DATE", {"2023"}}, {"DESCRIPTION", {"Description"}}, {"DIRECTOR", {"Director"}}, {"ENCODEDBY", {"Lavf59.27.100"}}, {"GENRE", {"Genre"}}, + {"RELEASEDATE", {"2023"}}, {"SUMMARY", {"Comment"}}, {"SYNOPSIS", {"Plot"}} }) == tag->properties()); @@ -1161,13 +1155,9 @@ public: chapters->addChapterEdition(edition1); chapters->addChapterEdition(edition2); - Matroska::AttachedFile attachedFile; - attachedFile.setFileName("folder.png"); - attachedFile.setMediaType("image/png"); - attachedFile.setDescription("Cover"); - attachedFile.setData(ByteVector("PNG data")); - attachedFile.setUID(1763187649ULL); - f.attachments(true)->addAttachedFile(attachedFile); + f.attachments(true)->addAttachedFile(Matroska::AttachedFile( + ByteVector("PNG data"), "folder.png", "image/png", 1763187649ULL, + "Cover")); CPPUNIT_ASSERT(f.save()); } { From d83d751443051e788be05d87b8bee0d5aed83703 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Fri, 2 Jan 2026 15:30:41 +0100 Subject: [PATCH 31/31] Matroska: Fix build with older macOS and 32-bit Android compilers When building for macOS < 10.14, the API for std::optional and std::variant is restricted error: 'value' is unavailable: introduced in macOS 10.14 error: 'get<..>' is unavailable: introduced in macOS 10.14 There was also an issue with Android armeabi-v7a where long is not 64 bit and a static assertion failed. --- taglib/matroska/ebml/ebmlelement.cpp | 2 +- taglib/matroska/ebml/ebmlfloatelement.cpp | 12 +++++--- taglib/matroska/ebml/ebmlutils.cpp | 37 ++++++++--------------- taglib/matroska/ebml/ebmlutils.h | 6 ++-- taglib/matroska/matroskacues.cpp | 20 +++++++----- taglib/matroska/matroskasimpletag.cpp | 6 ++-- 6 files changed, 40 insertions(+), 43 deletions(-) diff --git a/taglib/matroska/ebml/ebmlelement.cpp b/taglib/matroska/ebml/ebmlelement.cpp index 691b6479..351e2870 100644 --- a/taglib/matroska/ebml/ebmlelement.cpp +++ b/taglib/matroska/ebml/ebmlelement.cpp @@ -52,7 +52,7 @@ std::unique_ptr EBML::Element::factory(File &file) } // Get the size length and data length - const auto &[sizeLength, dataSize] = readVINT(file); + const auto &[sizeLength, dataSize] = readVINT(file); if(!sizeLength) return nullptr; diff --git a/taglib/matroska/ebml/ebmlfloatelement.cpp b/taglib/matroska/ebml/ebmlfloatelement.cpp index 1460de8e..00ea199d 100644 --- a/taglib/matroska/ebml/ebmlfloatelement.cpp +++ b/taglib/matroska/ebml/ebmlfloatelement.cpp @@ -52,10 +52,12 @@ EBML::FloatElement::FloatVariantType EBML::FloatElement::getValue() const double EBML::FloatElement::getValueAsDouble(double defaultValue) const { if(std::holds_alternative(value)) { - return std::get(value); + // get_if() used instead of get() to support restricted compilers + return *std::get_if(&value); } if(std::holds_alternative(value)) { - return std::get(value); + // get_if() used instead of get() to support restricted compilers + return *std::get_if(&value); } return defaultValue; } @@ -93,10 +95,12 @@ ByteVector EBML::FloatElement::render() { ByteVector data; if(std::holds_alternative(value)) { - data = ByteVector::fromFloat64BE(std::get(value)); + // get_if() used instead of get() to support restricted compilers + data = ByteVector::fromFloat64BE(*std::get_if(&value)); } else if(std::holds_alternative(value)) { - data = ByteVector::fromFloat32BE(std::get(value)); + // get_if() used instead of get() to support restricted compilers + data = ByteVector::fromFloat32BE(*std::get_if(&value)); } ByteVector buffer = renderId(); buffer.append(renderVINT(data.size(), 0)); diff --git a/taglib/matroska/ebml/ebmlutils.cpp b/taglib/matroska/ebml/ebmlutils.cpp index e3baf7ee..60f05155 100644 --- a/taglib/matroska/ebml/ebmlutils.cpp +++ b/taglib/matroska/ebml/ebmlutils.cpp @@ -71,33 +71,25 @@ namespace TagLib::EBML { template unsigned int VINTSizeLength<8>(uint8_t firstByte); } -template -std::pair EBML::readVINT(File &file) +std::pair EBML::readVINT(File &file) { - static_assert(sizeof(T) == 8); auto buffer = file.readBlock(1); if(buffer.size() != 1) { debug("Failed to read VINT size"); return {0, 0}; } - unsigned int nb_bytes = VINTSizeLength<8>(*buffer.begin()); - if(!nb_bytes) + unsigned int numBytes = VINTSizeLength<8>(*buffer.begin()); + if(!numBytes) return {0, 0}; - if(nb_bytes > 1) - buffer.append(file.readBlock(nb_bytes - 1)); - const int bitsToShift = static_cast(sizeof(T) * 8) - 7 * nb_bytes; - offset_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift; - return { nb_bytes, static_cast(buffer.toLongLong(true)) & mask }; + if(numBytes > 1) + buffer.append(file.readBlock(numBytes - 1)); + const int bitsToShift = static_cast(sizeof(uint64_t) * 8) - 7 * numBytes; + const uint64_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift; + return { numBytes, buffer.toULongLong(true) & mask }; } -namespace TagLib::EBML { - template std::pair readVINT(File &file); - template std::pair readVINT(File &file); -} - -template -std::pair EBML::parseVINT(const ByteVector &buffer) +std::pair EBML::parseVINT(const ByteVector &buffer) { if(buffer.isEmpty()) return {0, 0}; @@ -106,14 +98,9 @@ std::pair EBML::parseVINT(const ByteVector &buffer) if(!numBytes) return {0, 0}; - const int bitsToShift = static_cast(sizeof(T) * 8) - 7 * numBytes; - offset_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift; - return { numBytes, static_cast(buffer.toLongLong(true)) & mask }; -} - -namespace TagLib::EBML { - template std::pair parseVINT(const ByteVector &buffer); - template std::pair parseVINT(const ByteVector &buffer); + const int bitsToShift = static_cast(sizeof(uint64_t) * 8) - 7 * numBytes; + const uint64_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift; + return { numBytes, buffer.toULongLong(true) & mask }; } ByteVector EBML::renderVINT(uint64_t number, int minSizeLength) diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h index 7ad23157..4038e5b0 100644 --- a/taglib/matroska/ebml/ebmlutils.h +++ b/taglib/matroska/ebml/ebmlutils.h @@ -37,11 +37,9 @@ namespace TagLib { template unsigned int VINTSizeLength(uint8_t firstByte); - template - std::pair readVINT(File &file); + std::pair readVINT(File &file); - template - std::pair parseVINT(const ByteVector &buffer); + std::pair parseVINT(const ByteVector &buffer); ByteVector renderVINT(uint64_t number, int minSizeLength); diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp index 2c4a4f63..c71f3fe0 100644 --- a/taglib/matroska/matroskacues.cpp +++ b/taglib/matroska/matroskacues.cpp @@ -66,28 +66,32 @@ ByteVector Matroska::Cues::renderInternal() // Relative position, optional if(cueTrack->getRelativePosition().has_value()) { auto relativePosition = EBML::make_unique_element(); - relativePosition->setValue(cueTrack->getRelativePosition().value()); + // operator*() used instead of value() to support restricted compilers + relativePosition->setValue(*cueTrack->getRelativePosition()); cueTrackElement->appendElement(std::move(relativePosition)); } // Duration, optional if(cueTrack->getDuration().has_value()) { auto duration = EBML::make_unique_element(); - duration->setValue(cueTrack->getDuration().value()); + // operator*() used instead of value() to support restricted compilers + duration->setValue(*cueTrack->getDuration()); cueTrackElement->appendElement(std::move(duration)); } // Block number, optional if(cueTrack->getBlockNumber().has_value()) { auto blockNumber = EBML::make_unique_element(); - blockNumber->setValue(cueTrack->getBlockNumber().value()); + // operator*() used instead of value() to support restricted compilers + blockNumber->setValue(*cueTrack->getBlockNumber()); cueTrackElement->appendElement(std::move(blockNumber)); } // Codec state, not in version 1 if(cueTrack->getCodecState().has_value()) { auto codecState = EBML::make_unique_element(); - codecState->setValue(cueTrack->getCodecState().value()); + // operator*() used instead of value() to support restricted compilers + codecState->setValue(*cueTrack->getCodecState()); cueTrackElement->appendElement(std::move(codecState)); } @@ -209,8 +213,9 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) debug("No cluster found at position"); return false; } - if(codecState.has_value() && codecState.value() != 0) { - file.seek(segmentDataOffset + codecState.value()); + if(codecState.has_value() && *codecState != 0) { + // operator*() used instead of value() to support restricted compilers + file.seek(segmentDataOffset + *codecState); if(EBML::Element::readId(file) != static_cast(EBML::Element::Id::MkCodecState)) { debug("No codec state found at position"); return false; @@ -296,8 +301,9 @@ bool Matroska::CueTrack::adjustOffset(offset_t offset, offset_t delta) clusterPosition += delta; ret = true; } + // operator*() used instead of value() to support restricted compilers if(offset_t codecStateValue; - codecState.has_value() && (codecStateValue = codecState.value()) != 0 && + codecState.has_value() && (codecStateValue = *codecState) != 0 && codecStateValue > offset) { codecState = codecStateValue + delta; ret = true; diff --git a/taglib/matroska/matroskasimpletag.cpp b/taglib/matroska/matroskasimpletag.cpp index 1eb9a381..cd9a57d5 100644 --- a/taglib/matroska/matroskasimpletag.cpp +++ b/taglib/matroska/matroskasimpletag.cpp @@ -126,7 +126,8 @@ Matroska::SimpleTag::ValueType Matroska::SimpleTag::type() const String Matroska::SimpleTag::toString() const { if(std::holds_alternative(d->value)) { - return std::get(d->value); + // get_if() used instead of get() to support restricted compilers + return *std::get_if(&d->value); } return {}; } @@ -134,7 +135,8 @@ String Matroska::SimpleTag::toString() const ByteVector Matroska::SimpleTag::toByteVector() const { if(std::holds_alternative(d->value)) { - return std::get(d->value); + // get_if() used instead of get() to support restricted compilers + return *std::get_if(&d->value); } return {}; }