diff --git a/taglib/matroska/ebml/ebmlelement.h b/taglib/matroska/ebml/ebmlelement.h index 55141cc8..cb020303 100644 --- a/taglib/matroska/ebml/ebmlelement.h +++ b/taglib/matroska/ebml/ebmlelement.h @@ -69,6 +69,7 @@ namespace TagLib::EBML { inline constexpr Element::Id MkSimpleTag = 0x67C8; inline constexpr Element::Id MkTagName = 0x45A3; inline constexpr Element::Id MkTagLanguage = 0x447A; + inline constexpr Element::Id MkTagBinary = 0x4485; inline constexpr Element::Id MkTagString = 0x4487; inline constexpr Element::Id MkTagsTagLanguage = 0x447A; inline constexpr Element::Id MkTagsLanguageDefault = 0x4484; diff --git a/taglib/matroska/ebml/ebmlmktags.cpp b/taglib/matroska/ebml/ebmlmktags.cpp index 422454a7..d594797e 100644 --- a/taglib/matroska/ebml/ebmlmktags.cpp +++ b/taglib/matroska/ebml/ebmlmktags.cpp @@ -19,6 +19,7 @@ ***************************************************************************/ #include "ebmlmktags.h" +#include "ebmlbinaryelement.h" #include "ebmluintelement.h" #include "ebmlstringelement.h" #include "matroskatag.h" @@ -79,7 +80,8 @@ Matroska::Tag *EBML::MkTags::parse() tagName = &(static_cast(simpleTagChild)->getValue()); else if(id == ElementIDs::MkTagString && !tagValueString) tagValueString = &(static_cast(simpleTagChild)->getValue()); - // TODO implement binary + else if(id == ElementIDs::MkTagBinary && !tagValueBinary) + tagValueBinary = &(static_cast(simpleTagChild)->getValue()); else if(id == ElementIDs::MkTagsTagLanguage && !language) language = &(static_cast(simpleTagChild)->getValue()); else if(id == ElementIDs::MkTagsLanguageDefault) @@ -96,7 +98,7 @@ Matroska::Tag *EBML::MkTags::parse() sTagString->setValue(*tagValueString); sTag = sTagString; } - else if(tagValueBinary) { + else { // tagValueBinary must be non null auto sTagBinary = new Matroska::SimpleTagBinary(); sTagBinary->setTargetTypeValue(targetTypeValue); sTagBinary->setValue(*tagValueBinary); diff --git a/taglib/matroska/matroskaattachments.h b/taglib/matroska/matroskaattachments.h index b74ffb54..a49e7b61 100644 --- a/taglib/matroska/matroskaattachments.h +++ b/taglib/matroska/matroskaattachments.h @@ -48,12 +48,15 @@ namespace TagLib { void removeAttachedFile(AttachedFile *file); void clear(); const AttachedFileList &attachedFileList() const; - bool render() override; private: friend class EBML::MkAttachments; friend class File; class AttachmentsPrivate; + + // private Element implementation + bool render() override; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; }; diff --git a/taglib/matroska/matroskacues.cpp b/taglib/matroska/matroskacues.cpp index ac1d2547..06d6e288 100644 --- a/taglib/matroska/matroskacues.cpp +++ b/taglib/matroska/matroskacues.cpp @@ -154,5 +154,6 @@ bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) bool Matroska::CueTrack::adjustOffset(offset_t, offset_t) { + // TODO implement return false; } diff --git a/taglib/matroska/matroskafile.cpp b/taglib/matroska/matroskafile.cpp index daa7ccd2..b0b02c60 100644 --- a/taglib/matroska/matroskafile.cpp +++ b/taglib/matroska/matroskafile.cpp @@ -28,6 +28,7 @@ #include "ebmlmksegment.h" #include "tlist.h" #include "tdebug.h" +#include "tagutils.h" #include #include @@ -57,10 +58,10 @@ public: // static members //////////////////////////////////////////////////////////////////////////////// -bool Matroska::File::isSupported(IOStream *) +bool Matroska::File::isSupported(IOStream *stream) { - // TODO implement - return false; + const ByteVector id = Utils::readHeader(stream, 4, false); + return id.startsWith("\x1A\x45\xDF\xA3"); } //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/matroska/matroskasegment.h b/taglib/matroska/matroskasegment.h index 96652c4f..dcd1163a 100644 --- a/taglib/matroska/matroskasegment.h +++ b/taglib/matroska/matroskasegment.h @@ -46,6 +46,5 @@ namespace TagLib::Matroska { }; } - #endif #endif diff --git a/taglib/matroska/matroskatag.cpp b/taglib/matroska/matroskatag.cpp index 39abe9c0..fe65d1e4 100644 --- a/taglib/matroska/matroskatag.cpp +++ b/taglib/matroska/matroskatag.cpp @@ -25,6 +25,7 @@ #include "matroskasimpletag.h" #include "ebmlmasterelement.h" #include "ebmlstringelement.h" +#include "ebmlbinaryelement.h" #include "ebmlmktags.h" #include "ebmluintelement.h" #include "ebmlutils.h" @@ -33,16 +34,62 @@ using namespace TagLib; -namespace TagLib::Matroska::Utils { - std::pair translateKey(const String &key); - String translateTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue); -} - class Matroska::Tag::TagPrivate { public: TagPrivate() = default; ~TagPrivate() = default; + + bool setTag(const String &key, const String &value); + const String *getTag(const String &key) const; + + template + int removeSimpleTags(T &&p) + { + auto &list = tags; + int numRemoved = 0; + for(auto it = list.begin(); it != list.end();) { + it = std::find_if(it, list.end(), std::forward(p)); + if(it != list.end()) { + delete *it; + *it = nullptr; + it = list.erase(it); + numRemoved++; + } + } + return numRemoved; + } + + template + SimpleTagsList findSimpleTags(T &&p) + { + auto &list = tags; + for(auto it = list.begin(); it != list.end();) { + it = std::find_if(it, list.end(), std::forward(p)); + if(it != list.end()) { + list.append(*it); + ++it; + } + } + return list; + } + + template + const SimpleTag *findSimpleTag(T &&p) const + { + auto &list = tags; + auto it = std::find_if(list.begin(), list.end(), std::forward(p)); + return it != list.end() ? *it : nullptr; + } + + template + SimpleTag *findSimpleTag(T &&p) + { + return const_cast( + const_cast(this)->findSimpleTag(std::forward(p)) + ); + } + List tags; ByteVector data; }; @@ -79,84 +126,74 @@ const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsList() const return d->tags; } -Matroska::SimpleTagsList &Matroska::Tag::simpleTagsListPrivate() -{ - return d->tags; -} - -const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsListPrivate() const -{ - return d->tags; -} - void Matroska::Tag::setTitle(const String &s) { - setTag("TITLE", s); + d->setTag("TITLE", s); } void Matroska::Tag::setArtist(const String &s) { - setTag("ARTIST", s); + d->setTag("ARTIST", s); } void Matroska::Tag::setAlbum(const String &s) { - setTag("ALBUM", s); + d->setTag("ALBUM", s); } void Matroska::Tag::setComment(const String &s) { - setTag("COMMENT", s); + d->setTag("COMMENT", s); } void Matroska::Tag::setGenre(const String &s) { - setTag("GENRE", s); + d->setTag("GENRE", s); } void Matroska::Tag::setYear(unsigned int i) { - setTag("DATE", String::number(i)); + d->setTag("DATE", String::number(i)); } void Matroska::Tag::setTrack(unsigned int i) { - setTag("TRACKNUMBER", String::number(i)); + d->setTag("TRACKNUMBER", String::number(i)); } String Matroska::Tag::title() const { - const auto value = getTag("TITLE"); + const auto value = d->getTag("TITLE"); return value ? *value : String(); } String Matroska::Tag::artist() const { - const auto value = getTag("ARTIST"); + const auto value = d->getTag("ARTIST"); return value ? *value : String(); } String Matroska::Tag::album() const { - const auto value = getTag("ALBUM"); + const auto value = d->getTag("ALBUM"); return value ? *value : String(); } String Matroska::Tag::comment() const { - const auto value = getTag("COMMENT"); + const auto value = d->getTag("COMMENT"); return value ? *value : String(); } String Matroska::Tag::genre() const { - const auto value = getTag("GENRE"); + const auto value = d->getTag("GENRE"); return value ? *value : String(); } unsigned int Matroska::Tag::year() const { - auto value = getTag("DATE"); + auto value = d->getTag("DATE"); if(!value) return 0; auto list = value->split("-"); @@ -165,7 +202,7 @@ unsigned int Matroska::Tag::year() const unsigned int Matroska::Tag::track() const { - auto value = getTag("TRACKNUMBER"); + auto value = d->getTag("TRACKNUMBER"); if(!value) return 0; auto list = value->split("-"); @@ -230,8 +267,10 @@ bool Matroska::Tag::render() tagValue->setValue(tStr->value()); t->appendElement(tagValue); } - else if((tBin = dynamic_cast(simpleTag))) { - // Todo + else if((tBin = dynamic_cast(simpleTag))) { + auto tagValue = new EBML::BinaryElement(EBML::ElementIDs::MkTagBinary); + tagValue->setValue(tBin->value()); + t->appendElement(tagValue); } // Language @@ -264,42 +303,72 @@ bool Matroska::Tag::render() namespace { // PropertyMap key, Tag name, Target type value + // If the key is the same as the name and the target type value is Track, + // no translation is needed because this is the default mapping. + // Therefore, keys like TITLE, ARTIST, GENRE, COMMENT, etc. are omitted. + // For offical tags, see https://www.matroska.org/technical/tagging.html constexpr std::array simpleTagsTranslation { - std::tuple("TITLE", "TITLE", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("ALBUM", "TITLE", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("ARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("ALBUMARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("SUBTITLE", "SUBTITLE", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("TRACKNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("TRACKTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("DISCNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Part), + std::tuple("DISCTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Part), std::tuple("DATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album), // Todo - original date - std::tuple("GENRE", "GENRE", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("COMMENT", "COMMENT", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("TITLESORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album), - std::tuple("COMPOSERSORT", "COMPOSERSORT", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("COMPOSER", "COMPOSER", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("LYRICIST", "LYRICIST", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("CONDUCTOR", "CONDUCTOR", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("REMIXER", "REMIXER", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("BPM", "BPM", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("COPYRIGHT", "COPYRIGHT", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ALBUMARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album), std::tuple("ENCODEDBY", "ENCODED_BY", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("MOOD", "MOOD", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("MEDIA", "ORIGINAL_MEDIA_TYPE", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("LABEL", "LABEL_CODE", Matroska::SimpleTag::TargetTypeValue::Track), std::tuple("CATALOGNUMBER", "CATALOG_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track), - std::tuple("BARCODE", "BARCODE", Matroska::SimpleTag::TargetTypeValue::Track), - // Todo MusicBrainz tags + std::tuple("DJMIXER", "MIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("REMIXER", "REMIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("INITIALKEY", "INITIAL_KEY", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("RELEASEDATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ENCODINGTIME", "DATE_ENCODED", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("TAGGINGDATE", "DATE_TAGGED", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ENCODEDBY", "ENCODER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("ENCODING", "ENCODER_SETTINGS", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("OWNER", "PURCHASE_OWNER", Matroska::SimpleTag::TargetTypeValue::Track), + std::tuple("MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMARTISTID", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("MUSICBRAINZ_ALBUMID", "MUSICBRAINZ_ALBUMID", Matroska::SimpleTag::TargetTypeValue::Album), + std::tuple("MUSICBRAINZ_RELEASEGROUPID", "MUSICBRAINZ_RELEASEGROUPID", Matroska::SimpleTag::TargetTypeValue::Album), }; + + std::pair translateKey(const String &key) + { + auto it = std::find_if(simpleTagsTranslation.cbegin(), + simpleTagsTranslation.cend(), + [&key](const auto &t) { return key == std::get<0>(t); } + ); + if(it != simpleTagsTranslation.end()) + return { std::get<1>(*it), std::get<2>(*it) }; + if (!key.isEmpty() && !key.startsWith("_")) + return { key, Matroska::SimpleTag::TargetTypeValue::Track }; + return { String(), Matroska::SimpleTag::TargetTypeValue::None }; + } + + String translateTag(const String &name, Matroska::SimpleTag::TargetTypeValue targetTypeValue) + { + auto it = std::find_if(simpleTagsTranslation.cbegin(), + simpleTagsTranslation.cend(), + [&name, targetTypeValue](const auto &t) { + return name == std::get<1>(t) + && targetTypeValue == std::get<2>(t); + } + ); + return it != simpleTagsTranslation.end() + ? String(std::get<0>(*it), String::UTF8) + : targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track && !name.startsWith("_") + ? name + : String(); + } } -bool Matroska::Tag::setTag(const String &key, const String &value) +bool Matroska::Tag::TagPrivate::setTag(const String &key, const String &value) { - const auto pair = Utils::translateKey(key); + const auto pair = translateKey(key); // Workaround Clang issue - no lambda capture of structured bindings const String &name = pair.first; auto targetTypeValue = pair.second; @@ -316,14 +385,14 @@ bool Matroska::Tag::setTag(const String &key, const String &value) t->setTargetTypeValue(targetTypeValue); t->setName(name); t->setValue(value); - addSimpleTag(t); + tags.append(t); } return true; } -const String *Matroska::Tag::getTag(const String &key) const +const String *Matroska::Tag::TagPrivate::getTag(const String &key) const { - const auto pair = Utils::translateKey(key); + const auto pair = translateKey(key); // Workaround Clang issue - no lambda capture of structured bindings const String &name = pair.first; auto targetTypeValue = pair.second; @@ -340,36 +409,23 @@ const String *Matroska::Tag::getTag(const String &key) const return tag ? &tag->value() : nullptr; } -std::pair Matroska::Utils::translateKey(const String &key) -{ - auto it = std::find_if(simpleTagsTranslation.cbegin(), - simpleTagsTranslation.cend(), - [&key](const auto &t) { return key == std::get<0>(t); } - ); - if(it != simpleTagsTranslation.end()) - return { std::get<1>(*it), std::get<2>(*it) }; - else - return { String(), SimpleTag::TargetTypeValue::None }; -} - -String Matroska::Utils::translateTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue) -{ - auto it = std::find_if(simpleTagsTranslation.cbegin(), - simpleTagsTranslation.cend(), - [&name, targetTypeValue](const auto &t) { - return name == std::get<1>(t) - && targetTypeValue == std::get<2>(t); - } - ); - return it != simpleTagsTranslation.end() ? std::get<0>(*it) : String(); -} - PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap) { PropertyMap unsupportedProperties; - for(const auto &[key, value] : propertyMap) { - if(!setTag(key, value.toString())) - unsupportedProperties[key] = value; + for(const auto &[key, values] : propertyMap) { + for(const auto &value : values) { + if(auto [name, targetTypeValue] = translateKey(key); + !name.isEmpty()) { + auto t = new SimpleTagString(); + t->setTargetTypeValue(targetTypeValue); + t->setName(name); + t->setValue(value); + d->tags.append(t); + } + else { + unsupportedProperties[key] = values; + } + } } return unsupportedProperties; } @@ -378,11 +434,11 @@ PropertyMap Matroska::Tag::properties() const { PropertyMap properties; SimpleTagString *tStr = nullptr; - for(auto simpleTag : d->tags) { + for(auto simpleTag : std::as_const(d->tags)) { if((tStr = dynamic_cast(simpleTag))) { - String key = Utils::translateTag(tStr->name(), tStr->targetTypeValue()); - if(!key.isEmpty() && !properties.contains(key)) - properties[key] = tStr->value(); + String key = translateTag(tStr->name(), tStr->targetTypeValue()); + if(!key.isEmpty()) + properties[key].append(tStr->value()); } } return properties; diff --git a/taglib/matroska/matroskatag.h b/taglib/matroska/matroskatag.h index 6f4a706a..5f4c0db2 100644 --- a/taglib/matroska/matroskatag.h +++ b/taglib/matroska/matroskatag.h @@ -18,12 +18,10 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAS_MATROSKATAG_H -#define HAS_MATROSKATAG_H +#ifndef TAGLIB_MATROSKATAG_H +#define TAGLIB_MATROSKATAG_H #include -#include -#include #include "tag.h" #include "tstring.h" @@ -48,10 +46,6 @@ namespace TagLib { public: Tag(); ~Tag() override; - void addSimpleTag(SimpleTag *tag); - void removeSimpleTag(SimpleTag *tag); - void clearSimpleTags(); - const SimpleTagsList &simpleTagsList() const; String title() const override; String artist() const override; String album() const override; @@ -67,64 +61,22 @@ namespace TagLib { void setYear(unsigned int i) override; void setTrack(unsigned int i) override; bool isEmpty() const override; - bool render() override; PropertyMap properties() const override; PropertyMap setProperties(const PropertyMap &propertyMap) override; - template - int removeSimpleTags(T &&p) - { - auto &list = simpleTagsListPrivate(); - int numRemoved = 0; - for(auto it = list.begin(); it != list.end();) { - it = std::find_if(it, list.end(), std::forward(p)); - if(it != list.end()) { - delete *it; - *it = nullptr; - it = list.erase(it); - numRemoved++; - } - } - return numRemoved; - } - template - SimpleTagsList findSimpleTags(T &&p) - { - auto &list = simpleTagsListPrivate(); - for(auto it = list.begin(); it != list.end();) { - it = std::find_if(it, list.end(), std::forward(p)); - if(it != list.end()) { - list.append(*it); - ++it; - } - } - return list; - } - - template - const SimpleTag *findSimpleTag(T &&p) const - { - auto &list = simpleTagsListPrivate(); - auto it = std::find_if(list.begin(), list.end(), std::forward(p)); - return it != list.end() ? *it : nullptr; - } - - template - SimpleTag *findSimpleTag(T &&p) - { - return const_cast( - const_cast(this)->findSimpleTag(std::forward(p)) - ); - } + void addSimpleTag(SimpleTag *tag); + void removeSimpleTag(SimpleTag *tag); + void clearSimpleTags(); + const SimpleTagsList &simpleTagsList() const; private: friend class File; friend class EBML::MkTags; - SimpleTagsList &simpleTagsListPrivate(); - const SimpleTagsList &simpleTagsListPrivate() const; - bool setTag(const String &key, const String &value); - const String *getTag(const String &key) const; class TagPrivate; + + // private Element implementation + bool render() override; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE std::unique_ptr d; };