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