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