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);