Added write support

This commit is contained in:
complexlogic
2023-09-30 22:02:22 -07:00
committed by Urs Fleisch
parent 47e9b9a17c
commit 80837485cf
23 changed files with 756 additions and 152 deletions

View File

@ -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}

View 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;
}

View File

@ -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.

View File

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

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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(); }

View File

@ -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};
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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:

View File

@ -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>())
{

View File

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

View File

@ -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;
}

View File

@ -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;
};