Merge pull request #1149 from complexlogic/matroska

Another Matroska Attempt
This commit is contained in:
Urs Fleisch
2026-01-24 15:27:22 +01:00
committed by GitHub
81 changed files with 8846 additions and 105 deletions

View File

@@ -100,6 +100,7 @@ include(ConfigureChecks.cmake)
option(WITH_APE "Build with APE, MPC, WavPack" ON)
option(WITH_ASF "Build with ASF" ON)
option(WITH_DSF "Build with DSF" ON)
option(WITH_MATROSKA "Build with Matroska" ON)
option(WITH_MOD "Build with Tracker modules" ON)
option(WITH_MP4 "Build with MP4" ON)
option(WITH_RIFF "Build with AIFF, RIFF, WAV" ON)
@@ -197,6 +198,9 @@ endif()
if(WITH_DSF)
set(TAGLIB_WITH_DSF TRUE)
endif()
if(WITH_MATROSKA)
set(TAGLIB_WITH_MATROSKA TRUE)
endif()
if(WITH_MOD)
set(TAGLIB_WITH_MOD TRUE)
endif()

View File

@@ -63,6 +63,7 @@ and ID3 tags cannot be disabled. The following CMake options are available:
| `WITH_APE` | Build with APE, MPC, WavPack (default ON) |
| `WITH_ASF` | Build with ASF (default ON) |
| `WITH_DSF` | Build with DSF (default ON) |
| `WITH_MATROSKA` | Build with Matroska (default ON) |
| `WITH_MOD` | Build with Tracker modules (default ON) |
| `WITH_MP4` | Build with MP4 (default ON) |
| `WITH_RIFF` | Build with AIFF, RIFF, WAV (default ON) |

View File

@@ -63,6 +63,12 @@ if(WITH_SHORTEN)
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/shorten
)
endif()
if(WITH_MATROSKA)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/matroska
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/matroska/ebml
)
endif()
include_directories(${tag_c_HDR_DIRS})
set(tag_c_HDRS tag_c.h)

View File

@@ -80,6 +80,9 @@
#ifdef TAGLIB_WITH_SHORTEN
#include "shortenfile.h"
#endif
#ifdef TAGLIB_WITH_MATROSKA
#include "matroskafile.h"
#endif
using namespace TagLib;
@@ -241,6 +244,11 @@ TagLib_File *taglib_file_new_type_any_char(const T *filename, TagLib_File_Type t
case TagLib_File_SHORTEN:
file = new Shorten::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_MATROSKA
case TagLib_File_MATROSKA:
file = new Matroska::File(filename);
break;
#endif
default:
break;

View File

@@ -132,7 +132,8 @@ typedef enum {
TagLib_File_Opus,
TagLib_File_DSF,
TagLib_File_DSDIFF,
TagLib_File_SHORTEN
TagLib_File_SHORTEN,
TagLib_File_MATROSKA
} TagLib_File_Type;
/*!

View File

@@ -2,6 +2,7 @@ include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/../taglib
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ape
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/matroska
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2
@@ -38,6 +39,18 @@ target_link_libraries(framelist tag)
add_executable(strip-id3v1 strip-id3v1.cpp)
target_link_libraries(strip-id3v1 tag)
if(WITH_MATROSKA)
########### next target ###############
add_executable(matroskareader matroskareader.cpp)
target_link_libraries(matroskareader tag)
########### next target ###############
add_executable(matroskawriter matroskawriter.cpp)
target_link_libraries(matroskawriter tag)
endif()
install(TARGETS tagreader tagreader_c tagwriter framelist strip-id3v1
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}

124
examples/matroskareader.cpp Normal file
View File

@@ -0,0 +1,124 @@
#include <cstdio>
#include "matroskafile.h"
#include "matroskatag.h"
#include "matroskasimpletag.h"
#include "matroskaattachments.h"
#include "matroskaattachedfile.h"
#include "matroskachapters.h"
#include "matroskachapteredition.h"
#include "tstring.h"
#include "tutils.h"
#include "tbytevector.h"
#define GREEN_TEXT(s) "" s ""
#define PRINT_PRETTY(label, value) printf(" " GREEN_TEXT(label) ": %s\n", value)
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("Usage: matroskareader FILE\n");
return 1;
}
TagLib::Matroska::File file(argv[1]);
if(!file.isValid()) {
printf("File is not valid\n");
return 1;
}
auto tag = dynamic_cast<TagLib::Matroska::Tag*>(file.tag());
if(!tag) {
printf("File has no tag\n");
return 0;
}
const TagLib::Matroska::SimpleTagsList &list = tag->simpleTagsList();
printf("Found %u tag(s):\n", list.size());
for(const TagLib::Matroska::SimpleTag &t : list) {
PRINT_PRETTY("Tag Name", t.name().toCString(true));
if(t.type() == TagLib::Matroska::SimpleTag::StringType)
PRINT_PRETTY("Tag Value", t.toString().toCString(true));
else if(t.type() == TagLib::Matroska::SimpleTag::BinaryType)
PRINT_PRETTY("Tag Value",
TagLib::Utils::formatString("Binary with size %i", t.toByteVector().size()).toCString(false)
);
auto targetTypeValue = static_cast<unsigned int>(t.targetTypeValue());
PRINT_PRETTY("Target Type Value",
targetTypeValue == 0 ? "None" : TagLib::Utils::formatString("%i", targetTypeValue).toCString(false)
);
if(auto trackUid = t.trackUid()) {
PRINT_PRETTY("Track UID",
TagLib::Utils::formatString("%llu",trackUid).toCString(false)
);
}
const TagLib::String &language = t.language();
PRINT_PRETTY("Language", !language.isEmpty() ? language.toCString(false) : "Not set");
printf("\n");
}
TagLib::Matroska::Attachments *attachments = file.attachments();
if(attachments) {
const TagLib::Matroska::Attachments::AttachedFileList &list = attachments->attachedFileList();
printf("Found %u attachment(s)\n", list.size());
for(const auto &attachedFile : list) {
PRINT_PRETTY("Filename", attachedFile.fileName().toCString(true));
const TagLib::String &description = attachedFile.description();
PRINT_PRETTY("Description", !description.isEmpty() ? description.toCString(true) : "None");
const TagLib::String &mediaType = attachedFile.mediaType();
PRINT_PRETTY("Media Type", !mediaType.isEmpty() ? mediaType.toCString(false) : "None");
PRINT_PRETTY("Data Size",
TagLib::Utils::formatString("%u byte(s)", attachedFile.data().size()).toCString(false)
);
PRINT_PRETTY("UID",
TagLib::Utils::formatString("%llu", attachedFile.uid()).toCString(false)
);
}
}
else
printf("File has no attachments\n");
TagLib::Matroska::Chapters *chapters = file.chapters();
if(chapters) {
printf("Chapters:\n");
const TagLib::Matroska::Chapters::ChapterEditionList &editions = chapters->chapterEditionList();
for(const auto &edition : editions) {
if(edition.uid()) {
PRINT_PRETTY("Edition UID", TagLib::Utils::formatString("%llu", edition.uid())
.toCString(false));
}
PRINT_PRETTY("Edition Flags", TagLib::Utils::formatString("default=%d, ordered=%d",
edition.isDefault(), edition.isOrdered())
.toCString(false));
printf("\n");
for(const auto &chapter : edition.chapterList()) {
PRINT_PRETTY("Chapter UID", TagLib::Utils::formatString("%llu", chapter.uid())
.toCString(false));
PRINT_PRETTY("Chapter flags", TagLib::Utils::formatString("hidden=%d", chapter.isHidden())
.toCString(false));
PRINT_PRETTY("Start-End", TagLib::Utils::formatString("%llu-%llu",
chapter.timeStart(), chapter.timeEnd()).toCString(false));
for(const auto &display : chapter.displayList()) {
PRINT_PRETTY("Display", display.string().toCString(false));
PRINT_PRETTY("Language", !display.language().isEmpty()
? display.language().toCString(false) : "Not set");
}
printf("\n");
}
}
}
else
printf("File has no chapters\n");
if(auto properties = dynamic_cast<const TagLib::Matroska::Properties *>(file.audioProperties())) {
printf("Properties:\n");
PRINT_PRETTY("Doc Type", properties->docType().toCString(false));
PRINT_PRETTY("Doc Type Version", TagLib::String::number(properties->docTypeVersion()).toCString(false));
PRINT_PRETTY("Codec Name", properties->codecName().toCString(true));
PRINT_PRETTY("Bitrate", TagLib::String::number(properties->bitrate()).toCString(false));
PRINT_PRETTY("Sample Rate", TagLib::String::number(properties->sampleRate()).toCString(false));
PRINT_PRETTY("Channels", TagLib::String::number(properties->channels()).toCString(false));
PRINT_PRETTY("Length [ms]", TagLib::String::number(properties->lengthInMilliseconds()).toCString(false));
}
return 0;
}

View File

@@ -0,0 +1,45 @@
#include <cstdio>
#include "matroskafile.h"
#include "matroskatag.h"
#include "matroskasimpletag.h"
#include "matroskaattachments.h"
#include "matroskaattachedfile.h"
#include "tfilestream.h"
#include "tstring.h"
#include "tutils.h"
int main(int argc, char *argv[])
{
if(argc != 3) {
printf("Usage: matroskawriter FILE ARTWORK\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();
tag->addSimpleTag(TagLib::Matroska::SimpleTag(
"Test Name 1", TagLib::String("Test Value 1"),
TagLib::Matroska::SimpleTag::TargetTypeValue::Track, "en"));
tag->addSimpleTag(TagLib::Matroska::SimpleTag(
"Test Name 2", TagLib::String("Test Value 2"),
TagLib::Matroska::SimpleTag::TargetTypeValue::Album));
tag->setTitle("Test title");
tag->setArtist("Test artist");
tag->setYear(1969);
TagLib::FileStream image(argv[2]);
auto data = image.readBlock(image.length());
auto attachments = file.attachments(true);
attachments->addAttachedFile(TagLib::Matroska::AttachedFile(
data, "cover.jpg", "image/jpeg"));
file.save();
return 0;
}

View File

@@ -71,6 +71,7 @@ void usage()
std::cout << " -R <tagname> <tagvalue>" << std::endl;
std::cout << " -I <tagname> <tagvalue>" << std::endl;
std::cout << " -D <tagname>" << std::endl;
std::cout << " -C <complex-property-key> <key1=val1,key2=val2,...>" << std::endl;
std::cout << " -p <picturefile> <description> (\"\" \"\" to remove)" << std::endl;
std::cout << std::endl;
@@ -95,6 +96,102 @@ void checkForRejectedProperties(const TagLib::PropertyMap &tags)
}
}
/*!
* Create a list of variant maps from a string.
* The shorthand syntax in the string is kept simple, but should be sufficient
* for testing. Multiple maps are separated by ';', values within a map are
* assigned with key=value and separated by a ','. Types are detected, use
* double quotes to force a string. A ByteVector can be constructed from the
* contents of a file, the path is given after "file://". There is no escape
* character, use hex codes for ',' (\x2C) or ';' (\x3B).
*/
TagLib::List<TagLib::VariantMap> parseComplexPropertyValues(const TagLib::String &str)
{
if(str.isEmpty() || str == "\"\"" || str == "''") {
return {};
}
TagLib::List<TagLib::VariantMap> values;
const auto valueStrs = str.split(";");
for(const auto &valueStr : valueStrs) {
TagLib::VariantMap value;
const auto keyValStrs = valueStr.split(",");
for(const auto &keyValStr : keyValStrs) {
if(int equalPos = keyValStr.find('='); equalPos != -1) {
TagLib::String key = keyValStr.substr(0, equalPos);
TagLib::String valStr = keyValStr.substr(equalPos + 1);
bool hasDot = false;
bool hasNonNumeric = false;
bool hasSign = false;
for(auto it = valStr.cbegin(); it != valStr.cend(); ++it) {
if(it == valStr.cbegin() && (*it == '-' || *it == '+')) {
hasSign = true;
}
else if(*it == '.') {
hasDot = true;
}
else if(*it < '0' || *it > '9') {
hasNonNumeric = true;
}
}
TagLib::Variant val;
if(valStr == "null") {
// keep empty variant
}
else if(valStr == "true" || valStr == "false") {
val = TagLib::Variant(valStr == "true");
}
else if(!hasNonNumeric && hasDot) {
val = TagLib::Variant(std::stod(valStr.to8Bit()));
}
else if(!hasNonNumeric && hasSign) {
val = valStr.toLongLong(nullptr);
}
else if(!hasNonNumeric) {
val = valStr.toULongLong(nullptr);
}
else if(valStr.startsWith("file://")) {
auto filePath = valStr.substr(7 );
if(isFile(filePath.toCString())) {
std::ifstream fs;
fs.open(filePath.toCString(), std::ios::in | std::ios::binary);
std::stringstream buffer;
buffer << fs.rdbuf();
fs.close();
TagLib::String buf(buffer.str());
val = TagLib::Variant(buf.data(TagLib::String::Latin1));
}
else {
std::cout << filePath.toCString() << " not found." << std::endl;
val = TagLib::Variant(TagLib::ByteVector());
}
}
else {
int len = valStr.size();
if(len >= 2 && valStr[0] == '"' && valStr[len - 1] == '"') {
valStr = valStr.substr(1, len - 2);
}
int hexPos = 0;
while((hexPos = valStr.find("\\x", hexPos)) != -1) {
char ch;
bool ok;
if(static_cast<int>(valStr.length()) < hexPos + 4 ||
(ch = static_cast<char>(
valStr.substr(hexPos + 2, 2).toLongLong(&ok, 16)), !ok)) {
break;
}
valStr = valStr.substr(0, hexPos) + ch + valStr.substr(hexPos + 4);
++hexPos;
}
val = TagLib::Variant(valStr);
}
value.insert(key, val);
}
}
values.append(value);
}
return values;
}
int main(int argc, char *argv[])
{
TagLib::List<TagLib::FileRef> fileList;
@@ -170,6 +267,19 @@ int main(int argc, char *argv[])
checkForRejectedProperties(f.setProperties(map));
break;
}
case 'C': {
if(i + 2 < argc) {
numArgsConsumed = 3;
if(!value.isEmpty()) {
TagLib::List<TagLib::VariantMap> values = parseComplexPropertyValues(argv[i + 2]);
f.setComplexProperties(value, values);
}
}
else {
usage();
}
break;
}
case 'p': {
if(i + 2 < argc) {
numArgsConsumed = 3;

View File

@@ -64,8 +64,15 @@ if(WITH_SHORTEN)
${CMAKE_CURRENT_SOURCE_DIR}/shorten
)
endif()
if(WITH_MATROSKA)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/matroska
${CMAKE_CURRENT_SOURCE_DIR}/matroska/ebml
)
endif()
include_directories(${tag_HDR_DIRS})
set(tag_PRIVATE_HDRS)
set(tag_HDRS
tag.h
fileref.h
@@ -220,6 +227,41 @@ if(WITH_SHORTEN)
shorten/shortentag.h
)
endif()
if(WITH_MATROSKA)
set(tag_HDRS ${tag_HDRS}
matroska/matroskaattachedfile.h
matroska/matroskaattachments.h
matroska/matroskachapter.h
matroska/matroskachapteredition.h
matroska/matroskachapters.h
matroska/matroskaelement.h
matroska/matroskafile.h
matroska/matroskaproperties.h
matroska/matroskasimpletag.h
matroska/matroskatag.h
)
set(tag_PRIVATE_HDRS ${tag_PRIVATE_HDRS}
matroska/ebml/ebmlbinaryelement.h
matroska/ebml/ebmlelement.h
matroska/ebml/ebmlmasterelement.h
matroska/ebml/ebmlmkattachments.h
matroska/ebml/ebmlmkchapters.h
matroska/ebml/ebmlmkcues.h
matroska/ebml/ebmlmkseekhead.h
matroska/ebml/ebmlmksegment.h
matroska/ebml/ebmlmkinfo.h
matroska/ebml/ebmlmktracks.h
matroska/ebml/ebmlmktags.h
matroska/ebml/ebmlstringelement.h
matroska/ebml/ebmluintelement.h
matroska/ebml/ebmlfloatelement.h
matroska/ebml/ebmlutils.h
matroska/ebml/ebmlvoidelement.h
matroska/matroskacues.h
matroska/matroskaseekhead.h
matroska/matroskasegment.h
)
endif()
set(mpeg_SRCS
mpeg/mpegfile.cpp
@@ -410,6 +452,43 @@ if(WITH_SHORTEN)
)
endif()
if(WITH_MATROSKA)
set(matroska_SRCS
matroska/matroskaattachedfile.cpp
matroska/matroskaattachments.cpp
matroska/matroskachapter.cpp
matroska/matroskachapteredition.cpp
matroska/matroskachapters.cpp
matroska/matroskacues.cpp
matroska/matroskaelement.cpp
matroska/matroskafile.cpp
matroska/matroskaproperties.cpp
matroska/matroskaseekhead.cpp
matroska/matroskasegment.cpp
matroska/matroskasimpletag.cpp
matroska/matroskatag.cpp
)
set(ebml_SRCS
matroska/ebml/ebmlbinaryelement.cpp
matroska/ebml/ebmlelement.cpp
matroska/ebml/ebmlmasterelement.cpp
matroska/ebml/ebmlmkattachments.cpp
matroska/ebml/ebmlmkchapters.cpp
matroska/ebml/ebmlmkcues.cpp
matroska/ebml/ebmlmkseekhead.cpp
matroska/ebml/ebmlmksegment.cpp
matroska/ebml/ebmlmkinfo.cpp
matroska/ebml/ebmlmktracks.cpp
matroska/ebml/ebmlmktags.cpp
matroska/ebml/ebmlstringelement.cpp
matroska/ebml/ebmluintelement.cpp
matroska/ebml/ebmlfloatelement.cpp
matroska/ebml/ebmlutils.cpp
matroska/ebml/ebmlvoidelement.cpp
)
endif()
set(toolkit_SRCS
toolkit/tstring.cpp
toolkit/tstringlist.cpp
@@ -433,7 +512,7 @@ set(tag_LIB_SRCS
${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS}
${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS}
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS}
${dsf_SRCS} ${dsdiff_SRCS} ${shorten_SRCS}
${dsf_SRCS} ${dsdiff_SRCS} ${shorten_SRCS} ${matroska_SRCS} ${ebml_SRCS}
tag.cpp
tagunion.cpp
fileref.cpp
@@ -441,7 +520,7 @@ set(tag_LIB_SRCS
tagutils.cpp
)
add_library(tag ${tag_LIB_SRCS} ${tag_HDRS})
add_library(tag ${tag_LIB_SRCS} ${tag_HDRS} ${tag_PRIVATE_HDRS})
target_include_directories(tag INTERFACE
$<INSTALL_INTERFACE:include>

View File

@@ -77,6 +77,9 @@
#ifdef TAGLIB_WITH_SHORTEN
#include "shortenfile.h"
#endif
#ifdef TAGLIB_WITH_MATROSKA
#include "matroskafile.h"
#endif
using namespace TagLib;
@@ -220,6 +223,10 @@ namespace
else if(ext == "SHN")
file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_MATROSKA
else if(ext == "MKA" || ext == "MKV" || ext == "WEBM")
file = new Matroska::File(stream, readAudioProperties);
#endif
// if file is not valid, leave it to content-based detection.
@@ -289,6 +296,10 @@ namespace
else if(Shorten::File::isSupported(stream))
file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_MATROSKA
else if(Matroska::File::isSupported(stream))
file = new Matroska::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
// isSupported() only does a quick check, so double check the file here.
@@ -513,6 +524,11 @@ StringList FileRef::defaultFileExtensions()
#ifdef TAGLIB_WITH_SHORTEN
l.append("shn");
#endif
#ifdef TAGLIB_WITH_MATROSKA
l.append("mkv");
l.append("mka");
l.append("webm");
#endif
return l;
}

View File

@@ -0,0 +1,70 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlbinaryelement.h"
#include "ebmlutils.h"
#include "tfile.h"
#include "tdebug.h"
using namespace TagLib;
EBML::BinaryElement::BinaryElement(Id id, int sizeLength, offset_t dataSize):
Element(id, sizeLength, dataSize)
{
}
EBML::BinaryElement::BinaryElement(Id id, int sizeLength, offset_t dataSize, offset_t):
Element(id, sizeLength, dataSize)
{
}
EBML::BinaryElement::BinaryElement(Id id):
Element(id, 0, 0)
{
}
const ByteVector& EBML::BinaryElement::getValue() const
{
return value;
}
void EBML::BinaryElement::setValue(const ByteVector& val)
{
value = val;
}
bool EBML::BinaryElement::read(File &file)
{
value = file.readBlock(dataSize);
if(value.size() != dataSize) {
debug("Failed to read binary element");
return false;
}
return true;
}
ByteVector EBML::BinaryElement::render()
{
ByteVector buffer = renderId();
dataSize = value.size();
buffer.append(renderVINT(dataSize, 0));
buffer.append(value);
return buffer;
}

View File

@@ -0,0 +1,51 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLBINARYELEMENT_H
#define TAGLIB_EBMLBINARYELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlelement.h"
#include "tbytevector.h"
namespace TagLib {
class File;
namespace EBML {
class BinaryElement : public Element
{
public:
BinaryElement(Id id, int sizeLength, offset_t dataSize);
BinaryElement(Id id, int sizeLength, offset_t dataSize, offset_t);
explicit BinaryElement(Id id);
const ByteVector &getValue() const;
void setValue(const ByteVector &val);
bool read(File &file) override;
ByteVector render() override;
private:
ByteVector value;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,213 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlelement.h"
#include "ebmlvoidelement.h"
#include "ebmlmasterelement.h"
#include "ebmlbinaryelement.h"
#include "ebmlfloatelement.h"
#include "ebmlmkseekhead.h"
#include "ebmlmksegment.h"
#include "ebmlmktags.h"
#include "ebmlmkattachments.h"
#include "ebmlmkchapters.h"
#include "ebmlmktracks.h"
#include "ebmlstringelement.h"
#include "ebmluintelement.h"
#include "ebmlutils.h"
#include "tfile.h"
#include "tdebug.h"
#include "tutils.h"
using namespace TagLib;
#define RETURN_ELEMENT_FOR_CASE(eid) \
case (eid): return make_unique_element<eid>(id, sizeLength, dataSize, offset)
std::unique_ptr<EBML::Element> EBML::Element::factory(File &file)
{
// Get the element ID
const offset_t offset = file.tell();
unsigned int uintId = readId(file);
if(!uintId) {
debug("Failed to parse EMBL ElementID");
return nullptr;
}
// Get the size length and data length
const auto &[sizeLength, dataSize] = readVINT(file);
if(!sizeLength)
return nullptr;
// Return the subclass
// The enum switch without default will give us a warning if an ID is missing
auto id = static_cast<Id>(uintId);
switch(id) {
RETURN_ELEMENT_FOR_CASE(Id::EBMLHeader);
RETURN_ELEMENT_FOR_CASE(Id::DocType);
RETURN_ELEMENT_FOR_CASE(Id::DocTypeVersion);
RETURN_ELEMENT_FOR_CASE(Id::MkSegment);
RETURN_ELEMENT_FOR_CASE(Id::MkInfo);
RETURN_ELEMENT_FOR_CASE(Id::MkTracks);
RETURN_ELEMENT_FOR_CASE(Id::MkTags);
RETURN_ELEMENT_FOR_CASE(Id::MkAttachments);
RETURN_ELEMENT_FOR_CASE(Id::MkTag);
RETURN_ELEMENT_FOR_CASE(Id::MkTagTargets);
RETURN_ELEMENT_FOR_CASE(Id::MkSimpleTag);
RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFile);
RETURN_ELEMENT_FOR_CASE(Id::MkSeek);
RETURN_ELEMENT_FOR_CASE(Id::MkTrackEntry);
RETURN_ELEMENT_FOR_CASE(Id::MkAudio);
RETURN_ELEMENT_FOR_CASE(Id::MkTagName);
RETURN_ELEMENT_FOR_CASE(Id::MkTagString);
RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileName);
RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileDescription);
RETURN_ELEMENT_FOR_CASE(Id::MkTagLanguage);
RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileMediaType);
RETURN_ELEMENT_FOR_CASE(Id::MkCodecID);
RETURN_ELEMENT_FOR_CASE(Id::MkTagTargetTypeValue);
RETURN_ELEMENT_FOR_CASE(Id::MkTagTrackUID);
RETURN_ELEMENT_FOR_CASE(Id::MkTagsLanguageDefault);
RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileUID);
RETURN_ELEMENT_FOR_CASE(Id::MkSeekPosition);
RETURN_ELEMENT_FOR_CASE(Id::MkTimestampScale);
RETURN_ELEMENT_FOR_CASE(Id::MkBitDepth);
RETURN_ELEMENT_FOR_CASE(Id::MkChannels);
RETURN_ELEMENT_FOR_CASE(Id::MkAttachedFileData);
RETURN_ELEMENT_FOR_CASE(Id::MkSeekID);
RETURN_ELEMENT_FOR_CASE(Id::MkDuration);
RETURN_ELEMENT_FOR_CASE(Id::MkTitle);
RETURN_ELEMENT_FOR_CASE(Id::MkSamplingFrequency);
RETURN_ELEMENT_FOR_CASE(Id::MkSeekHead);
RETURN_ELEMENT_FOR_CASE(Id::VoidElement);
RETURN_ELEMENT_FOR_CASE(Id::MkCluster);
RETURN_ELEMENT_FOR_CASE(Id::MkCodecState);
RETURN_ELEMENT_FOR_CASE(Id::MkTagBinary);
RETURN_ELEMENT_FOR_CASE(Id::MkCues);
RETURN_ELEMENT_FOR_CASE(Id::MkCuePoint);
RETURN_ELEMENT_FOR_CASE(Id::MkCueTime);
RETURN_ELEMENT_FOR_CASE(Id::MkCueTrackPositions);
RETURN_ELEMENT_FOR_CASE(Id::MkCueTrack);
RETURN_ELEMENT_FOR_CASE(Id::MkCueClusterPosition);
RETURN_ELEMENT_FOR_CASE(Id::MkCueRelativePosition);
RETURN_ELEMENT_FOR_CASE(Id::MkCueDuration);
RETURN_ELEMENT_FOR_CASE(Id::MkCueBlockNumber);
RETURN_ELEMENT_FOR_CASE(Id::MkCueCodecState);
RETURN_ELEMENT_FOR_CASE(Id::MkCueReference);
RETURN_ELEMENT_FOR_CASE(Id::MkCueRefTime);
RETURN_ELEMENT_FOR_CASE(Id::MkChapters);
RETURN_ELEMENT_FOR_CASE(Id::MkEditionEntry);
RETURN_ELEMENT_FOR_CASE(Id::MkEditionUID);
RETURN_ELEMENT_FOR_CASE(Id::MkEditionFlagDefault);
RETURN_ELEMENT_FOR_CASE(Id::MkEditionFlagOrdered);
RETURN_ELEMENT_FOR_CASE(Id::MkChapterAtom);
RETURN_ELEMENT_FOR_CASE(Id::MkChapterUID);
RETURN_ELEMENT_FOR_CASE(Id::MkChapterTimeStart);
RETURN_ELEMENT_FOR_CASE(Id::MkChapterTimeEnd);
RETURN_ELEMENT_FOR_CASE(Id::MkChapterFlagHidden);
RETURN_ELEMENT_FOR_CASE(Id::MkChapterDisplay);
RETURN_ELEMENT_FOR_CASE(Id::MkChapString);
RETURN_ELEMENT_FOR_CASE(Id::MkChapLanguage);
}
return std::make_unique<Element>(id, sizeLength, dataSize);
}
unsigned int EBML::Element::readId(File &file)
{
auto buffer = file.readBlock(1);
if(buffer.size() != 1) {
debug("Failed to read VINT size");
return 0;
}
const unsigned int numBytes = VINTSizeLength<4>(*buffer.begin());
if(!numBytes)
return 0;
if(numBytes > 1)
buffer.append(file.readBlock(numBytes - 1));
if(buffer.size() != numBytes) {
debug("Failed to read VINT data");
return 0;
}
return buffer.toUInt(true);
}
EBML::Element::Element(Id id, int sizeLength, offset_t dataSize):
id(id), sizeLength(sizeLength), dataSize(dataSize)
{
}
EBML::Element::Element(Id id, int sizeLength, offset_t dataSize, offset_t):
id(id), sizeLength(sizeLength), dataSize(dataSize)
{
}
EBML::Element::~Element() = default;
bool EBML::Element::read(File& file)
{
skipData(file);
return true;
}
void EBML::Element::skipData(File &file)
{
file.seek(dataSize, File::Position::Current);
}
EBML::Element::Id EBML::Element::getId() const
{
return id;
}
offset_t EBML::Element::headSize() const
{
return idSize(id) + sizeLength;
}
offset_t EBML::Element::getSize() const
{
return headSize() + dataSize;
}
int EBML::Element::getSizeLength() const
{
return sizeLength;
}
int64_t EBML::Element::getDataSize() const
{
return dataSize;
}
ByteVector EBML::Element::render()
{
ByteVector buffer = renderId();
buffer.append(renderVINT(0, 0));
return buffer;
}
ByteVector EBML::Element::renderId() const
{
const int numBytes = idSize(id);
static const auto byteOrder = Utils::systemByteOrder();
const auto uintId = static_cast<uint32_t>(id);
uint32_t data = byteOrder == Utils::LittleEndian ? Utils::byteSwap(uintId) : uintId;
return ByteVector(reinterpret_cast<char *>(&data) + (4 - numBytes), numBytes);
}

View File

@@ -0,0 +1,249 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLELEMENT_H
#define TAGLIB_EBMLELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include <cstdint>
#include <memory>
#include "taglib.h"
namespace TagLib
{
class File;
class ByteVector;
namespace EBML {
class Element
{
public:
enum class Id : unsigned int
{
EBMLHeader = 0x1A45DFA3,
DocType = 0x4282,
DocTypeVersion = 0x4287,
VoidElement = 0xEC,
MkSegment = 0x18538067,
MkTags = 0x1254C367,
MkTag = 0x7373,
MkTagTargets = 0x63C0,
MkTagTargetTypeValue = 0x68CA,
MkTagTrackUID = 0x63C5,
MkSimpleTag = 0x67C8,
MkTagName = 0x45A3,
MkTagLanguage = 0x447A,
MkTagBinary = 0x4485,
MkTagString = 0x4487,
MkTagsTagLanguage = 0x447A,
MkTagsLanguageDefault = 0x4484,
MkAttachments = 0x1941A469,
MkAttachedFile = 0x61A7,
MkAttachedFileDescription = 0x467E,
MkAttachedFileName = 0x466E,
MkAttachedFileMediaType = 0x4660,
MkAttachedFileData = 0x465C,
MkAttachedFileUID = 0x46AE,
MkSeekHead = 0x114D9B74,
MkSeek = 0x4DBB,
MkSeekID = 0x53AB,
MkSeekPosition = 0x53AC,
MkCluster = 0x1F43B675,
MkCodecState = 0xA4,
MkCues = 0x1C53BB6B,
MkCuePoint = 0xBB,
MkCueTime = 0xB3,
MkCueTrackPositions = 0xB7,
MkCueTrack = 0xF7,
MkCueClusterPosition = 0xF1,
MkCueRelativePosition = 0xF0,
MkCueDuration = 0xB2,
MkCueBlockNumber = 0x5378,
MkCueCodecState = 0xEA,
MkCueReference = 0xDB,
MkCueRefTime = 0x96,
MkInfo = 0x1549A966,
MkTimestampScale = 0x2AD7B1,
MkDuration = 0x4489,
MkTitle = 0x7BA9,
MkTracks = 0x1654AE6B,
MkTrackEntry = 0xAE,
MkCodecID = 0x86,
MkAudio = 0xE1,
MkSamplingFrequency = 0xB5,
MkBitDepth = 0x6264,
MkChannels = 0x9F,
MkChapters = 0x1043A770,
MkEditionEntry = 0x45B9,
MkEditionUID = 0x45BC,
MkEditionFlagDefault = 0x45DB,
MkEditionFlagOrdered = 0x45DD,
MkChapterAtom = 0xB6,
MkChapterUID = 0x73C4,
MkChapterTimeStart = 0x91,
MkChapterTimeEnd = 0x92,
MkChapterFlagHidden = 0x98,
MkChapterDisplay = 0x80,
MkChapString = 0x85,
MkChapLanguage = 0x437C,
};
Element(Id id, int sizeLength, offset_t dataSize);
Element(Id id, int sizeLength, offset_t dataSize, offset_t);
virtual ~Element();
virtual bool read(File &file);
void skipData(File &file);
Id getId() const;
offset_t headSize() const;
offset_t getSize() const;
int getSizeLength() const;
int64_t getDataSize() const;
ByteVector renderId() const;
virtual ByteVector render();
static std::unique_ptr<Element> factory(File &file);
static unsigned int readId(File &file);
protected:
Id id;
int sizeLength;
offset_t dataSize;
};
// Template specializations to ensure that elements for the different IDs
// are consistently created and cast. They provide a mapping between IDs
// and Element subclasses. The switch in factory() makes sure that the
// template for all IDs are instantiated, i.e. that every ID has its Element
// subclass mapped.
class MasterElement;
class UIntElement;
class BinaryElement;
class FloatElement;
class MkSegment;
class MkInfo;
class MkTracks;
class MkTags;
class MkAttachments;
class MkSeekHead;
class MkChapters;
class MkCues;
class VoidElement;
class UTF8StringElement;
class Latin1StringElement;
template <Element::Id ID>
struct GetElementTypeById;
template <> struct GetElementTypeById<Element::Id::EBMLHeader> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::DocType> { using type = Latin1StringElement; };
template <> struct GetElementTypeById<Element::Id::DocTypeVersion> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkSegment> { using type = MkSegment; };
template <> struct GetElementTypeById<Element::Id::MkInfo> { using type = MkInfo; };
template <> struct GetElementTypeById<Element::Id::MkTracks> { using type = MkTracks; };
template <> struct GetElementTypeById<Element::Id::MkTags> { using type = MkTags; };
template <> struct GetElementTypeById<Element::Id::MkAttachments> { using type = MkAttachments; };
template <> struct GetElementTypeById<Element::Id::MkTag> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkTagTargets> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkSimpleTag> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkAttachedFile> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkSeek> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkTrackEntry> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkAudio> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkCuePoint> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkCueTrackPositions> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkCueReference> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkCluster> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkCues> { using type = MkCues; };
template <> struct GetElementTypeById<Element::Id::MkTagName> { using type = UTF8StringElement; };
template <> struct GetElementTypeById<Element::Id::MkTagString> { using type = UTF8StringElement; };
template <> struct GetElementTypeById<Element::Id::MkAttachedFileName> { using type = UTF8StringElement; };
template <> struct GetElementTypeById<Element::Id::MkAttachedFileDescription> { using type = UTF8StringElement; };
template <> struct GetElementTypeById<Element::Id::MkTagLanguage> { using type = Latin1StringElement; };
template <> struct GetElementTypeById<Element::Id::MkAttachedFileMediaType> { using type = Latin1StringElement; };
template <> struct GetElementTypeById<Element::Id::MkCodecID> { using type = Latin1StringElement; };
template <> struct GetElementTypeById<Element::Id::MkTagTargetTypeValue> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkTagTrackUID> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkAttachedFileUID> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkSeekPosition> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkTimestampScale> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkBitDepth> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkChannels> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkCueTime> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkCueTrack> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkCueClusterPosition> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkCueRelativePosition> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkCueDuration> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkCueBlockNumber> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkCueCodecState> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkCueRefTime> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkTagsLanguageDefault> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkAttachedFileData> { using type = BinaryElement; };
template <> struct GetElementTypeById<Element::Id::MkSeekID> { using type = BinaryElement; };
template <> struct GetElementTypeById<Element::Id::MkTagBinary> { using type = BinaryElement; };
template <> struct GetElementTypeById<Element::Id::MkCodecState> { using type = BinaryElement; };
template <> struct GetElementTypeById<Element::Id::MkDuration> { using type = FloatElement; };
template <> struct GetElementTypeById<Element::Id::MkTitle> { using type = UTF8StringElement; };
template <> struct GetElementTypeById<Element::Id::MkSamplingFrequency> { using type = FloatElement; };
template <> struct GetElementTypeById<Element::Id::MkSeekHead> { using type = MkSeekHead; };
template <> struct GetElementTypeById<Element::Id::MkChapters> { using type = MkChapters; };
template <> struct GetElementTypeById<Element::Id::MkEditionEntry> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkEditionUID> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkEditionFlagDefault> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkEditionFlagOrdered> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkChapterAtom> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkChapterUID> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkChapterTimeStart> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkChapterTimeEnd> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkChapterFlagHidden> { using type = UIntElement; };
template <> struct GetElementTypeById<Element::Id::MkChapterDisplay> { using type = MasterElement; };
template <> struct GetElementTypeById<Element::Id::MkChapString> { using type = UTF8StringElement; };
template <> struct GetElementTypeById<Element::Id::MkChapLanguage> { using type = Latin1StringElement; };
template <> struct GetElementTypeById<Element::Id::VoidElement> { using type = VoidElement; };
template <Element::Id ID, typename T=typename GetElementTypeById<ID>::type>
const T *element_cast(const std::unique_ptr<Element> &ptr)
{
return static_cast<const T *>(ptr.get());
}
template <Element::Id ID, typename T=typename GetElementTypeById<ID>::type>
std::unique_ptr<T> element_cast(std::unique_ptr<Element> &&ptr)
{
return std::unique_ptr<T>(static_cast<T *>(ptr.release()));
}
template <Element::Id ID, typename T=typename GetElementTypeById<ID>::type>
std::unique_ptr<T> make_unique_element(Element::Id id, int sizeLength, offset_t dataSize, offset_t offset)
{
return std::make_unique<T>(id, sizeLength, dataSize, offset);
}
template <Element::Id ID, typename T=typename GetElementTypeById<ID>::type>
std::unique_ptr<T> make_unique_element()
{
return std::make_unique<T>(ID, 0, 0, 0);
}
}
}
#endif
#endif

View File

@@ -0,0 +1,109 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlfloatelement.h"
#include "ebmlutils.h"
#include "tbytevector.h"
#include "tfile.h"
#include "tdebug.h"
using namespace TagLib;
EBML::FloatElement::FloatElement(Id id, int sizeLength, offset_t dataSize):
Element(id, sizeLength, dataSize)
{
}
EBML::FloatElement::FloatElement(Id id, int sizeLength, offset_t dataSize, offset_t):
Element(id, sizeLength, dataSize)
{
}
EBML::FloatElement::FloatElement(Id id):
FloatElement(id, 0, 0)
{
}
EBML::FloatElement::FloatVariantType EBML::FloatElement::getValue() const
{ return value; }
double EBML::FloatElement::getValueAsDouble(double defaultValue) const
{
if(std::holds_alternative<double>(value)) {
// get_if() used instead of get() to support restricted compilers
return *std::get_if<double>(&value);
}
if(std::holds_alternative<float>(value)) {
// get_if() used instead of get() to support restricted compilers
return *std::get_if<float>(&value);
}
return defaultValue;
}
void EBML::FloatElement::setValue(FloatVariantType val)
{
value = val;
}
bool EBML::FloatElement::read(File &file)
{
const ByteVector buffer = file.readBlock(dataSize);
if(buffer.size() != dataSize) {
debug("Failed to read EBML Float element");
return false;
}
if(dataSize == 0) {
value = std::monostate();
}
else if(dataSize == 4) {
value = buffer.toFloat32BE(0);
}
else if(dataSize == 8) {
value = buffer.toFloat64BE(0);
}
else {
debug("Invalid size for EBML Float element");
return false;
}
return true;
}
ByteVector EBML::FloatElement::render()
{
ByteVector data;
if(std::holds_alternative<double>(value)) {
// get_if() used instead of get() to support restricted compilers
data = ByteVector::fromFloat64BE(*std::get_if<double>(&value));
}
else if(std::holds_alternative<float>(value)) {
// get_if() used instead of get() to support restricted compilers
data = ByteVector::fromFloat32BE(*std::get_if<float>(&value));
}
ByteVector buffer = renderId();
buffer.append(renderVINT(data.size(), 0));
buffer.append(data);
return buffer;
}

View File

@@ -0,0 +1,59 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLFLOATELEMENT_H
#define TAGLIB_EBMLFLOATELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include <variant>
#include "ebmlelement.h"
namespace TagLib {
class File;
namespace EBML {
class FloatElement : public Element
{
public:
using FloatVariantType = std::variant<std::monostate, float, double>;
FloatElement(Id id, int sizeLength, offset_t dataSize);
FloatElement(Id id, int sizeLength, offset_t dataSize, offset_t);
explicit FloatElement(Id id);
FloatVariantType getValue() const;
double getValueAsDouble(double defaultValue = 0.0) const;
void setValue(FloatVariantType val);
bool read(File &file) override;
ByteVector render() override;
private:
FloatVariantType value;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,127 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmasterelement.h"
#include "ebmlvoidelement.h"
#include "ebmlutils.h"
#include "tfile.h"
using namespace TagLib;
EBML::MasterElement::MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset):
Element(id, sizeLength, dataSize), offset(offset)
{
}
EBML::MasterElement::MasterElement(Id id):
Element(id, 0, 0), offset(0)
{
}
EBML::MasterElement::~MasterElement() = default;
offset_t EBML::MasterElement::getOffset() const
{
return offset;
}
void EBML::MasterElement::appendElement(std::unique_ptr<Element> &&element)
{
elements.push_back(std::move(element));
}
std::list<std::unique_ptr<EBML::Element>>::iterator EBML::MasterElement::begin()
{
return elements.begin();
}
std::list<std::unique_ptr<EBML::Element>>::iterator EBML::MasterElement::end()
{
return elements.end();
}
std::list<std::unique_ptr<EBML::Element>>::const_iterator EBML::MasterElement::begin() const
{
return elements.begin();
}
std::list<std::unique_ptr<EBML::Element>>::const_iterator EBML::MasterElement::end() const
{
return elements.end();
}
std::list<std::unique_ptr<EBML::Element>>::const_iterator EBML::MasterElement::cbegin() const
{
return elements.cbegin();
}
std::list<std::unique_ptr<EBML::Element>>::const_iterator EBML::MasterElement::cend() const
{
return elements.cend();
}
offset_t EBML::MasterElement::getPadding() const
{
return padding;
}
void EBML::MasterElement::setPadding(offset_t numBytes)
{
padding = numBytes;
}
offset_t EBML::MasterElement::getMinRenderSize() const
{
return minRenderSize;
}
void EBML::MasterElement::setMinRenderSize(offset_t minimumSize)
{
minRenderSize = minimumSize;
}
bool EBML::MasterElement::read(File &file)
{
const offset_t maxOffset = file.tell() + dataSize;
std::unique_ptr<Element> element;
while((element = findNextElement(file, maxOffset))) {
if(!element->read(file))
return false;
elements.push_back(std::move(element));
}
return file.tell() == maxOffset;
}
ByteVector EBML::MasterElement::render()
{
ByteVector buffer = renderId();
ByteVector data;
for(const auto &element : elements)
data.append(element->render());
dataSize = data.size();
buffer.append(renderVINT(dataSize, 0));
buffer.append(data);
if(minRenderSize) {
if(const auto bufferSize = buffer.size();
minRenderSize >= bufferSize + MIN_VOID_ELEMENT_SIZE)
buffer.append(VoidElement::renderSize(minRenderSize - bufferSize));
}
return buffer;
}

View File

@@ -0,0 +1,68 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMASTERELEMENT_H
#define TAGLIB_EBMLMASTERELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include <list>
#include "ebmlelement.h"
#include "taglib.h"
namespace TagLib
{
class ByteVector;
namespace EBML {
class MasterElement : public Element
{
public:
MasterElement(Id id, int sizeLength, offset_t dataSize, offset_t offset);
explicit MasterElement(Id id);
~MasterElement() override;
offset_t getOffset() const;
bool read(File &file) override;
ByteVector render() override;
void appendElement(std::unique_ptr<Element> &&element);
std::list<std::unique_ptr<Element>>::iterator begin();
std::list<std::unique_ptr<Element>>::iterator end();
std::list<std::unique_ptr<Element>>::const_iterator begin() const;
std::list<std::unique_ptr<Element>>::const_iterator end() const;
std::list<std::unique_ptr<Element>>::const_iterator cbegin() const;
std::list<std::unique_ptr<Element>>::const_iterator cend() const;
offset_t getPadding() const;
void setPadding(offset_t numBytes);
offset_t getMinRenderSize() const;
void setMinRenderSize(offset_t minimumSize);
protected:
offset_t offset;
offset_t padding = 0;
offset_t minRenderSize = 0;
std::list<std::unique_ptr<Element>> elements;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,81 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmkattachments.h"
#include "ebmlstringelement.h"
#include "ebmluintelement.h"
#include "ebmlbinaryelement.h"
#include "matroskaattachments.h"
#include "matroskaattachedfile.h"
using namespace TagLib;
EBML::MkAttachments::MkAttachments(int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkAttachments, sizeLength, dataSize, offset)
{
}
EBML::MkAttachments::MkAttachments(Id, int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkAttachments, sizeLength, dataSize, offset)
{
}
EBML::MkAttachments::MkAttachments():
MasterElement(Id::MkAttachments, 0, 0, 0)
{
}
std::unique_ptr<Matroska::Attachments> EBML::MkAttachments::parse() const
{
auto attachments = std::make_unique<Matroska::Attachments>();
attachments->setOffset(offset);
attachments->setSize(getSize());
for(const auto &element : elements) {
if(element->getId() != Id::MkAttachedFile)
continue;
const String *filename = nullptr;
const String *description = nullptr;
const String *mediaType = nullptr;
const ByteVector *data = nullptr;
Matroska::AttachedFile::UID uid = 0;
const auto attachedFile = element_cast<Id::MkAttachedFile>(element);
for(const auto &attachedFileChild : *attachedFile) {
if(const Id id = attachedFileChild->getId(); id == Id::MkAttachedFileName)
filename = &element_cast<Id::MkAttachedFileName>(attachedFileChild)->getValue();
else if(id == Id::MkAttachedFileData)
data = &element_cast<Id::MkAttachedFileData>(attachedFileChild)->getValue();
else if(id == Id::MkAttachedFileDescription)
description = &element_cast<Id::MkAttachedFileDescription>(attachedFileChild)->getValue();
else if(id == Id::MkAttachedFileMediaType)
mediaType = &element_cast<Id::MkAttachedFileMediaType>(attachedFileChild)->getValue();
else if(id == Id::MkAttachedFileUID)
uid = element_cast<Id::MkAttachedFileUID>(attachedFileChild)->getValue();
}
if(!(filename && data))
continue;
attachments->addAttachedFile(Matroska::AttachedFile(
*data, *filename, mediaType ? *mediaType : String(),
uid, description ? *description : String()));
}
return attachments;
}

View File

@@ -0,0 +1,47 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMKATTACHMENTS_H
#define TAGLIB_EBMLMKATTACHMENTS_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlmasterelement.h"
#include "taglib.h"
namespace TagLib {
namespace Matroska {
class Attachments;
}
namespace EBML {
class MkAttachments : public MasterElement
{
public:
MkAttachments(int sizeLength, offset_t dataSize, offset_t offset);
MkAttachments(Id, int sizeLength, offset_t dataSize, offset_t offset);
MkAttachments();
std::unique_ptr<Matroska::Attachments> parse() const;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,114 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmkchapters.h"
#include "ebmlstringelement.h"
#include "ebmluintelement.h"
#include "matroskachapters.h"
#include "matroskachapteredition.h"
using namespace TagLib;
EBML::MkChapters::MkChapters(int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkChapters, sizeLength, dataSize, offset)
{
}
EBML::MkChapters::MkChapters(Id, int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkChapters, sizeLength, dataSize, offset)
{
}
EBML::MkChapters::MkChapters():
MasterElement(Id::MkChapters, 0, 0, 0)
{
}
std::unique_ptr<Matroska::Chapters> EBML::MkChapters::parse() const
{
auto chapters = std::make_unique<Matroska::Chapters>();
chapters->setOffset(offset);
chapters->setSize(getSize());
for(const auto &element : elements) {
if(element->getId() != Id::MkEditionEntry)
continue;
List<Matroska::Chapter> editionChapters;
Matroska::ChapterEdition::UID editionUid = 0;
bool editionIsDefault = false;
bool editionIsOrdered = false;
const auto edition = element_cast<Id::MkEditionEntry>(element);
for(const auto &editionChild : *edition) {
if(const Id id = editionChild->getId(); id == Id::MkEditionUID)
editionUid = element_cast<Id::MkEditionUID>(editionChild)->getValue();
else if(id == Id::MkEditionFlagDefault)
editionIsDefault = element_cast<Id::MkEditionFlagDefault>(editionChild)->getValue() != 0;
else if(id == Id::MkEditionFlagOrdered)
editionIsOrdered = element_cast<Id::MkEditionFlagOrdered>(editionChild)->getValue() != 0;
else if(id == Id::MkChapterAtom) {
Matroska::Chapter::UID chapterUid = 0;
Matroska::Chapter::Time chapterTimeStart = 0;
Matroska::Chapter::Time chapterTimeEnd = 0;
List<Matroska::Chapter::Display> chapterDisplays;
bool chapterHidden = false;
const auto chapterAtom = element_cast<Id::MkChapterAtom>(editionChild);
for(const auto &chapterChild : *chapterAtom) {
if(const Id cid = chapterChild->getId(); cid == Id::MkChapterUID)
chapterUid = element_cast<Id::MkChapterUID>(chapterChild)->getValue();
else if(cid == Id::MkChapterTimeStart)
chapterTimeStart = element_cast<Id::MkChapterTimeStart>(chapterChild)->getValue();
else if(cid == Id::MkChapterTimeEnd)
chapterTimeEnd = element_cast<Id::MkChapterTimeEnd>(chapterChild)->getValue();
else if(cid == Id::MkChapterFlagHidden)
chapterHidden = element_cast<Id::MkChapterFlagHidden>(chapterChild)->getValue() != 0;
else if(cid == Id::MkChapterDisplay) {
const auto display = element_cast<Id::MkChapterDisplay>(chapterChild);
String displayString;
String displayLanguage;
for(const auto &displayChild : *display) {
if(const Id did = displayChild->getId(); did == Id::MkChapString)
displayString = element_cast<Id::MkChapString>(displayChild)->getValue();
else if(did == Id::MkChapLanguage)
displayLanguage = element_cast<Id::MkChapLanguage>(displayChild)->getValue();
}
if(!displayString.isEmpty()) {
chapterDisplays.append(Matroska::Chapter::Display(displayString, displayLanguage));
}
}
}
if(chapterUid) {
editionChapters.append(Matroska::Chapter(
chapterTimeStart, chapterTimeEnd, chapterDisplays, chapterUid, chapterHidden));
}
}
}
if(!editionChapters.isEmpty()) {
chapters->addChapterEdition(Matroska::ChapterEdition(
editionChapters, editionIsDefault, editionIsOrdered, editionUid));
}
}
return chapters;
}

View File

@@ -0,0 +1,52 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMKCHAPTERS_H
#define TAGLIB_EBMLMKCHAPTERS_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlmasterelement.h"
#include "taglib.h"
namespace TagLib {
namespace Matroska {
class Chapters;
}
namespace EBML {
class MkChapters : public MasterElement
{
public:
MkChapters(int sizeLength, offset_t dataSize, offset_t offset);
MkChapters(Id, int sizeLength, offset_t dataSize, offset_t offset);
MkChapters();
std::unique_ptr<Matroska::Chapters> parse() const;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,89 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmkcues.h"
#include "ebmluintelement.h"
#include "matroskacues.h"
using namespace TagLib;
EBML::MkCues::MkCues(int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkCues, sizeLength, dataSize, offset)
{
}
EBML::MkCues::MkCues(Id, int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkCues, sizeLength, dataSize, offset)
{
}
EBML::MkCues::MkCues():
MasterElement(Id::MkCues, 0, 0, 0)
{
}
std::unique_ptr<Matroska::Cues> EBML::MkCues::parse(offset_t segmentDataOffset) const
{
auto cues = std::make_unique<Matroska::Cues>(segmentDataOffset);
cues->setOffset(offset);
cues->setSize(getSize());
cues->setID(static_cast<Matroska::Element::ID>(id));
for(const auto &cuesChild : elements) {
if(cuesChild->getId() != Id::MkCuePoint)
continue;
const auto cuePointElement = element_cast<Id::MkCuePoint>(cuesChild);
auto cuePoint = std::make_unique<Matroska::CuePoint>();
for(const auto &cuePointChild : *cuePointElement) {
if(const Id id = cuePointChild->getId(); id == Id::MkCueTime)
cuePoint->setTime(element_cast<Id::MkCueTime>(cuePointChild)->getValue());
else if(id == Id::MkCueTrackPositions) {
auto cueTrack = std::make_unique<Matroska::CueTrack>();
const auto cueTrackElement = element_cast<Id::MkCueTrackPositions>(cuePointChild);
for(const auto &cueTrackChild : *cueTrackElement) {
if(const Id trackId = cueTrackChild->getId(); trackId == Id::MkCueTrack)
cueTrack->setTrackNumber(element_cast<Id::MkCueTrack>(cueTrackChild)->getValue());
else if(trackId == Id::MkCueClusterPosition)
cueTrack->setClusterPosition(element_cast<Id::MkCueClusterPosition>(cueTrackChild)->getValue());
else if(trackId == Id::MkCueRelativePosition)
cueTrack->setRelativePosition(element_cast<Id::MkCueRelativePosition>(cueTrackChild)->getValue());
else if(trackId == Id::MkCueDuration)
cueTrack->setDuration(element_cast<Id::MkCueDuration>(cueTrackChild)->getValue());
else if(trackId == Id::MkCueBlockNumber)
cueTrack->setBlockNumber(element_cast<Id::MkCueBlockNumber>(cueTrackChild)->getValue());
else if(trackId == Id::MkCueCodecState)
cueTrack->setCodecState(element_cast<Id::MkCueCodecState>(cueTrackChild)->getValue());
else if(trackId == Id::MkCueReference) {
const auto cueReference = element_cast<Id::MkCueReference>(cueTrackChild);
for(const auto &cueReferenceChild : *cueReference) {
if(cueReferenceChild->getId() != Id::MkCueRefTime)
continue;
cueTrack->addReferenceTime(element_cast<Id::MkCueRefTime>(cueReferenceChild)->getValue());
}
}
}
cuePoint->addCueTrack(std::move(cueTrack));
}
}
cues->addCuePoint(std::move(cuePoint));
}
return cues;
}

View File

@@ -0,0 +1,47 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMKCUES_H
#define TAGLIB_EBMLMKCUES_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlmasterelement.h"
#include "taglib.h"
namespace TagLib {
namespace Matroska {
class Cues;
}
namespace EBML {
class MkCues : public MasterElement
{
public:
MkCues(int sizeLength, offset_t dataSize, offset_t offset);
MkCues(Id, int sizeLength, offset_t dataSize, offset_t offset);
MkCues();
std::unique_ptr<Matroska::Cues> parse(offset_t segmentDataOffset) const;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,72 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmkinfo.h"
#include "ebmlstringelement.h"
#include "ebmluintelement.h"
#include "ebmlfloatelement.h"
#include "matroskaproperties.h"
using namespace TagLib;
EBML::MkInfo::MkInfo(int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkInfo, sizeLength, dataSize, offset)
{
}
EBML::MkInfo::MkInfo(Id, int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkInfo, sizeLength, dataSize, offset)
{
}
EBML::MkInfo::MkInfo():
MasterElement(Id::MkInfo, 0, 0, 0)
{
}
void EBML::MkInfo::parse(Matroska::Properties *properties) const
{
if(!properties)
return;
unsigned long long timestampScale = 1000000;
double duration = 0.0;
String title;
for(const auto &element : elements) {
if(const Id id = element->getId(); id == Id::MkTimestampScale) {
timestampScale = element_cast<Id::MkTimestampScale>(element)->getValue();
}
else if(id == Id::MkDuration) {
duration = element_cast<Id::MkDuration>(element)->getValueAsDouble();
}
else if(id == Id::MkTitle) {
title = element_cast<Id::MkTitle>(element)->getValue();
}
}
properties->setLengthInMilliseconds(
static_cast<int>(duration * static_cast<double>(timestampScale) / 1000000.0));
properties->setTitle(title);
}

View File

@@ -0,0 +1,52 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMKINFO_H
#define TAGLIB_EBMLMKINFO_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlmasterelement.h"
#include "taglib.h"
namespace TagLib {
namespace Matroska {
class Properties;
}
namespace EBML {
class MkInfo : public MasterElement
{
public:
MkInfo(int sizeLength, offset_t dataSize, offset_t offset);
MkInfo(Id, int sizeLength, offset_t dataSize, offset_t offset);
MkInfo();
void parse(Matroska::Properties * properties) const;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,73 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmkseekhead.h"
#include "matroskaseekhead.h"
#include "ebmluintelement.h"
#include "ebmlbinaryelement.h"
using namespace TagLib;
EBML::MkSeekHead::MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset)
{
}
EBML::MkSeekHead::MkSeekHead(Id, int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkSeekHead, sizeLength, dataSize, offset)
{
}
EBML::MkSeekHead::MkSeekHead():
MasterElement(Id::MkSeekHead, 0, 0, 0)
{
}
std::unique_ptr<Matroska::SeekHead> EBML::MkSeekHead::parse(offset_t segmentDataOffset) const
{
auto seekHead = std::make_unique<Matroska::SeekHead>(segmentDataOffset);
seekHead->setOffset(offset);
seekHead->setSize(getSize() + padding);
for(const auto &element : elements) {
if(element->getId() != Id::MkSeek)
continue;
const auto seekElement = element_cast<Id::MkSeek>(element);
Matroska::Element::ID entryId = 0;
offset_t offset = 0;
for(const auto &seekElementChild : *seekElement) {
if(const Id id = seekElementChild->getId(); id == Id::MkSeekID && !entryId) {
if(auto data = element_cast<Id::MkSeekID>(seekElementChild)->getValue();
data.size() == 4)
entryId = data.toUInt(true);
}
else if(id == Id::MkSeekPosition && !offset)
offset = element_cast<Id::MkSeekPosition>(seekElementChild)->getValue();
}
if(entryId && offset)
seekHead->addEntry(entryId, offset);
else {
seekHead.reset();
return nullptr;
}
}
return seekHead;
}

View File

@@ -0,0 +1,47 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMKSEEKHEAD_H
#define TAGLIB_EBMLMKSEEKHEAD_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlmasterelement.h"
#include "taglib.h"
namespace TagLib {
namespace Matroska {
class SeekHead;
}
namespace EBML {
class MkSeekHead : public MasterElement
{
public:
MkSeekHead(int sizeLength, offset_t dataSize, offset_t offset);
MkSeekHead(Id, int sizeLength, offset_t dataSize, offset_t offset);
MkSeekHead();
std::unique_ptr<Matroska::SeekHead> parse(offset_t segmentDataOffset) const;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,148 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmksegment.h"
#include "ebmlutils.h"
#include "matroskafile.h"
#include "matroskatag.h"
#include "matroskaattachments.h"
#include "matroskachapters.h"
#include "matroskacues.h"
#include "matroskaseekhead.h"
#include "matroskasegment.h"
using namespace TagLib;
EBML::MkSegment::MkSegment(int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkSegment, sizeLength, dataSize, offset)
{
}
EBML::MkSegment::MkSegment(Id, int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkSegment, sizeLength, dataSize, offset)
{
}
EBML::MkSegment::~MkSegment() = default;
offset_t EBML::MkSegment::segmentDataOffset() const
{
return offset + idSize(id) + sizeLength;
}
bool EBML::MkSegment::read(File &file)
{
const offset_t maxOffset = file.tell() + dataSize;
std::unique_ptr<Element> element;
int i = 0;
int seekHeadIndex = -1;
while((element = findNextElement(file, maxOffset))) {
if(const Id id = element->getId(); id == Id::MkSeekHead) {
seekHeadIndex = i;
seekHead = element_cast<Id::MkSeekHead>(std::move(element));
if(!seekHead->read(file))
return false;
}
else if(id == Id::MkCues) {
cues = element_cast<Id::MkCues>(std::move(element));
if(!cues->read(file))
return false;
}
else if(id == Id::MkInfo) {
info = element_cast<Id::MkInfo>(std::move(element));
if(!info->read(file))
return false;
}
else if(id == Id::MkTracks) {
tracks = element_cast<Id::MkTracks>(std::move(element));
if(!tracks->read(file))
return false;
}
else if(id == Id::MkTags) {
tags = element_cast<Id::MkTags>(std::move(element));
if(!tags->read(file))
return false;
}
else if(id == Id::MkAttachments) {
attachments = element_cast<Id::MkAttachments>(std::move(element));
if(!attachments->read(file))
return false;
}
else if(id == Id::MkChapters) {
chapters = element_cast<Id::MkChapters>(std::move(element));
if(!chapters->read(file))
return false;
}
else {
if(id == Id::VoidElement
&& seekHead
&& seekHeadIndex == i - 1)
seekHead->setPadding(element->getSize());
element->skipData(file);
}
i++;
}
return true;
}
std::unique_ptr<Matroska::Tag> EBML::MkSegment::parseTag() const
{
return tags ? tags->parse() : nullptr;
}
std::unique_ptr<Matroska::Attachments> EBML::MkSegment::parseAttachments() const
{
return attachments ? attachments->parse() : nullptr;
}
std::unique_ptr<Matroska::Chapters> EBML::MkSegment::parseChapters() const
{
return chapters ? chapters->parse() : nullptr;
}
std::unique_ptr<Matroska::SeekHead> EBML::MkSegment::parseSeekHead() const
{
return seekHead ? seekHead->parse(segmentDataOffset()) : nullptr;
}
std::unique_ptr<Matroska::Cues> EBML::MkSegment::parseCues() const
{
return cues ? cues->parse(segmentDataOffset()) : nullptr;
}
std::unique_ptr<Matroska::Segment> EBML::MkSegment::parseSegment() const
{
return std::make_unique<Matroska::Segment>(sizeLength, dataSize, offset + idSize(id));
}
void EBML::MkSegment::parseInfo(Matroska::Properties *properties) const
{
if(info) {
info->parse(properties);
}
}
void EBML::MkSegment::parseTracks(Matroska::Properties *properties) const
{
if(tracks) {
tracks->parse(properties);
}
}

View File

@@ -0,0 +1,76 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMKSEGMENT_H
#define TAGLIB_EBMLMKSEGMENT_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlmasterelement.h"
#include "ebmlmktags.h"
#include "ebmlmkattachments.h"
#include "ebmlmkchapters.h"
#include "ebmlmkseekhead.h"
#include "ebmlmkcues.h"
#include "ebmlmkinfo.h"
#include "ebmlmktracks.h"
#include "taglib.h"
namespace TagLib {
namespace Matroska {
class Tag;
class Attachments;
class Chapters;
class SeekHead;
class Segment;
}
namespace EBML {
class MkSegment : public MasterElement
{
public:
MkSegment(int sizeLength, offset_t dataSize, offset_t offset);
MkSegment(Id, int sizeLength, offset_t dataSize, offset_t offset);
~MkSegment() override;
offset_t segmentDataOffset() const;
bool read(File &file) override;
std::unique_ptr<Matroska::Tag> parseTag() const;
std::unique_ptr<Matroska::Attachments> parseAttachments() const;
std::unique_ptr<Matroska::Chapters> parseChapters() const;
std::unique_ptr<Matroska::SeekHead> parseSeekHead() const;
std::unique_ptr<Matroska::Cues> parseCues() const;
std::unique_ptr<Matroska::Segment> parseSegment() const;
void parseInfo(Matroska::Properties *properties) const;
void parseTracks(Matroska::Properties *properties) const;
private:
std::unique_ptr<MkTags> tags;
std::unique_ptr<MkAttachments> attachments;
std::unique_ptr<MkChapters> chapters;
std::unique_ptr<MkSeekHead> seekHead;
std::unique_ptr<MkCues> cues;
std::unique_ptr<MkInfo> info;
std::unique_ptr<MkTracks> tracks;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,119 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmktags.h"
#include "ebmlbinaryelement.h"
#include "ebmluintelement.h"
#include "ebmlstringelement.h"
#include "matroskatag.h"
#include "matroskasimpletag.h"
#include "tlist.h"
using namespace TagLib;
EBML::MkTags::MkTags(int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkTags, sizeLength, dataSize, offset)
{
}
EBML::MkTags::MkTags(Id, int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkTags, sizeLength, dataSize, offset)
{
}
EBML::MkTags::MkTags():
MasterElement(Id::MkTags, 0, 0, 0)
{
}
std::unique_ptr<Matroska::Tag> EBML::MkTags::parse() const
{
auto mTag = std::make_unique<Matroska::Tag>();
mTag->setOffset(offset);
mTag->setSize(getSize());
mTag->setID(static_cast<Matroska::Element::ID>(id));
// Loop through each <Tag> element
for(const auto &tagsChild : elements) {
if(tagsChild->getId() != Id::MkTag)
continue;
const auto tag = element_cast<Id::MkTag>(tagsChild);
List<const MasterElement *> simpleTags;
const MasterElement *targets = nullptr;
// Identify the <Targets> element and the <SimpleTag> elements
for(const auto &tagChild : *tag) {
if(const Id tagChildId = tagChild->getId(); !targets && tagChildId == Id::MkTagTargets)
targets = element_cast<Id::MkTagTargets>(tagChild);
else if(tagChildId == Id::MkSimpleTag)
simpleTags.append(element_cast<Id::MkSimpleTag>(tagChild));
}
// Parse the <Targets> element
Matroska::SimpleTag::TargetTypeValue targetTypeValue = Matroska::SimpleTag::TargetTypeValue::None;
unsigned long long trackUid = 0;
if(targets) {
for(const auto &targetsChild : *targets) {
if(const Id id = targetsChild->getId(); id == Id::MkTagTargetTypeValue
&& targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None) {
targetTypeValue = static_cast<Matroska::SimpleTag::TargetTypeValue>(
element_cast<Id::MkTagTargetTypeValue>(targetsChild)->getValue()
);
}
else if(id == Id::MkTagTrackUID) {
trackUid = element_cast<Id::MkTagTrackUID>(targetsChild)->getValue();
}
}
}
// Parse each <SimpleTag>
for(const auto simpleTag : simpleTags) {
const String *tagValueString = nullptr;
const ByteVector *tagValueBinary = nullptr;
String tagName;
String language;
bool defaultLanguageFlag = true;
for(const auto &simpleTagChild : *simpleTag) {
if(const Id id = simpleTagChild->getId(); id == Id::MkTagName && tagName.isEmpty())
tagName = element_cast<Id::MkTagName>(simpleTagChild)->getValue();
else if(id == Id::MkTagString && !tagValueString)
tagValueString = &element_cast<Id::MkTagString>(simpleTagChild)->getValue();
else if(id == Id::MkTagBinary && !tagValueBinary)
tagValueBinary = &element_cast<Id::MkTagBinary>(simpleTagChild)->getValue();
else if(id == Id::MkTagsTagLanguage && language.isEmpty())
language = element_cast<Id::MkTagsTagLanguage>(simpleTagChild)->getValue();
else if(id == Id::MkTagsLanguageDefault)
defaultLanguageFlag = element_cast<Id::MkTagsLanguageDefault>(simpleTagChild)->getValue() ? true : false;
}
if(tagName.isEmpty() || (tagValueString && tagValueBinary) || (!tagValueString && !tagValueBinary))
continue;
mTag->addSimpleTag(tagValueString
? Matroska::SimpleTag(tagName, *tagValueString,
targetTypeValue, language, defaultLanguageFlag,
trackUid)
: Matroska::SimpleTag(tagName, *tagValueBinary,
targetTypeValue, language, defaultLanguageFlag,
trackUid));
}
}
return mTag;
}

View File

@@ -0,0 +1,47 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMKTAGS_H
#define TAGLIB_EBMLMKTAGS_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlmasterelement.h"
#include "taglib.h"
namespace TagLib {
namespace Matroska {
class Tag;
}
namespace EBML {
class MkTags : public MasterElement
{
public:
MkTags(int sizeLength, offset_t dataSize, offset_t offset);
MkTags(Id, int sizeLength, offset_t dataSize, offset_t offset);
MkTags();
std::unique_ptr<Matroska::Tag> parse() const;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,86 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlmktracks.h"
#include "ebmlstringelement.h"
#include "ebmluintelement.h"
#include "ebmlfloatelement.h"
#include "matroskaproperties.h"
using namespace TagLib;
EBML::MkTracks::MkTracks(int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkTracks, sizeLength, dataSize, offset)
{
}
EBML::MkTracks::MkTracks(Id, int sizeLength, offset_t dataSize, offset_t offset):
MasterElement(Id::MkTracks, sizeLength, dataSize, offset)
{
}
EBML::MkTracks::MkTracks():
MasterElement(Id::MkTracks, 0, 0, 0)
{
}
void EBML::MkTracks::parse(Matroska::Properties *properties) const
{
if(!properties)
return;
for(const auto &element : elements) {
if(element->getId() != Id::MkTrackEntry)
continue;
String codecId;
double samplingFrequency = 0.0;
unsigned long long bitDepth = 0;
unsigned long long channels = 0;
const auto trackEntry = element_cast<Id::MkTrackEntry>(element);
for(const auto &trackEntryChild : *trackEntry) {
if(const Id trackEntryChildId = trackEntryChild->getId(); trackEntryChildId == Id::MkCodecID)
codecId = element_cast<Id::MkCodecID>(trackEntryChild)->getValue();
else if(trackEntryChildId == Id::MkAudio) {
const auto audio = element_cast<Id::MkAudio>(trackEntryChild);
for(const auto &audioChild : *audio) {
if(const Id audioChildId = audioChild->getId(); audioChildId == Id::MkSamplingFrequency)
samplingFrequency = element_cast<Id::MkSamplingFrequency>(audioChild)->getValueAsDouble();
else if(audioChildId == Id::MkBitDepth)
bitDepth = element_cast<Id::MkBitDepth>(audioChild)->getValue();
else if(audioChildId == Id::MkChannels)
channels = element_cast<Id::MkChannels>(audioChild)->getValue();
}
}
}
if(bitDepth || channels) {
properties->setSampleRate(static_cast<int>(samplingFrequency));
properties->setBitsPerSample(static_cast<int>(bitDepth));
properties->setChannels(static_cast<int>(channels));
properties->setCodecName(codecId);
return;
}
}
}

View File

@@ -0,0 +1,52 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLMKTRACKS_H
#define TAGLIB_EBMLMKTRACKS_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlmasterelement.h"
#include "taglib.h"
namespace TagLib {
namespace Matroska {
class Properties;
}
namespace EBML {
class MkTracks : public MasterElement
{
public:
MkTracks(int sizeLength, offset_t dataSize, offset_t offset);
MkTracks(Id, int sizeLength, offset_t dataSize, offset_t offset);
MkTracks();
void parse(Matroska::Properties *properties) const;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,100 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlstringelement.h"
#include <string>
#include "tfile.h"
#include "tbytevector.h"
#include "tdebug.h"
#include "ebmlutils.h"
using namespace TagLib;
EBML::StringElement::StringElement(
String::Type stringEncoding, Id id, int sizeLength, offset_t dataSize):
Element(id, sizeLength, dataSize), encoding(stringEncoding)
{
}
const String& EBML::StringElement::getValue() const
{
return value;
}
void EBML::StringElement::setValue(const String& val)
{
value = val;
}
bool EBML::StringElement::read(File &file)
{
ByteVector buffer = file.readBlock(dataSize);
if(buffer.size() != dataSize) {
debug("Failed to read string");
return false;
}
// The EBML strings aren't supposed to be null-terminated,
// but we'll check for it and strip the null terminator if found
if(const int nullByte = buffer.find('\0'); nullByte >= 0)
buffer = ByteVector(buffer.data(), nullByte);
value = String(buffer, encoding);
return true;
}
ByteVector EBML::StringElement::render()
{
ByteVector buffer = renderId();
const std::string string = value.to8Bit(encoding == String::UTF8);
dataSize = string.size();
buffer.append(renderVINT(dataSize, 0));
buffer.append(ByteVector(string.data(), static_cast<unsigned int>(dataSize)));
return buffer;
}
EBML::UTF8StringElement::UTF8StringElement(Id id, int sizeLength, offset_t dataSize):
StringElement(String::UTF8, id, sizeLength, dataSize)
{
}
EBML::UTF8StringElement::UTF8StringElement(Id id, int sizeLength, offset_t dataSize, offset_t):
UTF8StringElement(id, sizeLength, dataSize)
{
}
EBML::UTF8StringElement::UTF8StringElement(Id id):
UTF8StringElement(id, 0, 0)
{
}
EBML::Latin1StringElement::Latin1StringElement(Id id, int sizeLength, offset_t dataSize):
StringElement(String::Latin1, id, sizeLength, dataSize)
{
}
EBML::Latin1StringElement::Latin1StringElement(Id id, int sizeLength, offset_t dataSize, offset_t):
Latin1StringElement(id, sizeLength, dataSize)
{
}
EBML::Latin1StringElement::Latin1StringElement(Id id):
Latin1StringElement(id, 0, 0)
{
}

View File

@@ -0,0 +1,65 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLSTRINGELEMENT_H
#define TAGLIB_EBMLSTRINGELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlelement.h"
#include "tstring.h"
namespace TagLib {
class File;
class ByteVector;
namespace EBML {
class StringElement : public Element
{
public:
StringElement(String::Type stringEncoding, Id id, int sizeLength, offset_t dataSize);
const String &getValue() const;
void setValue(const String &val);
bool read(File &file) override;
ByteVector render() override;
private:
String value;
String::Type encoding;
};
class UTF8StringElement : public StringElement {
public:
UTF8StringElement(Id id, int sizeLength, offset_t dataSize);
UTF8StringElement(Id id, int sizeLength, offset_t dataSize, offset_t);
explicit UTF8StringElement(Id id);
};
class Latin1StringElement : public StringElement {
public:
Latin1StringElement(Id id, int sizeLength, offset_t dataSize);
Latin1StringElement(Id id, int sizeLength, offset_t dataSize, offset_t);
explicit Latin1StringElement(Id id);
};
}
}
#endif
#endif

View File

@@ -0,0 +1,95 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmluintelement.h"
#include "ebmlutils.h"
#include "tbytevector.h"
#include "tfile.h"
#include "tutils.h"
#include "tdebug.h"
using namespace TagLib;
EBML::UIntElement::UIntElement(Id id, int sizeLength, offset_t dataSize):
Element(id, sizeLength, dataSize)
{
}
EBML::UIntElement::UIntElement(Id id, int sizeLength, offset_t dataSize, offset_t):
Element(id, sizeLength, dataSize)
{
}
EBML::UIntElement::UIntElement(Id id):
UIntElement(id, 0, 0)
{
}
unsigned long long EBML::UIntElement::getValue() const
{
return value;
}
void EBML::UIntElement::setValue(unsigned long long val)
{
value = val;
}
bool EBML::UIntElement::read(File &file)
{
const ByteVector buffer = file.readBlock(dataSize);
if(buffer.size() != dataSize) {
debug("Failed to read EBML Uint element");
return false;
}
value = buffer.toULongLong(true);
return true;
}
ByteVector EBML::UIntElement::render()
{
int dataSize = 0;
if(value <= 0xFFull)
dataSize = 1;
else if(value <= 0xFFFFull)
dataSize = 2;
else if(value <= 0xFFFFFFull)
dataSize = 3;
else if(value <= 0xFFFFFFFFull)
dataSize = 4;
else if(value <= 0xFFFFFFFFFFull)
dataSize = 5;
else if(value <= 0xFFFFFFFFFFFFull)
dataSize = 6;
else if(value <= 0xFFFFFFFFFFFFFFull)
dataSize = 7;
else if(value <= 0xFFFFFFFFFFFFFFFFull)
dataSize = 8;
ByteVector buffer = renderId();
buffer.append(renderVINT(dataSize, 0));
uint64_t val = value;
static const auto byteOrder = Utils::systemByteOrder();
if(byteOrder == Utils::LittleEndian)
val = Utils::byteSwap(val);
buffer.append(ByteVector(reinterpret_cast<char *>(&val) + (sizeof(val) - dataSize), dataSize));
return buffer;
}

View File

@@ -0,0 +1,50 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLUINTELEMENT_H
#define TAGLIB_EBMLUINTELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlelement.h"
namespace TagLib {
class File;
namespace EBML {
class UIntElement : public Element
{
public:
UIntElement(Id id, int sizeLength, offset_t dataSize);
UIntElement(Id id, int sizeLength, offset_t dataSize, offset_t);
explicit UIntElement(Id id);
unsigned long long getValue() const;
void setValue(unsigned long long val);
bool read(File &file) override;
ByteVector render() override;
private:
unsigned long long value = 0;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,122 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlutils.h"
#include <random>
#include "tbytevector.h"
#include "matroskafile.h"
#include "tutils.h"
#include "tdebug.h"
using namespace TagLib;
std::unique_ptr<EBML::Element> EBML::findElement(File &file, Element::Id id, offset_t maxOffset)
{
std::unique_ptr<Element> element;
while(file.tell() < maxOffset) {
element = Element::factory(file);
if(!element || element->getId() == id)
return element;
element->skipData(file);
element.reset();
}
return element;
}
std::unique_ptr<EBML::Element> EBML::findNextElement(File &file, offset_t maxOffset)
{
return file.tell() < maxOffset ? Element::factory(file) : nullptr;
}
template <int maxSizeLength>
unsigned int EBML::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;
}
namespace TagLib::EBML {
template unsigned int VINTSizeLength<4>(uint8_t firstByte);
template unsigned int VINTSizeLength<8>(uint8_t firstByte);
}
std::pair<unsigned int, uint64_t> EBML::readVINT(File &file)
{
auto buffer = file.readBlock(1);
if(buffer.size() != 1) {
debug("Failed to read VINT size");
return {0, 0};
}
unsigned int numBytes = VINTSizeLength<8>(*buffer.begin());
if(!numBytes)
return {0, 0};
if(numBytes > 1)
buffer.append(file.readBlock(numBytes - 1));
const int bitsToShift = static_cast<int>(sizeof(uint64_t) * 8) - 7 * numBytes;
const uint64_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift;
return { numBytes, buffer.toULongLong(true) & mask };
}
std::pair<unsigned int, uint64_t> EBML::parseVINT(const ByteVector &buffer)
{
if(buffer.isEmpty())
return {0, 0};
unsigned int numBytes = VINTSizeLength<8>(*buffer.begin());
if(!numBytes)
return {0, 0};
const int bitsToShift = static_cast<int>(sizeof(uint64_t) * 8) - 7 * numBytes;
const uint64_t mask = 0xFFFFFFFFFFFFFFFF >> bitsToShift;
return { numBytes, buffer.toULongLong(true) & mask };
}
ByteVector EBML::renderVINT(uint64_t number, int minSizeLength)
{
const int numBytes = std::max(minSizeLength, minSize(number));
number |= 1ULL << (numBytes * 7);
static const auto byteOrder = Utils::systemByteOrder();
if(byteOrder == Utils::LittleEndian)
number = Utils::byteSwap(number);
return ByteVector(reinterpret_cast<char *>(&number) + (sizeof(number) - numBytes), numBytes);
}
unsigned long long EBML::randomUID()
{
static std::random_device device;
static std::mt19937 generator(device());
static std::uniform_int_distribution<unsigned long long> distribution;
return distribution(generator);
}

View File

@@ -0,0 +1,78 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLUTILS_H
#define TAGLIB_EBMLUTILS_H
#ifndef DO_NOT_DOCUMENT
#include <utility>
#include "taglib.h"
#include "ebmlelement.h"
namespace TagLib {
class File;
class ByteVector;
namespace EBML {
std::unique_ptr<Element> findElement(File &file, Element::Id id, offset_t maxOffset);
std::unique_ptr<Element> findNextElement(File &file, offset_t maxOffset);
template <int maxSizeLength>
unsigned int VINTSizeLength(uint8_t firstByte);
std::pair<unsigned int, uint64_t> readVINT(File &file);
std::pair<unsigned int, uint64_t> parseVINT(const ByteVector &buffer);
ByteVector renderVINT(uint64_t number, int minSizeLength);
unsigned long long randomUID();
constexpr int minSize(uint64_t data)
{
if(data <= 0x7Fu)
return 1;
if(data <= 0x3FFFu)
return 2;
if(data <= 0x1FFFFFu)
return 3;
if(data <= 0xFFFFFFFu)
return 4;
if(data <= 0x7FFFFFFFFu)
return 5;
return 0;
}
constexpr int idSize(Element::Id id)
{
const auto uintId = static_cast<unsigned int>(id);
if(uintId <= 0xFF)
return 1;
if(uintId <= 0xFFFF)
return 2;
if(uintId <= 0xFFFFFF)
return 3;
return 4;
}
}
}
#endif
#endif

View File

@@ -0,0 +1,73 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "ebmlvoidelement.h"
#include <algorithm>
#include "ebmlutils.h"
#include "tbytevector.h"
using namespace TagLib;
EBML::VoidElement::VoidElement(int sizeLength, offset_t dataSize):
Element(Id::VoidElement, sizeLength, dataSize)
{
}
EBML::VoidElement::VoidElement(Id, int sizeLength, offset_t dataSize, offset_t):
Element(Id::VoidElement, sizeLength, dataSize)
{
}
EBML::VoidElement::VoidElement():
Element(Id::VoidElement, 0, 0)
{
}
ByteVector EBML::VoidElement::render()
{
offset_t bytesNeeded = targetSize;
ByteVector buffer = renderId();
bytesNeeded -= buffer.size();
sizeLength = static_cast<int>(std::min(bytesNeeded, static_cast<offset_t>(8)));
bytesNeeded -= sizeLength;
dataSize = bytesNeeded;
buffer.append(renderVINT(dataSize, sizeLength));
if(dataSize)
buffer.append(ByteVector(static_cast<unsigned int>(dataSize), 0));
return buffer;
}
offset_t EBML::VoidElement::getTargetSize() const
{
return targetSize;
}
void EBML::VoidElement::setTargetSize(offset_t size)
{
this->targetSize = std::max(size, MIN_VOID_ELEMENT_SIZE);
}
ByteVector EBML::VoidElement::renderSize(offset_t targetSize)
{
VoidElement element;
element.setTargetSize(targetSize);
return element.render();
}

View File

@@ -0,0 +1,51 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_EBMLVOIDELEMENT_H
#define TAGLIB_EBMLVOIDELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include "ebmlelement.h"
namespace TagLib {
class File;
namespace EBML {
inline constexpr offset_t MIN_VOID_ELEMENT_SIZE = 2;
class VoidElement : public Element
{
public:
VoidElement(int sizeLength, offset_t dataSize);
VoidElement(Id, int sizeLength, offset_t dataSize, offset_t);
VoidElement();
ByteVector render() override;
offset_t getTargetSize() const;
void setTargetSize(offset_t size);
static ByteVector renderSize(offset_t targetSize);
private:
offset_t targetSize = MIN_VOID_ELEMENT_SIZE;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,99 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskaattachedfile.h"
#include "tbytevector.h"
using namespace TagLib;
class Matroska::AttachedFile::AttachedFilePrivate
{
public:
AttachedFilePrivate(const ByteVector &data, const String &fileName,
const String &mediaType, UID uid, const String &description) :
fileName(fileName), description(description), mediaType(mediaType),
data(data), uid(uid) {}
~AttachedFilePrivate() = default;
String fileName;
String description;
String mediaType;
ByteVector data;
UID uid = 0;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Matroska::AttachedFile::AttachedFile(const ByteVector &data,
const String &fileName, const String &mediaType, UID uid,
const String &description) :
d(std::make_unique<AttachedFilePrivate>(data, fileName, mediaType, uid, description))
{
}
Matroska::AttachedFile::AttachedFile(const AttachedFile &other) :
d(std::make_unique<AttachedFilePrivate>(*other.d))
{
}
Matroska::AttachedFile::AttachedFile(AttachedFile &&other) noexcept = default;
Matroska::AttachedFile::~AttachedFile() = default;
Matroska::AttachedFile &Matroska::AttachedFile::operator=(AttachedFile &&other) noexcept = default;
Matroska::AttachedFile &Matroska::AttachedFile::operator=(const AttachedFile &other)
{
AttachedFile(other).swap(*this);
return *this;
}
void Matroska::AttachedFile::swap(AttachedFile &other) noexcept
{
using std::swap;
swap(d, other.d);
}
const String &Matroska::AttachedFile::fileName() const
{
return d->fileName;
}
const String &Matroska::AttachedFile::description() const
{
return d->description;
}
const String &Matroska::AttachedFile::mediaType() const
{
return d->mediaType;
}
const ByteVector &Matroska::AttachedFile::data() const
{
return d->data;
}
Matroska::AttachedFile::UID Matroska::AttachedFile::uid() const
{
return d->uid;
}

View File

@@ -0,0 +1,110 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKAATTACHEDFILE_H
#define TAGLIB_MATROSKAATTACHEDFILE_H
#include <memory>
#include "tstring.h"
#include "taglib_export.h"
namespace TagLib {
class String;
class ByteVector;
namespace Matroska {
//! Attached file embedded into a Matroska file.
class TAGLIB_EXPORT AttachedFile
{
public:
//! Unique identifier.
using UID = unsigned long long;
/*!
* Construct an attached file.
*/
AttachedFile(const ByteVector &data, const String &fileName,
const String &mediaType, UID uid = 0,
const String &description = String());
/*!
* Construct an attached file as a copy of \a other.
*/
AttachedFile(const AttachedFile &other);
/*!
* Construct an attached file moving from \a other.
*/
AttachedFile(AttachedFile &&other) noexcept;
/*!
* Destroys this attached file.
*/
~AttachedFile();
/*!
* Copies the contents of \a other into this object.
*/
AttachedFile &operator=(const AttachedFile &other);
/*!
* Moves the contents of \a other into this object.
*/
AttachedFile &operator=(AttachedFile &&other) noexcept;
/*!
* Exchanges the content of the object with the content of \a other.
*/
void swap(AttachedFile &other) noexcept;
/*!
* Returns the filename of the attached file.
*/
const String &fileName() const;
/*!
* Returns the human-friendly description for the attached file.
*/
const String &description() const;
/*!
* Returns the media type of the attached file.
*/
const String &mediaType() const;
/*!
* Returns the data of the attached file.
*/
const ByteVector &data() const;
/*!
* Returns the UID of the attached file.
*/
UID uid() const;
private:
class AttachedFilePrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<AttachedFilePrivate> d;
};
}
}
#endif

View File

@@ -0,0 +1,141 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskaattachments.h"
#include "matroskaattachedfile.h"
#include "ebmlmkattachments.h"
#include "ebmlmasterelement.h"
#include "ebmlstringelement.h"
#include "ebmlbinaryelement.h"
#include "ebmluintelement.h"
#include "ebmlutils.h"
#include "tlist.h"
#include "tbytevector.h"
using namespace TagLib;
class Matroska::Attachments::AttachmentsPrivate
{
public:
AttachmentsPrivate() = default;
~AttachmentsPrivate() = default;
AttachmentsPrivate(const AttachmentsPrivate &) = delete;
AttachmentsPrivate &operator=(const AttachmentsPrivate &) = delete;
AttachedFileList files;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Matroska::Attachments::Attachments() :
Element(static_cast<ID>(EBML::Element::Id::MkAttachments)),
d(std::make_unique<AttachmentsPrivate>())
{
}
Matroska::Attachments::~Attachments() = default;
void Matroska::Attachments::addAttachedFile(const AttachedFile &file)
{
d->files.append(file);
setNeedsRender(true);
}
void Matroska::Attachments::removeAttachedFile(unsigned long long uid)
{
const auto it = std::find_if(d->files.begin(), d->files.end(),
[uid](const AttachedFile &file) {
return file.uid() == uid;
});
if(it != d->files.end()) {
d->files.erase(it);
setNeedsRender(true);
}
}
void Matroska::Attachments::clear()
{
d->files.clear();
setNeedsRender(true);
}
const Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFileList() const
{
return d->files;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
Matroska::Attachments::AttachedFileList &Matroska::Attachments::attachedFiles()
{
setNeedsRender(true);
return d->files;
}
ByteVector Matroska::Attachments::renderInternal()
{
if(d->files.isEmpty()) {
// Avoid writing an Attachments element without AttachedFile element.
return {};
}
EBML::MkAttachments attachments;
for(const auto &attachedFile : std::as_const(d->files)) {
auto attachedFileElement = EBML::make_unique_element<EBML::Element::Id::MkAttachedFile>();
// Filename
auto fileNameElement = EBML::make_unique_element<EBML::Element::Id::MkAttachedFileName>();
fileNameElement->setValue(attachedFile.fileName());
attachedFileElement->appendElement(std::move(fileNameElement));
// Media/MIME type
auto mediaTypeElement =
EBML::make_unique_element<EBML::Element::Id::MkAttachedFileMediaType>();
mediaTypeElement->setValue(attachedFile.mediaType());
attachedFileElement->appendElement(std::move(mediaTypeElement));
// Description
if(const String &description = attachedFile.description(); !description.isEmpty()) {
auto descriptionElement =
EBML::make_unique_element<EBML::Element::Id::MkAttachedFileDescription>();
descriptionElement->setValue(description);
attachedFileElement->appendElement(std::move(descriptionElement));
}
// Data
auto dataElement = EBML::make_unique_element<EBML::Element::Id::MkAttachedFileData>();
dataElement->setValue(attachedFile.data());
attachedFileElement->appendElement(std::move(dataElement));
// UID
auto uidElement = EBML::make_unique_element<EBML::Element::Id::MkAttachedFileUID>();
AttachedFile::UID uid = attachedFile.uid();
if(!uid)
uid = EBML::randomUID();
uidElement->setValue(uid);
attachedFileElement->appendElement(std::move(uidElement));
attachments.appendElement(std::move(attachedFileElement));
}
return attachments.render();
}

View File

@@ -0,0 +1,83 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKAATTACHMENTS_H
#define TAGLIB_MATROSKAATTACHMENTS_H
#include <memory>
#include "taglib_export.h"
#include "tlist.h"
#include "matroskaelement.h"
namespace TagLib {
class File;
namespace EBML {
class MkAttachments;
}
namespace Matroska {
class AttachedFile;
class File;
//! Collection of attached files.
class TAGLIB_EXPORT Attachments
#ifndef DO_NOT_DOCUMENT
: private Element
#endif
{
public:
//! List of attached files.
using AttachedFileList = List<AttachedFile>;
//! Construct attachments.
Attachments();
//! Destroy attachments.
virtual ~Attachments();
//! Add an attached file.
void addAttachedFile(const AttachedFile &file);
//! Remove an attached file.
void removeAttachedFile(unsigned long long uid);
//! Remove all attached files.
void clear();
//! Get list of all attached files.
const AttachedFileList &attachedFileList() const;
private:
friend class EBML::MkAttachments;
friend class File;
class AttachmentsPrivate;
// private Element implementation
ByteVector renderInternal() override;
AttachedFileList &attachedFiles();
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<AttachmentsPrivate> d;
};
}
}
#endif

View File

@@ -0,0 +1,157 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskachapter.h"
#include "tstring.h"
#include "tbytevector.h"
using namespace TagLib;
class Matroska::Chapter::Display::DisplayPrivate
{
public:
DisplayPrivate() = default;
~DisplayPrivate() = default;
String string;
String language;
};
class Matroska::Chapter::ChapterPrivate
{
public:
ChapterPrivate() = default;
~ChapterPrivate() = default;
UID uid = 0;
Time timeStart = 0;
Time timeEnd = 0;
List<Display> displayList;
bool hidden = false;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Matroska::Chapter::Chapter(Time timeStart, Time timeEnd,
const List<Display> &displayList, UID uid, bool hidden) :
d(std::make_unique<ChapterPrivate>())
{
d->uid = uid;
d->timeStart = timeStart;
d->timeEnd = timeEnd;
d->displayList = displayList;
d->hidden = hidden;
}
Matroska::Chapter::Chapter(const Chapter &other) :
d(std::make_unique<ChapterPrivate>(*other.d))
{
}
Matroska::Chapter::Chapter(Chapter &&other) noexcept = default;
Matroska::Chapter::~Chapter() = default;
Matroska::Chapter &Matroska::Chapter::operator=(Chapter &&other) noexcept = default;
Matroska::Chapter &Matroska::Chapter::operator=(const Chapter &other)
{
Chapter(other).swap(*this);
return *this;
}
void Matroska::Chapter::swap(Chapter &other) noexcept
{
using std::swap;
swap(d, other.d);
}
Matroska::Chapter::UID Matroska::Chapter::uid() const
{
return d->uid;
}
Matroska::Chapter::Time Matroska::Chapter::timeStart() const
{
return d->timeStart;
}
Matroska::Chapter::Time Matroska::Chapter::timeEnd() const
{
return d->timeEnd;
}
bool Matroska::Chapter::isHidden() const
{
return d->hidden;
}
const List<Matroska::Chapter::Display> &Matroska::Chapter::displayList() const
{
return d->displayList;
}
Matroska::Chapter::Display::Display(const String &string, const String &language) :
d(std::make_unique<DisplayPrivate>())
{
d->string = string;
d->language = language;
}
Matroska::Chapter::Display::Display(const Display &other) :
d(std::make_unique<DisplayPrivate>(*other.d))
{
}
Matroska::Chapter::Display::Display(Display &&other) noexcept = default;
Matroska::Chapter::Display::~Display() = default;
Matroska::Chapter::Display &Matroska::Chapter::Display::operator=(const Display &other)
{
Display(other).swap(*this);
return *this;
}
Matroska::Chapter::Display &Matroska::Chapter::Display::operator=(
Display &&other) noexcept = default;
void Matroska::Chapter::Display::swap(Display &other) noexcept
{
using std::swap;
swap(d, other.d);
}
const String &Matroska::Chapter::Display::string() const
{
return d->string;
}
const String &Matroska::Chapter::Display::language() const
{
return d->language;
}

View File

@@ -0,0 +1,178 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKACHAPTER_H
#define TAGLIB_MATROSKACHAPTER_H
#include <memory>
#include "taglib_export.h"
#include "tlist.h"
namespace TagLib {
class String;
class ByteVector;
namespace EBML {
class MkChapters;
}
namespace Matroska {
//! Matroska chapter.
class TAGLIB_EXPORT Chapter
{
public:
//! Unique identifier.
using UID = unsigned long long;
//! Timestamp in nanoseconds.
using Time = unsigned long long;
/*!
* Contains all possible strings to use for the chapter display.
*/
class TAGLIB_EXPORT Display
{
public:
/*!
* Construct a chapter display.
*/
Display(const String &string, const String &language);
/*!
* Construct a chapter display as a copy of \a other.
*/
Display(const Display &other);
/*!
* Construct a chapter display moving from \a other.
*/
Display(Display &&other) noexcept;
/*!
* Destroys this chapter display.
*/
~Display();
/*!
* Copies the contents of \a other into this object.
*/
Display &operator=(const Display &other);
/*!
* Moves the contents of \a other into this object.
*/
Display &operator=(Display &&other) noexcept;
/*!
* Exchanges the content of the object with the content of \a other.
*/
void swap(Display &other) noexcept;
/*!
* Returns string representing the chapter.
*/
const String &string() const;
/*!
* Returns language corresponding to the string.
*/
const String &language() const;
private:
class DisplayPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<DisplayPrivate> d;
};
/*!
* Construct a chapter.
*/
Chapter(Time timeStart, Time timeEnd, const List<Display> &displayList,
UID uid, bool hidden = false);
/*!
* Construct a chapter as a copy of \a other.
*/
Chapter(const Chapter &other);
/*!
* Construct a chapter moving from \a other.
*/
Chapter(Chapter &&other) noexcept;
/*!
* Destroys this chapter.
*/
~Chapter();
/*!
* Copies the contents of \a other into this object.
*/
Chapter &operator=(const Chapter &other);
/*!
* Moves the contents of \a other into this object.
*/
Chapter &operator=(Chapter &&other) noexcept;
/*!
* Exchanges the content of the object with the content of \a other.
*/
void swap(Chapter &other) noexcept;
/*!
* Returns the UID of the chapter.
*/
UID uid() const;
/*!
* Returns the timestamp of the start of the chapter in nanoseconds.
*/
Time timeStart() const;
/*!
* Returns the timestamp of the start of the chapter in nanoseconds.
*/
Time timeEnd() const;
/*!
* Check if chapter is hidden.
*/
bool isHidden() const;
/*!
* Returns strings with language.
*/
const List<Display> &displayList() const;
private:
class ChapterPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<ChapterPrivate> d;
};
}
}
#endif

View File

@@ -0,0 +1,102 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskachapter.h"
#include "matroskachapteredition.h"
#include "tstring.h"
#include "tbytevector.h"
#include "tlist.h"
using namespace TagLib;
class Matroska::ChapterEdition::ChapterEditionPrivate
{
public:
ChapterEditionPrivate() = default;
~ChapterEditionPrivate() = default;
List<Chapter> chapters;
UID uid = 0;
bool flagDefault = false;
bool flagOrdered = false;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Matroska::ChapterEdition::ChapterEdition(const List<Chapter> &chapterList,
bool isDefault, bool isOrdered, UID uid) :
d(std::make_unique<ChapterEditionPrivate>())
{
d->chapters = chapterList;
d->uid = uid;
d->flagDefault = isDefault;
d->flagOrdered = isOrdered;
}
Matroska::ChapterEdition::ChapterEdition(const ChapterEdition &other) :
d(std::make_unique<ChapterEditionPrivate>(*other.d))
{
}
Matroska::ChapterEdition::ChapterEdition(ChapterEdition &&other) noexcept = default;
Matroska::ChapterEdition::~ChapterEdition() = default;
Matroska::ChapterEdition &Matroska::ChapterEdition::operator=(
ChapterEdition &&other) noexcept = default;
Matroska::ChapterEdition &Matroska::ChapterEdition::operator=(const ChapterEdition &other)
{
ChapterEdition(other).swap(*this);
return *this;
}
void Matroska::ChapterEdition::swap(ChapterEdition &other) noexcept
{
using std::swap;
swap(d, other.d);
}
Matroska::ChapterEdition::UID Matroska::ChapterEdition::uid() const
{
return d->uid;
}
bool Matroska::ChapterEdition::isDefault() const
{
return d->flagDefault;
}
bool Matroska::ChapterEdition::isOrdered() const
{
return d->flagOrdered;
}
const List<Matroska::Chapter> &Matroska::ChapterEdition::chapterList() const
{
return d->chapters;
}

View File

@@ -0,0 +1,108 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKACHAPTEREDITION_H
#define TAGLIB_MATROSKACHAPTEREDITION_H
#include "matroskachapter.h"
namespace TagLib {
class String;
class ByteVector;
namespace Matroska {
//! Edition of chapters.
class TAGLIB_EXPORT ChapterEdition
{
public:
//! Unique identifier.
using UID = unsigned long long;
/*!
* Construct an edition.
*/
ChapterEdition(const List<Chapter> &chapterList,
bool isDefault, bool isOrdered = false, UID uid = 0);
/*!
* Construct an edition as a copy of \a other.
*/
ChapterEdition(const ChapterEdition &other);
/*!
* Construct an edition moving from \a other.
*/
ChapterEdition(ChapterEdition &&other) noexcept;
/*!
* Destroys this edition.
*/
~ChapterEdition();
/*!
* Copies the contents of \a other into this object.
*/
ChapterEdition &operator=(const ChapterEdition &other);
/*!
* Moves the contents of \a other into this object.
*/
ChapterEdition &operator=(ChapterEdition &&other) noexcept;
/*!
* Exchanges the content of the object with the content of \a other.
*/
void swap(ChapterEdition &other) noexcept;
/*!
* Returns the UID of the edition.
*/
UID uid() const;
/*!
* Check if this edition should be used as the default one.
*/
bool isDefault() const;
/*!
* Check if the chapters can be defined multiple times and the order to
* play them is enforced.
*/
bool isOrdered() const;
/*!
* Get the list of all chapters.
*/
const List<Chapter> &chapterList() const;
private:
class ChapterEditionPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<ChapterEditionPrivate> d;
};
}
}
#endif

View File

@@ -0,0 +1,151 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskachapters.h"
#include "matroskachapteredition.h"
#include "ebmlstringelement.h"
#include "ebmlbinaryelement.h"
#include "ebmlmkchapters.h"
#include "ebmluintelement.h"
#include "ebmlutils.h"
#include "tlist.h"
#include "tbytevector.h"
using namespace TagLib;
class Matroska::Chapters::ChaptersPrivate
{
public:
ChaptersPrivate() = default;
~ChaptersPrivate() = default;
ChaptersPrivate(const ChaptersPrivate &) = delete;
ChaptersPrivate &operator=(const ChaptersPrivate &) = delete;
ChapterEditionList editions;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Matroska::Chapters::Chapters() :
Element(static_cast<ID>(EBML::Element::Id::MkChapters)),
d(std::make_unique<ChaptersPrivate>())
{
}
Matroska::Chapters::~Chapters() = default;
void Matroska::Chapters::addChapterEdition(const ChapterEdition &edition)
{
d->editions.append(edition);
setNeedsRender(true);
}
void Matroska::Chapters::removeChapterEdition(unsigned long long uid)
{
const auto it = std::find_if(d->editions.begin(), d->editions.end(),
[uid](const ChapterEdition &file) {
return file.uid() == uid;
});
if(it != d->editions.end()) {
d->editions.erase(it);
setNeedsRender(true);
}
}
void Matroska::Chapters::clear()
{
d->editions.clear();
setNeedsRender(true);
}
const Matroska::Chapters::ChapterEditionList &Matroska::Chapters::chapterEditionList() const
{
return d->editions;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
ByteVector Matroska::Chapters::renderInternal()
{
if(d->editions.isEmpty()) {
// Avoid writing a Chapters element without ChapterEdition element.
return {};
}
EBML::MkChapters chapters;
for(const auto &chapterEdition : std::as_const(d->editions)) {
auto chapterEditionElement = EBML::make_unique_element<EBML::Element::Id::MkEditionEntry>();
if(const auto uid = chapterEdition.uid()) {
auto uidElement = EBML::make_unique_element<EBML::Element::Id::MkEditionUID>();
uidElement->setValue(uid);
chapterEditionElement->appendElement(std::move(uidElement));
}
auto defaultElement = EBML::make_unique_element<EBML::Element::Id::MkEditionFlagDefault>();
defaultElement->setValue(chapterEdition.isDefault());
chapterEditionElement->appendElement(std::move(defaultElement));
auto orderedElement = EBML::make_unique_element<EBML::Element::Id::MkEditionFlagOrdered>();
orderedElement->setValue(chapterEdition.isOrdered());
chapterEditionElement->appendElement(std::move(orderedElement));
for(const auto &chapter : chapterEdition.chapterList()) {
auto chapterElement = EBML::make_unique_element<EBML::Element::Id::MkChapterAtom>();
auto cuidElement = EBML::make_unique_element<EBML::Element::Id::MkChapterUID>();
const auto cuid = chapter.uid();
cuidElement->setValue(cuid ? cuid : EBML::randomUID());
chapterElement->appendElement(std::move(cuidElement));
auto timeStartElement = EBML::make_unique_element<EBML::Element::Id::MkChapterTimeStart>();
timeStartElement->setValue(chapter.timeStart());
chapterElement->appendElement(std::move(timeStartElement));
auto timeEndElement = EBML::make_unique_element<EBML::Element::Id::MkChapterTimeEnd>();
timeEndElement->setValue(chapter.timeEnd());
chapterElement->appendElement(std::move(timeEndElement));
auto hiddenElement = EBML::make_unique_element<EBML::Element::Id::MkChapterFlagHidden>();
hiddenElement->setValue(chapter.isHidden());
chapterElement->appendElement(std::move(hiddenElement));
for(const auto &display : chapter.displayList()) {
auto displayElement = EBML::make_unique_element<EBML::Element::Id::MkChapterDisplay>();
auto stringElement = EBML::make_unique_element<EBML::Element::Id::MkChapString>();
stringElement->setValue(display.string());
displayElement->appendElement(std::move(stringElement));
auto languageElement = EBML::make_unique_element<EBML::Element::Id::MkChapLanguage>();
languageElement->setValue(display.language());
displayElement->appendElement(std::move(languageElement));
chapterElement->appendElement(std::move(displayElement));
}
chapterEditionElement->appendElement(std::move(chapterElement));
}
chapters.appendElement(std::move(chapterEditionElement));
}
return chapters.render();
}

View File

@@ -0,0 +1,87 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKACHAPTERS_H
#define TAGLIB_MATROSKACHAPTERS_H
#include <memory>
#include "taglib_export.h"
#include "tlist.h"
#include "matroskaelement.h"
namespace TagLib {
class File;
namespace EBML {
class MkChapters;
}
namespace Matroska {
class ChapterEdition;
class File;
//! Collection of chapter editions.
class TAGLIB_EXPORT Chapters
#ifndef DO_NOT_DOCUMENT
: private Element
#endif
{
public:
//! List of chapter editions.
using ChapterEditionList = List<ChapterEdition>;
//! Construct chapters.
Chapters();
//! Destroy chapters.
virtual ~Chapters();
//! Add a chapter edition.
void addChapterEdition(const ChapterEdition &edition);
//! Remove a chapter edition.
void removeChapterEdition(unsigned long long uid);
//! Remove all chapter editions.
void clear();
//! Get list of all chapter editions.
const ChapterEditionList &chapterEditionList() const;
private:
friend class EBML::MkChapters;
friend class File;
class ChaptersPrivate;
// private Element implementation
ByteVector renderInternal() override;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<ChaptersPrivate> d;
};
}
}
#endif

View File

@@ -0,0 +1,312 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskacues.h"
#include "ebmlelement.h"
#include "ebmlmkcues.h"
#include "ebmlmasterelement.h"
#include "ebmluintelement.h"
#include "tlist.h"
#include "tdebug.h"
#include "tfile.h"
using namespace TagLib;
Matroska::Cues::Cues(offset_t segmentDataOffset) :
Element(static_cast<ID>(EBML::Element::Id::MkCues)),
segmentDataOffset(segmentDataOffset)
{
setNeedsRender(false);
}
Matroska::Cues::~Cues() = default;
ByteVector Matroska::Cues::renderInternal()
{
const auto beforeSize = sizeRenderedOrWritten();
EBML::MkCues cues;
cues.setMinRenderSize(beforeSize);
for(const auto &cuePoint : cuePoints) {
auto cuePointElement = EBML::make_unique_element<EBML::Element::Id::MkCuePoint>();
auto timestamp = EBML::make_unique_element<EBML::Element::Id::MkCueTime>();
timestamp->setValue(cuePoint->getTime());
cuePointElement->appendElement(std::move(timestamp));
const auto &trackList = cuePoint->cueTrackList();
for(const auto &cueTrack : trackList) {
auto cueTrackElement = EBML::make_unique_element<EBML::Element::Id::MkCueTrackPositions>();
// Track number
auto trackNumber = EBML::make_unique_element<EBML::Element::Id::MkCueTrack>();
trackNumber->setValue(cueTrack->getTrackNumber());
cueTrackElement->appendElement(std::move(trackNumber));
// Cluster position
auto clusterPosition = EBML::make_unique_element<EBML::Element::Id::MkCueClusterPosition>();
clusterPosition->setValue(cueTrack->getClusterPosition());
cueTrackElement->appendElement(std::move(clusterPosition));
// Relative position, optional
if(cueTrack->getRelativePosition().has_value()) {
auto relativePosition = EBML::make_unique_element<EBML::Element::Id::MkCueRelativePosition>();
// operator*() used instead of value() to support restricted compilers
relativePosition->setValue(*cueTrack->getRelativePosition());
cueTrackElement->appendElement(std::move(relativePosition));
}
// Duration, optional
if(cueTrack->getDuration().has_value()) {
auto duration = EBML::make_unique_element<EBML::Element::Id::MkCueDuration>();
// operator*() used instead of value() to support restricted compilers
duration->setValue(*cueTrack->getDuration());
cueTrackElement->appendElement(std::move(duration));
}
// Block number, optional
if(cueTrack->getBlockNumber().has_value()) {
auto blockNumber = EBML::make_unique_element<EBML::Element::Id::MkCueBlockNumber>();
// operator*() used instead of value() to support restricted compilers
blockNumber->setValue(*cueTrack->getBlockNumber());
cueTrackElement->appendElement(std::move(blockNumber));
}
// Codec state, not in version 1
if(cueTrack->getCodecState().has_value()) {
auto codecState = EBML::make_unique_element<EBML::Element::Id::MkCueCodecState>();
// operator*() used instead of value() to support restricted compilers
codecState->setValue(*cueTrack->getCodecState());
cueTrackElement->appendElement(std::move(codecState));
}
// Reference times
if(auto referenceTimes = cueTrack->referenceTimes(); !referenceTimes.isEmpty()) {
auto cueReference = EBML::make_unique_element<EBML::Element::Id::MkCueReference>();
for(const auto reference : referenceTimes) {
auto refTime = EBML::make_unique_element<EBML::Element::Id::MkCueRefTime>();
refTime->setValue(reference);
cueReference->appendElement(std::move(refTime));
}
cueTrackElement->appendElement(std::move(cueReference));
}
cuePointElement->appendElement(std::move(cueTrackElement));
}
cues.appendElement(std::move(cuePointElement));
}
return cues.render();
}
void Matroska::Cues::write(TagLib::File &file)
{
if(!data().isEmpty())
Element::write(file);
}
bool Matroska::Cues::sizeChanged(Element &caller, offset_t delta)
{
// Adjust own offset
if(!Element::sizeChanged(caller, delta))
return false;
const offset_t offset = caller.offset() - segmentDataOffset;
for(const auto &cuePoint : cuePoints) {
if(cuePoint->adjustOffset(offset, delta)) {
setNeedsRender(true);
}
}
return true;
}
bool Matroska::Cues::isValid(TagLib::File &file) const
{
for(const auto &cuePoint : cuePoints) {
if(!cuePoint->isValid(file, segmentDataOffset))
return false;
}
return true;
}
void Matroska::Cues::addCuePoint(std::unique_ptr<CuePoint> &&cuePoint)
{
cuePoints.push_back(std::move(cuePoint));
}
const Matroska::Cues::CuePointList &Matroska::Cues::cuePointList()
{
return cuePoints;
}
Matroska::CuePoint::CuePoint() = default;
Matroska::CuePoint::~CuePoint() = default;
bool Matroska::CuePoint::isValid(TagLib::File &file, offset_t segmentDataOffset) const
{
for(const auto &track : cueTracks) {
if(!track->isValid(file, segmentDataOffset))
return false;
}
return true;
}
void Matroska::CuePoint::addCueTrack(std::unique_ptr<CueTrack> &&cueTrack)
{
cueTracks.push_back(std::move(cueTrack));
}
const Matroska::CuePoint::CueTrackList &Matroska::CuePoint::cueTrackList() const
{
return cueTracks;
}
void Matroska::CuePoint::setTime(Time timestamp)
{
time = timestamp;
}
Matroska::CuePoint::Time Matroska::CuePoint::getTime() const
{
return time;
}
bool Matroska::CuePoint::adjustOffset(offset_t offset, offset_t delta)
{
bool ret = false;
for(const auto &cueTrack : cueTracks)
ret |= cueTrack->adjustOffset(offset, delta);
return ret;
}
Matroska::CueTrack::CueTrack() = default;
Matroska::CueTrack::~CueTrack() = default;
bool Matroska::CueTrack::isValid(TagLib::File &file, offset_t segmentDataOffset) const
{
if(!trackNumber) {
debug("Cue track number not set");
return false;
}
if(!clusterPosition) {
debug("Cue track cluster position not set");
return false;
}
file.seek(segmentDataOffset + clusterPosition);
if(EBML::Element::readId(file) != static_cast<unsigned int>(EBML::Element::Id::MkCluster)) {
debug("No cluster found at position");
return false;
}
if(codecState.has_value() && *codecState != 0) {
// operator*() used instead of value() to support restricted compilers
file.seek(segmentDataOffset + *codecState);
if(EBML::Element::readId(file) != static_cast<unsigned int>(EBML::Element::Id::MkCodecState)) {
debug("No codec state found at position");
return false;
}
}
return true;
}
void Matroska::CueTrack::setTrackNumber(unsigned long long trackNr)
{
trackNumber = trackNr;
}
unsigned long long Matroska::CueTrack::getTrackNumber() const
{
return trackNumber;
}
void Matroska::CueTrack::setClusterPosition(offset_t clusterPos)
{
clusterPosition = clusterPos;
}
offset_t Matroska::CueTrack::getClusterPosition() const
{
return clusterPosition;
}
void Matroska::CueTrack::setRelativePosition(std::optional<offset_t> relativePos)
{
relativePosition = relativePos;
}
std::optional<offset_t> Matroska::CueTrack::getRelativePosition() const
{
return relativePosition;
}
void Matroska::CueTrack::setCodecState(std::optional<offset_t> codecStatePos)
{
codecState = codecStatePos;
}
std::optional<offset_t> Matroska::CueTrack::getCodecState() const
{
return codecState;
}
void Matroska::CueTrack::setBlockNumber(std::optional<unsigned long long> blockNr)
{
blockNumber = blockNr;
}
std::optional<unsigned long long> Matroska::CueTrack::getBlockNumber() const
{
return blockNumber;
}
void Matroska::CueTrack::setDuration(std::optional<unsigned long long> segmentTicks)
{
duration = segmentTicks;
}
std::optional<unsigned long long> Matroska::CueTrack::getDuration() const
{
return duration;
}
void Matroska::CueTrack::addReferenceTime(unsigned long long refTime)
{
refTimes.append(refTime);
}
const Matroska::CueTrack::ReferenceTimeList &Matroska::CueTrack::referenceTimes() const
{
return refTimes;
}
bool Matroska::CueTrack::adjustOffset(offset_t offset, offset_t delta)
{
bool ret = false;
if(clusterPosition > offset) {
clusterPosition += delta;
ret = true;
}
// operator*() used instead of value() to support restricted compilers
if(offset_t codecStateValue;
codecState.has_value() && (codecStateValue = *codecState) != 0 &&
codecStateValue > offset) {
codecState = codecStateValue + delta;
ret = true;
}
return ret;
}

View File

@@ -0,0 +1,116 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKACUES_H
#define TAGLIB_MATROSKACUES_H
#ifndef DO_NOT_DOCUMENT
#include <optional>
#include "tlist.h"
#include "matroskaelement.h"
namespace TagLib {
class File;
namespace EBML {
class MkCues;
}
namespace Matroska {
class CuePoint;
class CueTrack;
class Cues : public Element
{
public:
using CuePointList = std::list<std::unique_ptr<CuePoint>>;
explicit Cues(offset_t segmentDataOffset);
~Cues() override;
bool isValid(TagLib::File &file) const;
void addCuePoint(std::unique_ptr<CuePoint> &&cuePoint);
const CuePointList &cuePointList();
bool sizeChanged(Element &caller, offset_t delta) override;
void write(TagLib::File &file) override;
private:
friend class EBML::MkCues;
ByteVector renderInternal() override;
CuePointList cuePoints;
const offset_t segmentDataOffset;
};
class CuePoint
{
public:
using CueTrackList = std::list<std::unique_ptr<CueTrack>>;
using Time = unsigned long long;
CuePoint();
~CuePoint();
bool isValid(TagLib::File &file, offset_t segmentDataOffset) const;
void addCueTrack(std::unique_ptr<CueTrack> &&cueTrack);
const CueTrackList &cueTrackList() const;
void setTime(Time timestamp);
Time getTime() const;
bool adjustOffset(offset_t offset, offset_t delta);
private:
CueTrackList cueTracks;
Time time = 0;
};
class CueTrack
{
public:
using ReferenceTimeList = List<unsigned long long>;
CueTrack();
~CueTrack();
bool isValid(TagLib::File &file, offset_t segmentDataOffset) const;
void setTrackNumber(unsigned long long trackNr);
unsigned long long getTrackNumber() const;
void setClusterPosition(offset_t clusterPos);
offset_t getClusterPosition() const;
void setRelativePosition(std::optional<offset_t> relativePos);
std::optional<offset_t> getRelativePosition() const;
void setCodecState(std::optional<offset_t> codecStatePos);
std::optional<offset_t> getCodecState() const;
void setBlockNumber(std::optional<unsigned long long> blockNr);
std::optional<unsigned long long> getBlockNumber() const;
void setDuration(std::optional<unsigned long long> segmentTicks);
std::optional<unsigned long long> getDuration() const;
void addReferenceTime(unsigned long long refTime);
const ReferenceTimeList &referenceTimes() const;
bool adjustOffset(offset_t offset, offset_t delta);
private:
unsigned long long trackNumber = 0;
offset_t clusterPosition = 0;
std::optional<offset_t> relativePosition;
std::optional<unsigned long long> blockNumber;
std::optional<unsigned long long> duration;
std::optional<offset_t> codecState;
ReferenceTimeList refTimes;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,168 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskaelement.h"
#include <memory>
#include "tlist.h"
#include "tfile.h"
#include "tbytevector.h"
using namespace TagLib;
class Matroska::Element::ElementPrivate
{
public:
ElementPrivate() = default;
~ElementPrivate() = default;
ElementPrivate(const ElementPrivate &) = delete;
ElementPrivate &operator=(const ElementPrivate &) = delete;
offset_t size = 0;
offset_t offset = 0;
ID id = 0;
ByteVector data;
List<Element *> sizeListeners;
// The default write() implementation will delete an unrendered element,
// therefore rendering is required by default and needs to be explicitly set
// using setNeedsRender(false) together with overriding the write() method.
bool needsRender = true;
};
Matroska::Element::Element(ID id) :
e(std::make_unique<ElementPrivate>())
{
e->id = id;
}
Matroska::Element::~Element() = default;
offset_t Matroska::Element::size() const
{
return e->size;
}
offset_t Matroska::Element::offset() const
{
return e->offset;
}
void Matroska::Element::setData(const ByteVector &data)
{
e->data = data;
}
const ByteVector &Matroska::Element::data() const
{
return e->data;
}
void Matroska::Element::setOffset(offset_t offset)
{
e->offset = offset;
}
void Matroska::Element::adjustOffset(offset_t delta)
{
e->offset += delta;
}
void Matroska::Element::setSize(offset_t size)
{
e->size = size;
}
Matroska::Element::ID Matroska::Element::id() const
{
return e->id;
}
void Matroska::Element::addSizeListener(Element *element)
{
e->sizeListeners.append(element);
}
void Matroska::Element::addSizeListeners(const List<Element *> &elements)
{
e->sizeListeners.append(elements);
}
void Matroska::Element::setID(ID id)
{
e->id = id;
}
bool Matroska::Element::render()
{
if(!needsRender())
return true;
const auto beforeSize = sizeRenderedOrWritten();
const auto data = renderInternal();
setNeedsRender(false);
if(const auto afterSize = data.size(); afterSize != beforeSize) {
if(!emitSizeChanged(afterSize - beforeSize)) {
return false;
}
}
setData(data);
return true;
}
void Matroska::Element::setNeedsRender(bool needsRender)
{
e->needsRender = needsRender;
}
bool Matroska::Element::needsRender() const
{
return e->needsRender;
}
bool Matroska::Element::emitSizeChanged(offset_t delta)
{
for(const auto element : e->sizeListeners) {
if(!element->sizeChanged(*this, delta))
return false;
}
return true;
}
bool Matroska::Element::sizeChanged(Element &caller, offset_t delta)
{
// The equal case is needed when multiple new elements are added
// (e.g. Attachments and Tags), they will start with the same offset
// are updated via size change handling.
if(caller.offset() <= e->offset && caller.id() != e->id) {
e->offset += delta;
}
return true;
}
offset_t Matroska::Element::sizeRenderedOrWritten() const
{
const offset_t dataSize = e->data.size();
return dataSize != 0 ? dataSize : e->size;
}
void Matroska::Element::write(File &file)
{
file.insert(e->data, e->offset, e->size);
e->size = e->data.size();
}

View File

@@ -0,0 +1,74 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKAELEMENT_H
#define TAGLIB_MATROSKAELEMENT_H
#ifndef DO_NOT_DOCUMENT
#include <memory>
#include "taglib_export.h"
#include "taglib.h"
#include "tlist.h"
namespace TagLib {
class File;
class ByteVector;
namespace Matroska {
class TAGLIB_EXPORT Element
{
public:
using ID = unsigned int;
explicit Element(ID id);
virtual ~Element();
offset_t size() const;
offset_t offset() const;
ID id() const;
void setOffset(offset_t offset);
void adjustOffset(offset_t delta);
void setSize(offset_t size);
void setID(ID id);
virtual bool render();
void setNeedsRender(bool needsRender);
bool needsRender() const;
void setData(const ByteVector &data);
const ByteVector &data() const;
virtual void write(TagLib::File &file);
void addSizeListener(Element *element);
void addSizeListeners(const List<Element *> &elements);
bool emitSizeChanged(offset_t delta);
virtual bool sizeChanged(Element &caller, offset_t delta);
protected:
offset_t sizeRenderedOrWritten() const;
private:
virtual ByteVector renderInternal() = 0;
class ElementPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<ElementPrivate> e;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,559 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskafile.h"
#include <memory>
#include "matroskatag.h"
#include "matroskaattachments.h"
#include "matroskaattachedfile.h"
#include "matroskachapter.h"
#include "matroskachapteredition.h"
#include "matroskachapters.h"
#include "matroskaseekhead.h"
#include "matroskacues.h"
#include "matroskasegment.h"
#include "ebmlutils.h"
#include "ebmlelement.h"
#include "ebmlstringelement.h"
#include "ebmluintelement.h"
#include "ebmlmksegment.h"
#include "tlist.h"
#include "tdebug.h"
#include "tagutils.h"
#include "tpropertymap.h"
using namespace TagLib;
class Matroska::File::FilePrivate
{
public:
FilePrivate() = default;
~FilePrivate() = default;
FilePrivate(const FilePrivate &) = delete;
FilePrivate &operator=(const FilePrivate &) = delete;
std::unique_ptr<Tag> tag;
std::unique_ptr<Attachments> attachments;
std::unique_ptr<Chapters> chapters;
std::unique_ptr<SeekHead> seekHead;
std::unique_ptr<Cues> cues;
std::unique_ptr<Segment> segment;
std::unique_ptr<Properties> properties;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool Matroska::File::isSupported(IOStream *stream)
{
const ByteVector id = Utils::readHeader(stream, 4, false);
return id.startsWith("\x1A\x45\xDF\xA3");
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Matroska::File::File(FileName file, bool readProperties,
Properties::ReadStyle readStyle) :
TagLib::File(file),
d(std::make_unique<FilePrivate>())
{
if(!isOpen()) {
debug("Failed to open matroska file");
setValid(false);
return;
}
read(readProperties, readStyle);
}
Matroska::File::File(IOStream *stream, bool readProperties,
Properties::ReadStyle readStyle) :
TagLib::File(stream),
d(std::make_unique<FilePrivate>())
{
if(!isOpen()) {
debug("Failed to open matroska file");
setValid(false);
return;
}
read(readProperties, readStyle);
}
Matroska::File::~File() = default;
Matroska::Properties *Matroska::File::audioProperties() const
{
return d->properties.get();
}
Tag *Matroska::File::tag() const
{
return tag(true);
}
Matroska::Tag *Matroska::File::tag(bool create) const
{
if(!d->tag && create) {
d->tag = std::make_unique<Tag>();
if(d->properties) {
d->tag->setSegmentTitle(d->properties->title());
}
}
return d->tag.get();
}
PropertyMap Matroska::File::properties() const
{
return d->tag ? d->tag->properties() : PropertyMap();
}
void Matroska::File::removeUnsupportedProperties(const StringList &properties)
{
if(d->tag) {
d->tag->removeUnsupportedProperties(properties);
}
}
PropertyMap Matroska::File::setProperties(const PropertyMap &properties)
{
if(!d->tag) {
d->tag = std::make_unique<Tag>();
}
return d->tag->setProperties(properties);
}
namespace {
String keyForAttachedFile(const Matroska::AttachedFile &attachedFile)
{
if(attachedFile.mediaType().startsWith("image/")) {
return "PICTURE";
}
if(!attachedFile.fileName().isEmpty()) {
return attachedFile.fileName();
}
if(!attachedFile.mediaType().isEmpty()) {
return attachedFile.mediaType();
}
return String::fromULongLong(attachedFile.uid());
}
bool keyMatchesAttachedFile(const String &key, const Matroska::AttachedFile &attachedFile)
{
return !key.isEmpty() && (
(key == "PICTURE" && attachedFile.mediaType().startsWith("image/")) ||
key == attachedFile.fileName() ||
key == attachedFile.mediaType() ||
key == String::fromULongLong(attachedFile.uid())
);
}
}
StringList Matroska::File::complexPropertyKeys() const
{
StringList keys = TagLib::File::complexPropertyKeys();
if(d->attachments) {
const auto &attachedFiles = d->attachments->attachedFileList();
for(const auto &attachedFile : attachedFiles) {
if(String key = keyForAttachedFile(attachedFile);
!key.isEmpty() && !keys.contains(key)) {
keys.append(key);
}
}
}
if(d->chapters && !d->chapters->chapterEditionList().isEmpty()) {
keys.append("CHAPTERS");
}
return keys;
}
List<VariantMap> Matroska::File::complexProperties(const String &key) const
{
List<VariantMap> props = TagLib::File::complexProperties(key);
if(key.upper() == "CHAPTERS") {
if(d->chapters) {
for(const auto &edition : d->chapters->chapterEditionList()) {
VariantMap property;
if(const auto uid = edition.uid()) {
property.insert("uid", uid);
}
if(const auto isDefault = edition.isDefault()) {
property.insert("isDefault", isDefault);
}
if(const auto isOrdered = edition.isOrdered()) {
property.insert("isOrdered", isOrdered);
}
if(auto chapters = edition.chapterList(); !chapters.isEmpty()) {
VariantList chaps;
for(const auto &chapter : chapters) {
VariantMap chap;
if(const auto uid = chapter.uid()) {
chap.insert("uid", uid);
}
if(const auto isHidden = chapter.isHidden()) {
chap.insert("isHidden", isHidden);
}
chap.insert("timeStart", chapter.timeStart());
if(const auto timeEnd = chapter.timeEnd()) {
chap.insert("timeEnd", timeEnd);
}
if(auto displays = chapter.displayList(); !displays.isEmpty()) {
VariantList disps;
for(const auto &display : displays) {
VariantMap disp;
if(auto str = display.string(); !str.isEmpty()) {
disp.insert("string", str);
}
if(auto language = display.language(); !language.isEmpty()) {
disp.insert("language", language);
}
disps.append(disp);
}
chap.insert("displays", disps);
}
chaps.append(chap);
}
property.insert("chapters", chaps);
}
props.append(property);
}
}
}
if(d->attachments) {
const auto &attachedFiles = d->attachments->attachedFileList();
for(const auto &attachedFile : attachedFiles) {
if(keyMatchesAttachedFile(key, attachedFile)) {
VariantMap property;
property.insert("data", attachedFile.data());
property.insert("mimeType", attachedFile.mediaType());
property.insert("description", attachedFile.description());
property.insert("fileName", attachedFile.fileName());
property.insert("uid", attachedFile.uid());
props.append(property);
}
}
}
return props;
}
bool Matroska::File::setComplexProperties(const String &key, const List<VariantMap> &value)
{
if(TagLib::File::setComplexProperties(key, value)) {
return true;
}
if(key.upper() == "CHAPTERS") {
chapters(true)->clear();
for(const auto &ed : value) {
List<Chapter> editionChapters;
const auto chaps = ed.value("chapters").toList();
for(const auto &chapVar : chaps) {
auto chap = chapVar.toMap();
const auto disps = chap.value("displays").toList();
List<Chapter::Display> chapterDisplays;
for(const auto &dispVar : disps) {
auto disp = dispVar.toMap();
chapterDisplays.append(Chapter::Display(
disp.value("string").toString(),
disp.value("language").toString()));
}
editionChapters.append(Chapter(
chap.value("timeStart").toULongLong(),
chap.value("timeEnd").toULongLong(),
chapterDisplays,
chap.value("uid", 0ULL).toULongLong(),
chap.value("isHidden", false).toBool()));
}
d->chapters->addChapterEdition(ChapterEdition(
editionChapters,
ed.value("isDefault", false).toBool(),
ed.value("isOrdered", false).toBool(),
ed.value("uid", 0ULL).toULongLong()));
}
return true;
}
List<AttachedFile> &files = attachments(true)->attachedFiles();
for(auto it = files.begin(); it != files.end();) {
if(keyMatchesAttachedFile(key, *it)) {
it = files.erase(it);
}
else {
++it;
}
}
for(const auto &property : value) {
if(property.isEmpty())
continue;
auto mimeType = property.value("mimeType").value<String>();
auto data = property.value("data").value<ByteVector>();
auto fileName = property.value("fileName").value<String>();
auto uid = property.value("uid").value<unsigned long long>();
bool ok;
if(key.upper() == "PICTURE" && !mimeType.startsWith("image/")) {
mimeType = data.startsWith("\x89PNG\x0d\x0a\x1a\x0a")
? "image/png" : "image/jpeg";
}
else if(mimeType.isEmpty() && key.find("/") != -1) {
mimeType = key;
}
else if(fileName.isEmpty() && key.find(".") != -1) {
fileName = key;
}
else if(unsigned long long uidKey;
!uid && ((uidKey = key.toULongLong(&ok))) && ok) {
uid = uidKey;
}
if(fileName.isEmpty() && !mimeType.isEmpty()) {
const int slashPos = mimeType.rfind('/');
String ext = mimeType.substr(slashPos + 1);
if(ext == "jpeg") {
ext = "jpg";
}
fileName = "attachment." + ext;
}
if(!mimeType.isEmpty() && !fileName.isEmpty()) {
d->attachments->addAttachedFile(AttachedFile(
data, fileName, mimeType, uid,
property.value("description").value<String>()));
}
}
return true;
}
Matroska::Attachments *Matroska::File::attachments(bool create) const
{
if(!d->attachments && create)
d->attachments = std::make_unique<Attachments>();
return d->attachments.get();
}
Matroska::Chapters *Matroska::File::chapters(bool create) const
{
if(!d->chapters && create)
d->chapters = std::make_unique<Chapters>();
return d->chapters.get();
}
void Matroska::File::read(bool readProperties, Properties::ReadStyle readStyle)
{
const offset_t fileLength = length();
// Find the EBML Header
const auto head = EBML::element_cast<EBML::Element::Id::EBMLHeader>(
EBML::Element::factory(*this));
if(!head || head->getId() != EBML::Element::Id::EBMLHeader) {
debug("Failed to find EBML head");
setValid(false);
return;
}
if(readProperties) {
head->read(*this);
}
else {
head->skipData(*this);
}
// Find the Matroska segment in the file
const std::unique_ptr<EBML::MkSegment> segment(
EBML::element_cast<EBML::Element::Id::MkSegment>(
EBML::findElement(*this, EBML::Element::Id::MkSegment, fileLength - tell())
)
);
if(!segment) {
debug("Failed to find Matroska segment");
setValid(false);
return;
}
// Read the segment into memory from file
if(!segment->read(*this)) {
debug("Failed to read segment");
setValid(false);
return;
}
// Parse the elements
d->segment = segment->parseSegment();
d->seekHead = segment->parseSeekHead();
d->cues = segment->parseCues();
d->tag = segment->parseTag();
d->attachments = segment->parseAttachments();
d->chapters = segment->parseChapters();
if(readProperties) {
d->properties = std::make_unique<Properties>(this);
for(const auto &element : *head) {
if(const auto id = element->getId(); id == EBML::Element::Id::DocType) {
d->properties->setDocType(
EBML::element_cast<EBML::Element::Id::DocType>(element)->getValue());
}
else if(id == EBML::Element::Id::DocTypeVersion) {
d->properties->setDocTypeVersion(static_cast<int>(
EBML::element_cast<EBML::Element::Id::DocTypeVersion>(element)->getValue()));
}
}
segment->parseInfo(d->properties.get());
segment->parseTracks(d->properties.get());
if(d->tag) {
d->tag->setSegmentTitle(d->properties->title());
}
}
if(readStyle == AudioProperties::Accurate &&
((d->seekHead && !d->seekHead->isValid(*this)) ||
(d->cues && !d->cues->isValid(*this)))) {
setValid(false);
return;
}
setValid(true);
}
bool Matroska::File::save()
{
if(readOnly()) {
debug("Matroska::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("Matroska::File::save() -- File is not valid.");
return false;
}
// Do not create new attachments, chapters or tags and corresponding
// seek head entries if only empty objects were created.
if(d->chapters && d->chapters->chapterEditionList().isEmpty() &&
d->chapters->size() == 0 && d->chapters->offset() == 0 &&
d->chapters->data().isEmpty()) {
d->chapters.reset();
}
if(d->attachments && d->attachments->attachedFileList().isEmpty() &&
d->attachments->size() == 0 && d->attachments->offset() == 0 &&
d->attachments->data().isEmpty()) {
d->attachments.reset();
}
if(d->tag && d->tag->isEmpty() &&
d->tag->size() == 0 && d->tag->offset() == 0 &&
d->tag->data().isEmpty()) {
d->tag.reset();
}
List<Element *> renderList;
List<Element *> newElements;
// List of all possible elements we can write
List<Element *> elements {
d->chapters.get(),
d->attachments.get(),
d->tag.get()
};
/* Build render list. New elements will be added
* to the end of the file. For new elements,
* the order is from least likely to change,
* to most likely to change:
* 1. Chapters
* 2. Attachments
* 3. Tags
*/
for(auto element : elements) {
if(!element)
continue;
if(element->size())
renderList.append(element);
else {
element->setOffset(length());
newElements.append(element);
}
}
if(renderList.isEmpty() && newElements.isEmpty())
return true;
auto sortAscending = [](const auto a, const auto b) { return a->offset() < b->offset(); };
renderList.sort(sortAscending);
renderList.append(newElements);
// Add our new elements to the Seek Head (if the file has one)
if(d->seekHead) {
const auto segmentDataOffset = d->segment->dataOffset();
for(const auto element : newElements)
d->seekHead->addEntry(element->id(), element->offset() - segmentDataOffset);
d->seekHead->sort();
}
// Set up listeners, add seek head and segment length to the end
for(auto it = renderList.begin(); it != renderList.end(); ++it) {
for(auto it2 = std::next(it); it2 != renderList.end(); ++it2)
(*it)->addSizeListener(*it2);
if(d->cues)
(*it)->addSizeListener(d->cues.get());
if(d->seekHead)
(*it)->addSizeListener(d->seekHead.get());
(*it)->addSizeListener(d->segment.get());
}
if(d->cues) {
renderList.append(d->cues.get());
d->cues->addSizeListeners(renderList);
if(d->seekHead) {
d->cues->addSizeListener(d->seekHead.get());
}
d->cues->addSizeListener(d->segment.get());
}
if(d->seekHead) {
renderList.append(d->seekHead.get());
d->seekHead->addSizeListeners(renderList);
d->seekHead->addSizeListener(d->segment.get());
}
d->segment->addSizeListeners(renderList);
renderList.append(d->segment.get());
// Render the elements.
// Because size changes of elements can cause segment offset updates and
// size changes in other elements, we might need multiple rounds until no more
// element needs rendering.
int renderRound = 0;
bool rendering = true;
while(rendering && renderRound < 5) {
rendering = false;
for(const auto element : renderList) {
if(element->needsRender()) {
rendering = true;
if(!element->render()) {
return false;
}
}
}
++renderRound;
}
// Write out to file
renderList.sort(sortAscending);
for(const auto element : renderList)
element->write(*this);
return true;
}

View File

@@ -0,0 +1,183 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKAFILE_H
#define TAGLIB_MATROSKAFILE_H
#include "taglib_export.h"
#include "tfile.h"
#include "matroskaproperties.h"
namespace TagLib::Matroska {
class Tag;
class Attachments;
class Chapters;
/*!
* Implementation of TagLib::File for Matroska.
*/
class TAGLIB_EXPORT File : public TagLib::File
{
public:
/*!
* Constructs a Matroska file from \a file. If \a readProperties is \c true the
* file's audio properties will also be read.
*
* If \a readStyle is \c Accurate all seek head and cues segment positions
* are verified for the isValid() state of the file.
*/
explicit File(FileName file, bool readProperties = true,
Properties::ReadStyle readStyle = Properties::Average);
/*!
* Constructs a Matroska file from \a stream. If \a readProperties is \c true the
* file's audio properties will also be read.
*
* If \a readStyle is \c Accurate all seek head and cues segment positions
* are verified for the isValid() state of the file.
*/
explicit File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle readStyle = Properties::Average);
/*!
* Destroys this instance of the File.
*/
~File() override;
File(const File &) = delete;
File &operator=(const File &) = delete;
/*!
* Returns a pointer to the tag of the file.
*
* It will create a tag if one does not exist and returns a valid pointer.
*
* \note The tag <b>is still</b> owned by the Matroska::File and should not
* be deleted by the user. It will be deleted when the file (object) is
* destroyed.
*/
TagLib::Tag *tag() const override;
/*!
* Returns a pointer to the Matroska tag of the file.
*
* If \a create is \c false this may return a null pointer if there is no tag.
* If \a create is \c true it will create a tag if one does not exist and
* returns a valid pointer.
*
* \note The tag <b>is still</b> owned by the Matroska::File and should not
* be deleted by the user. It will be deleted when the file (object)
* destroyed.
*/
Tag *tag(bool create) const;
/*!
* Implements the reading part of the unified property interface.
*/
PropertyMap properties() const override;
void removeUnsupportedProperties(const StringList &properties) override;
/*!
* Implements the writing part of the unified tag dictionary interface.
*/
PropertyMap setProperties(const PropertyMap &) override;
/*!
* Returns the keys for attached files, "PICTURE" for images, the media
* type, file name or UID for other attached files.
* The names of the binary simple tags are included too.
*/
StringList complexPropertyKeys() const override;
/*!
* Get the pictures stored in the attachments as complex properties
* for \a key "PICTURE". Other attached files can be retrieved, by
* media type, file name or UID.
* The attached files are returned as maps with keys "data", "mimeType",
* "description", "fileName, "uid".
* Binary simple tags can be retrieved as maps with keys "data", "name",
* "targetTypeValue", "language", "defaultLanguage".
*/
List<VariantMap> complexProperties(const String &key) const override;
/*!
* Set attached files as complex properties \a value, e.g. pictures for
* \a key "PICTURE" with the maps in \a value having keys "data", "mimeType",
* "description", "fileName, "uid". For other attached files, the mime type,
* file name or UID can be used as the \a key.
* Maps with keys "name" (with the same value as \a key) and "data" are
* stored as binary simple tags with additional keys "targetTypeValue",
* "language", "defaultLanguage".
*/
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
/*!
* Returns the Matroska::Properties for this file. If no audio properties
* were read then this will return a null pointer.
*/
Properties *audioProperties() const override;
/*!
* Save the file.
*
* This returns \c true if the save was successful.
*/
bool save() override;
/*!
* Returns a pointer to the attachments of the file.
*
* If \a create is \c false this may return a null pointer if there are no
* attachments.
* If \a create is \c true it will create attachments if none exist and
* returns a valid pointer.
*/
Attachments *attachments(bool create = false) const;
/*!
* Returns a pointer to the chapters of the file.
*
* If \a create is \c false this may return a null pointer if there are no
* chapters.
* If \a create is \c true it will create chapters if none exist and
* returns a valid pointer.
*/
Chapters *chapters(bool create = false) const;
/*!
* Returns whether or not the given \a stream can be opened as a Matroska
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
void read(bool readProperties, Properties::ReadStyle readStyle);
class FilePrivate;
friend class Properties;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<FilePrivate> d;
};
}
#endif

View File

@@ -0,0 +1,155 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskaproperties.h"
#include "matroskafile.h"
using namespace TagLib;
class Matroska::Properties::PropertiesPrivate
{
public:
explicit PropertiesPrivate(File *file) : file(file) {}
~PropertiesPrivate() = default;
PropertiesPrivate(const PropertiesPrivate &) = delete;
PropertiesPrivate &operator=(const PropertiesPrivate &) = delete;
File *file;
String codecName;
String title;
String docType;
int docTypeVersion { 0 };
int length { 0 };
int bitrate { -1 };
int sampleRate { 0 };
int channels { 0 };
int bitsPerSample { 0 };
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Matroska::Properties::Properties(File *file, ReadStyle style) :
AudioProperties(style),
d(std::make_unique<PropertiesPrivate>(file))
{
}
Matroska::Properties::~Properties() = default;
int Matroska::Properties::lengthInMilliseconds() const
{
return d->length;
}
int Matroska::Properties::bitrate() const
{
if(d->bitrate == -1) {
d->bitrate = d->length != 0 ? static_cast<int>(d->file->length() * 8 / d->length) : 0;
}
return d->bitrate;
}
int Matroska::Properties::sampleRate() const
{
return d->sampleRate;
}
int Matroska::Properties::channels() const
{
return d->channels;
}
int Matroska::Properties::bitsPerSample() const
{
return d->bitsPerSample;
}
String Matroska::Properties::docType() const
{
return d->docType;
}
int Matroska::Properties::docTypeVersion() const
{
return d->docTypeVersion;
}
String Matroska::Properties::codecName() const
{
return d->codecName;
}
String Matroska::Properties::title() const
{
return d->title;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void Matroska::Properties::setLengthInMilliseconds(int length)
{
d->length = length;
}
void Matroska::Properties::setSampleRate(int sampleRate)
{
d->sampleRate = sampleRate;
}
void Matroska::Properties::setChannels(int channels)
{
d->channels = channels;
}
void Matroska::Properties::setBitsPerSample(int bitsPerSample)
{
d->bitsPerSample = bitsPerSample;
}
void Matroska::Properties::setDocType(const String &docType)
{
d->docType = docType;
}
void Matroska::Properties::setDocTypeVersion(int docTypeVersion)
{
d->docTypeVersion = docTypeVersion;
}
void Matroska::Properties::setCodecName(const String &codecName)
{
d->codecName = codecName;
}
void Matroska::Properties::setTitle(const String &title)
{
d->title = title;
}

View File

@@ -0,0 +1,127 @@
/***************************************************************************
copyright : (C) 2025 by Urs Fleisch
email : ufleisch@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKAPROPERTIES_H
#define TAGLIB_MATROSKAPROPERTIES_H
#include "taglib_export.h"
#include "audioproperties.h"
namespace TagLib::EBML {
class MkTracks;
class MkInfo;
}
namespace TagLib::Matroska {
class File;
//! An implementation of Matroska audio properties
class TAGLIB_EXPORT Properties : public AudioProperties
{
public:
/*!
* Creates an instance of Matroska::Properties.
*/
explicit Properties(File *file, ReadStyle style = Average);
/*!
* Destroys this Matroska::Properties instance.
*/
~Properties() override;
Properties(const Properties &) = delete;
Properties &operator=(const Properties &) = delete;
/*!
* Returns the length of the file in milliseconds.
*
* \see lengthInSeconds()
*/
int lengthInMilliseconds() const override;
/*!
* Returns the average bit rate of the file in kb/s.
*/
int bitrate() const override;
/*!
* Returns the sample rate in Hz.
*/
int sampleRate() const override;
/*!
* Returns the number of audio channels.
*/
int channels() const override;
/*!
* Returns the number of bits per audio sample.
*/
int bitsPerSample() const;
/*!
* Returns the EBML doc type, "matroska" or "webm".
*/
String docType() const;
/*!
* Returns the EBML doc type version, typical values are 2 or 4.
*/
int docTypeVersion() const;
/*!
* Returns the concrete codec name, for example "A_MPEG/L3"
* used in the file if available, otherwise an empty string.
*/
String codecName() const;
/*!
* Returns the general name of the segment.
* Some applications store the title of the file here, but players should
* prioritize the tag title over the segment title.
*/
String title() const;
private:
class PropertiesPrivate;
friend class EBML::MkInfo;
friend class EBML::MkTracks;
friend class File;
void setLengthInMilliseconds(int length);
void setSampleRate(int sampleRate);
void setChannels(int channels);
void setBitsPerSample(int bitsPerSample);
void setDocType(const String &docType);
void setDocTypeVersion(int docTypeVersion);
void setCodecName(const String &codecName);
void setTitle(const String &title);
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<PropertiesPrivate> d;
};
}
#endif

View File

@@ -0,0 +1,133 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskaseekhead.h"
#include "ebmlmkseekhead.h"
#include "ebmlbinaryelement.h"
#include "ebmluintelement.h"
#include "ebmlmasterelement.h"
#include "tfile.h"
#include "tutils.h"
#include "tdebug.h"
using namespace TagLib;
Matroska::SeekHead::SeekHead(offset_t segmentDataOffset) :
Element(static_cast<ID>(EBML::Element::Id::MkSeekHead)),
segmentDataOffset(segmentDataOffset)
{
setNeedsRender(false);
}
Matroska::SeekHead::~SeekHead() = default;
bool Matroska::SeekHead::isValid(TagLib::File &file) const
{
bool result = true;
for(const auto &[id, offset] : entries) {
file.seek(segmentDataOffset + offset);
if(EBML::Element::readId(file) != id) {
debug(Utils::formatString("No ID %x found at seek position", id));
result = false;
}
}
return result;
}
void Matroska::SeekHead::addEntry(const Element &element)
{
entries.append({element.id(), element.offset()});
debug("adding to seekhead");
setNeedsRender(true);
}
void Matroska::SeekHead::addEntry(ID id, offset_t offset)
{
entries.append({id, offset});
setNeedsRender(true);
}
ByteVector Matroska::SeekHead::renderInternal()
{
const auto beforeSize = sizeRenderedOrWritten();
EBML::MkSeekHead seekHead;
seekHead.setMinRenderSize(beforeSize);
for(const auto &[id, position] : entries) {
auto seekElement = EBML::make_unique_element<EBML::Element::Id::MkSeek>();
auto idElement = EBML::make_unique_element<EBML::Element::Id::MkSeekID>();
idElement->setValue(ByteVector::fromUInt(id, true));
seekElement->appendElement(std::move(idElement));
auto positionElement = EBML::make_unique_element<EBML::Element::Id::MkSeekPosition>();
positionElement->setValue(static_cast<unsigned long long>(position));
seekElement->appendElement(std::move(positionElement));
seekHead.appendElement(std::move(seekElement));
}
return seekHead.render();
}
void Matroska::SeekHead::write(File &file)
{
if(!data().isEmpty())
Element::write(file);
}
void Matroska::SeekHead::sort()
{
entries.sort([](const auto &a, const auto &b) { return a.second < b.second; });
}
bool Matroska::SeekHead::sizeChanged(Element &caller, offset_t delta)
{
ID callerID = caller.id();
if(callerID == static_cast<ID>(EBML::Element::Id::MkSegment)) {
adjustOffset(delta);
return true;
}
// The equal case is needed when multiple new elements are added
// (e.g. Attachments and Tags), they will start with the same offset
// and are updated via size change handling.
offset_t offset = caller.offset() - segmentDataOffset;
auto it = entries.begin();
while(it != entries.end()) {
it = std::find_if(it,
entries.end(),
[offset, callerID](const auto &a) {
return a.second >= offset && a.first != callerID;
}
);
if(it != entries.end()) {
it->second += delta;
setNeedsRender(true);
++it;
}
}
if(caller.data().isEmpty() && caller.size() + delta == 0) {
// The caller element is removed, remove it from the seek head.
it = std::find_if(entries.begin(), entries.end(),
[callerID](const auto &a){ return a.first == callerID; });
if(it != entries.end()) {
entries.erase(it);
}
}
return true;
}

View File

@@ -0,0 +1,55 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKASEEKHEAD_H
#define TAGLIB_MATROSKASEEKHEAD_H
#ifndef DO_NOT_DOCUMENT
#include "matroskaelement.h"
#include "tlist.h"
namespace TagLib {
class File;
class ByteVector;
namespace Matroska {
class SeekHead : public Element
{
public:
explicit SeekHead(offset_t segmentDataOffset);
~SeekHead() override;
bool isValid(TagLib::File &file) const;
void addEntry(const Element &element);
void addEntry(ID id, offset_t offset);
void write(TagLib::File &file) override;
void sort();
bool sizeChanged(Element &caller, offset_t delta) override;
private:
ByteVector renderInternal() override;
List<std::pair<unsigned int, offset_t>> entries;
const offset_t segmentDataOffset;
};
}
}
#endif
#endif

View File

@@ -0,0 +1,71 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskasegment.h"
#include "ebmlutils.h"
#include "tbytevector.h"
using namespace TagLib;
Matroska::Segment::Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset) :
Element(static_cast<ID>(EBML::Element::Id::MkSegment)),
sizeLength(sizeLength), dataSize(dataSize)
{
setOffset(lengthOffset);
setSize(sizeLength);
}
Matroska::Segment::~Segment() = default;
ByteVector Matroska::Segment::renderInternal()
{
return EBML::renderVINT(dataSize, static_cast<int>(sizeLength));
}
bool Matroska::Segment::render()
{
const auto beforeSize = sizeLength;
auto data = renderInternal();
setNeedsRender(false);
if(auto afterSize = data.size(); afterSize != beforeSize) {
sizeLength = 8;
data = renderInternal();
setNeedsRender(false);
afterSize = data.size();
if(!emitSizeChanged(afterSize - beforeSize)) {
return false;
}
}
setData(data);
return true;
}
bool Matroska::Segment::sizeChanged(Element &, offset_t delta)
{
dataSize += delta;
setNeedsRender(true);
return true;
}
offset_t Matroska::Segment::dataOffset() const
{
return offset() + sizeLength;
}

View File

@@ -0,0 +1,46 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKASEGMENT_H
#define TAGLIB_MATROSKASEGMENT_H
#ifndef DO_NOT_DOCUMENT
#include "matroskaelement.h"
namespace TagLib::Matroska {
class Segment : public Element
{
public:
Segment(offset_t sizeLength, offset_t dataSize, offset_t lengthOffset);
~Segment() override;
bool render() override;
bool sizeChanged(Element &caller, offset_t delta) override;
offset_t dataOffset() const;
private:
ByteVector renderInternal() override;
offset_t sizeLength;
offset_t dataSize;
};
}
#endif
#endif

View File

@@ -0,0 +1,142 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskasimpletag.h"
#include <variant>
#include "matroskatag.h"
#include "tbytevector.h"
using namespace TagLib;
class Matroska::SimpleTag::SimpleTagPrivate
{
public:
SimpleTagPrivate(const String &name, const String &value,
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage,
unsigned long long trackUid) :
value(value), name(name), language(language), trackUid(trackUid),
targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {}
SimpleTagPrivate(const String &name, const ByteVector &value,
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage,
unsigned long long trackUid) :
value(value), name(name), language(language), trackUid(trackUid),
targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {}
const std::variant<String, ByteVector> value;
const String name;
const String language;
const unsigned long long trackUid;
const TargetTypeValue targetTypeValue;
const bool defaultLanguageFlag;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Matroska::SimpleTag::SimpleTag(const String &name, const String &value,
TargetTypeValue targetTypeValue,
const String &language, bool defaultLanguage,
unsigned long long trackUid) :
d(std::make_unique<SimpleTagPrivate>(name, value, targetTypeValue,
language, defaultLanguage, trackUid))
{
}
Matroska::SimpleTag::SimpleTag(const String &name, const ByteVector &value,
TargetTypeValue targetTypeValue,
const String &language, bool defaultLanguage,
unsigned long long trackUid) :
d(std::make_unique<SimpleTagPrivate>(name, value, targetTypeValue,
language, defaultLanguage, trackUid))
{
}
Matroska::SimpleTag::SimpleTag(const SimpleTag &other) :
d(std::make_unique<SimpleTagPrivate>(*other.d))
{
}
Matroska::SimpleTag::SimpleTag(SimpleTag &&other) noexcept = default;
Matroska::SimpleTag::~SimpleTag() = default;
Matroska::SimpleTag &Matroska::SimpleTag::operator=(SimpleTag &&other) noexcept = default;
Matroska::SimpleTag &Matroska::SimpleTag::operator=(const SimpleTag &other)
{
SimpleTag(other).swap(*this);
return *this;
}
void Matroska::SimpleTag::swap(SimpleTag &other) noexcept
{
using std::swap;
swap(d, other.d);
}
Matroska::SimpleTag::TargetTypeValue Matroska::SimpleTag::targetTypeValue() const
{
return d->targetTypeValue;
}
const String &Matroska::SimpleTag::name() const
{
return d->name;
}
const String &Matroska::SimpleTag::language() const
{
return d->language;
}
bool Matroska::SimpleTag::defaultLanguageFlag() const
{
return d->defaultLanguageFlag;
}
unsigned long long Matroska::SimpleTag::trackUid() const
{
return d->trackUid;
}
Matroska::SimpleTag::ValueType Matroska::SimpleTag::type() const
{
return std::holds_alternative<ByteVector>(d->value) ? BinaryType : StringType;
}
String Matroska::SimpleTag::toString() const
{
if(std::holds_alternative<String>(d->value)) {
// get_if() used instead of get() to support restricted compilers
return *std::get_if<String>(&d->value);
}
return {};
}
ByteVector Matroska::SimpleTag::toByteVector() const
{
if(std::holds_alternative<ByteVector>(d->value)) {
// get_if() used instead of get() to support restricted compilers
return *std::get_if<ByteVector>(&d->value);
}
return {};
}

View File

@@ -0,0 +1,151 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKASIMPLETAG_H
#define TAGLIB_MATROSKASIMPLETAG_H
#include <memory>
#include "tstring.h"
namespace TagLib {
class String;
class ByteVector;
namespace Matroska {
//! Attribute of Matroska metadata.
class TAGLIB_EXPORT SimpleTag
{
public:
//! Specifies the level of other elements the tag value applies to.
enum TargetTypeValue {
None = 0, //!< Empty or omitted, everything in the segment
Shot = 10, //!< Shot
Subtrack = 20, //!< Subtrack / movement / scene
Track = 30, //!< Track / song / chapter
Part = 40, //!< Part / session
Album = 50, //!< Album / opera / concert / movie / episode
Edition = 60, //!< Edition / issue / volume / opus / season / sequel
Collection = 70 //!< Collection
};
//! The types the value can have.
enum ValueType {
StringType = 0, //!< Item contains text information coded in UTF-8
BinaryType = 1 //!< Item contains binary information
};
/*!
* Construct a string simple tag.
*/
SimpleTag(const String &name, const String &value,
TargetTypeValue targetTypeValue = None,
const String &language = String(), bool defaultLanguage = true,
unsigned long long trackUid = 0);
/*!
* Construct a binary simple tag.
*/
SimpleTag(const String &name, const ByteVector &value,
TargetTypeValue targetTypeValue = None,
const String &language = String(), bool defaultLanguage = true,
unsigned long long trackUid = 0);
/*!
* Construct a simple tag as a copy of \a other.
*/
SimpleTag(const SimpleTag &other);
/*!
* Construct a simple tag moving from \a other.
*/
SimpleTag(SimpleTag &&other) noexcept;
/*!
* Destroys this simple tag.
*/
~SimpleTag();
/*!
* Copies the contents of \a other into this item.
*/
SimpleTag &operator=(const SimpleTag &other);
/*!
* Moves the contents of \a other into this item.
*/
SimpleTag &operator=(SimpleTag &&other) noexcept;
/*!
* Exchanges the content of the simple tag with the content of \a other.
*/
void swap(SimpleTag &other) noexcept;
/*!
* Returns the name of the simple tag.
*/
const String &name() const;
/*!
* Returns the logical level of the target.
*/
TargetTypeValue targetTypeValue() const;
/*!
* Returns the language of the tag.
*/
const String &language() const;
/*!
* Returns if this is the default/original language to use for the tag.
*/
bool defaultLanguageFlag() const;
/*!
* Returns the UID that identifies the track that the tags belong to,
* zero if not defined, the tag applies to all tracks
*/
unsigned long long trackUid() const;
/*!
* Returns the type of the value.
*/
ValueType type() const;
/*!
* Returns the StringType value.
*/
String toString() const;
/*!
* Returns the BinaryType value.
*/
ByteVector toByteVector() const;
private:
class SimpleTagPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<SimpleTagPrivate> d;
};
}
}
#endif

View File

@@ -0,0 +1,604 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include "matroskatag.h"
#include <algorithm>
#include <array>
#include <tuple>
#include "ebmlmasterelement.h"
#include "ebmlstringelement.h"
#include "ebmlbinaryelement.h"
#include "ebmlmktags.h"
#include "ebmluintelement.h"
#include "ebmlutils.h"
#include "tpropertymap.h"
using namespace TagLib;
class Matroska::Tag::TagPrivate
{
public:
TagPrivate() = default;
~TagPrivate() = default;
bool setTag(const String &key, const String &value);
String getTag(const String &key) const;
template <typename T>
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<T>(p));
if(it != list.end()) {
it = list.erase(it);
numRemoved++;
}
}
return numRemoved;
}
template <typename T>
SimpleTagsList findSimpleTags(T &&p)
{
auto &list = tags;
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;
}
SimpleTagsList tags;
ByteVector data;
String segmentTitle;
};
Matroska::Tag::Tag() :
Element(static_cast<ID>(EBML::Element::Id::MkTags)),
d(std::make_unique<TagPrivate>())
{
}
Matroska::Tag::~Tag() = default;
void Matroska::Tag::addSimpleTag(const SimpleTag &tag)
{
d->tags.append(tag);
setNeedsRender(true);
}
void Matroska::Tag::addSimpleTags(const SimpleTagsList& simpleTags)
{
d->tags.append(simpleTags);
setNeedsRender(true);
}
void Matroska::Tag::insertSimpleTag(unsigned int index, const SimpleTag &tag)
{
if(index < d->tags.size()) {
auto it = d->tags.begin();
std::advance(it, index);
d->tags.insert(it, tag);
}
else {
d->tags.append(tag);
}
setNeedsRender(true);
}
void Matroska::Tag::removeSimpleTag(unsigned int index)
{
if(index < d->tags.size()) {
auto it = d->tags.begin();
std::advance(it, index);
d->tags.erase(it);
setNeedsRender(true);
}
}
void Matroska::Tag::removeSimpleTag(const String &name,
SimpleTag::TargetTypeValue targetTypeValue,
unsigned long long trackUid)
{
const auto it = std::find_if(d->tags.begin(), d->tags.end(),
[&name, targetTypeValue, trackUid](const SimpleTag &t) {
return t.name() == name && t.targetTypeValue() == targetTypeValue &&
t.trackUid() == trackUid;
}
);
if(it != d->tags.end()) {
d->tags.erase(it);
setNeedsRender(true);
}
}
void Matroska::Tag::clearSimpleTags()
{
d->tags.clear();
setNeedsRender(true);
}
const Matroska::SimpleTagsList &Matroska::Tag::simpleTagsList() const
{
return d->tags;
}
void Matroska::Tag::setSegmentTitle(const String &title)
{
d->segmentTitle = title;
}
bool Matroska::Tag::setTag(const String &key, const String &value)
{
const bool found = d->setTag(key, value);
if(found) {
setNeedsRender(true);
}
return found;
}
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", i != 0 ? String::number(i) : String());
}
void Matroska::Tag::setTrack(unsigned int i)
{
setTag("TRACKNUMBER", i != 0 ? String::number(i) : String());
}
String Matroska::Tag::title() const
{
String s = d->getTag("TITLE");
if(s.isEmpty()) {
return d->segmentTitle;
}
return s;
}
String Matroska::Tag::artist() const
{
return d->getTag("ARTIST");
}
String Matroska::Tag::album() const
{
return d->getTag("ALBUM");
}
String Matroska::Tag::comment() const
{
return d->getTag("COMMENT");
}
String Matroska::Tag::genre() const
{
return d->getTag("GENRE");
}
unsigned int Matroska::Tag::year() const
{
const auto value = d->getTag("DATE");
if(value.isEmpty())
return 0;
auto list = value.split("-");
return static_cast<unsigned int>(list.front().toInt());
}
unsigned int Matroska::Tag::track() const
{
const auto value = d->getTag("TRACKNUMBER");
if(value.isEmpty())
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::renderInternal()
{
if(d->tags.isEmpty()) {
// Avoid writing a Tags element without Tag element.
return {};
}
EBML::MkTags tags;
List<SimpleTagsList> targetList;
// Build target-based list
for(const auto &tag : std::as_const(d->tags)) {
auto targetTypeValue = tag.targetTypeValue();
auto trackUid = tag.trackUid();
auto it = std::find_if(targetList.begin(),
targetList.end(),
[&](const auto &list) {
const auto &simpleTag = list.front();
return simpleTag.targetTypeValue() == targetTypeValue &&
simpleTag.trackUid() == trackUid;
}
);
if(it == targetList.end()) {
SimpleTagsList list;
list.append(tag);
targetList.append(list);
}
else
it->append(tag);
}
for(const auto &list : targetList) {
const auto &frontTag = list.front();
const auto targetTypeValue = frontTag.targetTypeValue();
const auto trackUid = frontTag.trackUid();
auto tag = EBML::make_unique_element<EBML::Element::Id::MkTag>();
// Build <Tag Targets> element
auto targets = EBML::make_unique_element<EBML::Element::Id::MkTagTargets>();
if(targetTypeValue != SimpleTag::TargetTypeValue::None) {
auto element = EBML::make_unique_element<EBML::Element::Id::MkTagTargetTypeValue>();
element->setValue(static_cast<unsigned int>(targetTypeValue));
targets->appendElement(std::move(element));
}
if(trackUid != 0) {
auto element = EBML::make_unique_element<EBML::Element::Id::MkTagTrackUID>();
element->setValue(trackUid);
targets->appendElement(std::move(element));
}
tag->appendElement(std::move(targets));
// Build <Simple Tag> element
for(const auto &simpleTag : list) {
auto t = EBML::make_unique_element<EBML::Element::Id::MkSimpleTag>();
auto tagName = EBML::make_unique_element<EBML::Element::Id::MkTagName>();
tagName->setValue(simpleTag.name());
t->appendElement(std::move(tagName));
// Tag Value
if(simpleTag.type() == SimpleTag::StringType) {
auto tagValue = EBML::make_unique_element<EBML::Element::Id::MkTagString>();
tagValue->setValue(simpleTag.toString());
t->appendElement(std::move(tagValue));
}
else if(simpleTag.type() == SimpleTag::BinaryType) {
auto tagValue = EBML::make_unique_element<EBML::Element::Id::MkTagBinary>();
tagValue->setValue(simpleTag.toByteVector());
t->appendElement(std::move(tagValue));
}
// Language
auto language = EBML::make_unique_element<EBML::Element::Id::MkTagsTagLanguage>();
const String &lang = simpleTag.language();
language->setValue(!lang.isEmpty() ? lang : "und");
t->appendElement(std::move(language));
// Default language flag
auto dlf = EBML::make_unique_element<EBML::Element::Id::MkTagsLanguageDefault>();
dlf->setValue(simpleTag.defaultLanguageFlag() ? 1 : 0);
t->appendElement(std::move(dlf));
tag->appendElement(std::move(t));
}
tags.appendElement(std::move(tag));
}
return tags.render();
}
namespace
{
// PropertyMap key, Tag name, Target type value, strict
// 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
// unless they shall have priority over higher level tags with the same name
// when no target type value is given. The strict boolean marks
// entries which shall not be mapped without correct target type value.
// For offical tags, see https://www.matroska.org/technical/tagging.html
constexpr std::array simpleTagsTranslation {
std::tuple("TITLE", "TITLE", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("ALBUM", "TITLE", Matroska::SimpleTag::TargetTypeValue::Album, true),
std::tuple("ARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("ALBUMARTIST", "ARTIST", Matroska::SimpleTag::TargetTypeValue::Album, true),
std::tuple("TRACKNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("DISCNUMBER", "PART_NUMBER", Matroska::SimpleTag::TargetTypeValue::Album, true),
std::tuple("TRACKTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("DISCTOTAL", "TOTAL_PARTS", Matroska::SimpleTag::TargetTypeValue::Album, true),
std::tuple("DATE", "DATE_RECORDED", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("TITLESORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("ALBUMSORT", "TITLESORT", Matroska::SimpleTag::TargetTypeValue::Album, true),
std::tuple("ARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("ALBUMARTISTSORT", "ARTISTSORT", Matroska::SimpleTag::TargetTypeValue::Album, true),
std::tuple("MEDIA", "ORIGINAL_MEDIA_TYPE", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("LABEL", "LABEL_CODE", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("CATALOGNUMBER", "CATALOG_NUMBER", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("DJMIXER", "MIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("REMIXER", "REMIXED_BY", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("INITIALKEY", "INITIAL_KEY", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("RELEASEDATE", "DATE_RELEASED", Matroska::SimpleTag::TargetTypeValue::Album, false),
std::tuple("ENCODINGTIME", "DATE_ENCODED", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("TAGGINGDATE", "DATE_TAGGED", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("ENCODEDBY", "ENCODER", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("ENCODING", "ENCODER_SETTINGS", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("OWNER", "PURCHASE_OWNER", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_GAIN", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("REPLAYGAIN_ALBUM_GAIN", "REPLAYGAIN_GAIN", Matroska::SimpleTag::TargetTypeValue::Album, true),
std::tuple("REPLAYGAIN_TRACK_PEAK", "REPLAYGAIN_PEAK", Matroska::SimpleTag::TargetTypeValue::Track, false),
std::tuple("REPLAYGAIN_ALBUM_PEAK", "REPLAYGAIN_PEAK", Matroska::SimpleTag::TargetTypeValue::Album, true),
std::tuple("MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMARTISTID", Matroska::SimpleTag::TargetTypeValue::Album, false),
std::tuple("MUSICBRAINZ_ALBUMID", "MUSICBRAINZ_ALBUMID", Matroska::SimpleTag::TargetTypeValue::Album, false),
std::tuple("MUSICBRAINZ_RELEASEGROUPID", "MUSICBRAINZ_RELEASEGROUPID", Matroska::SimpleTag::TargetTypeValue::Album, false),
};
std::tuple<String, Matroska::SimpleTag::TargetTypeValue, bool> 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), std::get<3>(*it) };
if(!key.isEmpty())
return { key, Matroska::SimpleTag::TargetTypeValue::Track, false };
return { String(), Matroska::SimpleTag::TargetTypeValue::None, false };
}
String translateTag(const String &name, Matroska::SimpleTag::TargetTypeValue targetTypeValue)
{
const 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) ||
(targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None
&& !std::get<3>(t)));
}
);
return it != simpleTagsTranslation.end()
? String(std::get<0>(*it), String::UTF8)
: targetTypeValue == Matroska::SimpleTag::TargetTypeValue::Track ||
targetTypeValue == Matroska::SimpleTag::TargetTypeValue::None
? name
: String();
}
}
bool Matroska::Tag::TagPrivate::setTag(const String &key, const String &value)
{
const auto tpl = translateKey(key);
// Workaround Clang issue - no lambda capture of structured bindings
const String &name = std::get<0>(tpl);
auto targetTypeValue = std::get<1>(tpl);
if(name.isEmpty())
return false;
removeSimpleTags(
[&name, targetTypeValue] (const auto &t) {
return t.name() == name
&& t.targetTypeValue() == targetTypeValue;
}
);
if(!value.isEmpty()) {
tags.append(SimpleTag(name, value, targetTypeValue));
}
return true;
}
String Matroska::Tag::TagPrivate::getTag(const String &key) const
{
const auto tpl = translateKey(key);
// Workaround Clang issue - no lambda capture of structured bindings
const String &name = std::get<0>(tpl);
auto targetTypeValue = std::get<1>(tpl);
bool strict = std::get<2>(tpl);
if(name.isEmpty())
return {};
const auto it = std::find_if(tags.begin(), tags.end(),
[&name, targetTypeValue, strict] (const SimpleTag &t) {
return t.name() == name
&& t.type() == SimpleTag::StringType
&& (t.targetTypeValue() == targetTypeValue ||
(t.targetTypeValue() == SimpleTag::TargetTypeValue::None && !strict))
&& t.trackUid() == 0;
}
);
return it != tags.end() ? it->toString() : String();
}
PropertyMap Matroska::Tag::properties() const
{
PropertyMap properties;
for(const auto &simpleTag : std::as_const(d->tags)) {
if(simpleTag.type() == SimpleTag::StringType && simpleTag.trackUid() == 0) {
if(String key = translateTag(simpleTag.name(), simpleTag.targetTypeValue());
!key.isEmpty())
properties[key].append(simpleTag.toString());
else
properties.addUnsupportedData(simpleTag.name());
}
}
return properties;
}
PropertyMap Matroska::Tag::setProperties(const PropertyMap &propertyMap)
{
// Remove all simple tags which would be returned in properties()
for(auto it = d->tags.begin(); it != d->tags.end();) {
if(it->type() == SimpleTag::StringType &&
it->trackUid() == 0 &&
!translateTag(it->name(), it->targetTypeValue()).isEmpty()) {
it = d->tags.erase(it);
setNeedsRender(true);
}
else {
++it;
}
}
// Add the new properties
PropertyMap unsupportedProperties;
for(const auto &[key, values] : propertyMap) {
for(const auto &value : values) {
if(auto [name, targetTypeValue, _] = translateKey(key);
!name.isEmpty()) {
d->tags.append(SimpleTag(name, value, targetTypeValue));
setNeedsRender(true);
}
else {
unsupportedProperties[key] = values;
}
}
}
return unsupportedProperties;
}
void Matroska::Tag::removeUnsupportedProperties(const StringList &properties)
{
if(d->removeSimpleTags(
[&properties](const SimpleTag &t) {
return properties.contains(t.name());
}) > 0) {
setNeedsRender(true);
}
}
StringList Matroska::Tag::complexPropertyKeys() const
{
StringList keys;
for(const SimpleTag &t : std::as_const(d->tags)) {
if(t.type() != SimpleTag::StringType ||
t.trackUid() != 0 ||
translateTag(t.name(), t.targetTypeValue()).isEmpty()) {
keys.append(t.name());
}
}
return keys;
}
List<VariantMap> Matroska::Tag::complexProperties(const String &key) const
{
List<VariantMap> props;
if(key.upper() != "PICTURE") { // Pictures are handled at the file level
for(const SimpleTag &t : std::as_const(d->tags)) {
if(t.name() == key &&
(t.type() != SimpleTag::StringType ||
t.trackUid() != 0 ||
translateTag(t.name(), t.targetTypeValue()).isEmpty())) {
VariantMap property;
if(t.type() != SimpleTag::StringType) {
property.insert("data", t.toByteVector());
}
else {
property.insert("value", t.toString());
}
property.insert("name", t.name());
if(t.targetTypeValue() != SimpleTag::TargetTypeValue::None) {
property.insert("targetTypeValue", t.targetTypeValue());
}
if(t.trackUid()) {
property.insert("trackUid", t.trackUid());
}
property.insert("language", t.language());
property.insert("defaultLanguage", t.defaultLanguageFlag());
props.append(property);
}
}
}
return props;
}
bool Matroska::Tag::setComplexProperties(const String &key, const List<VariantMap> &value)
{
if(key.upper() == "PICTURE") {
// Pictures are handled at the file level
return false;
}
if(d->removeSimpleTags(
[&key](const SimpleTag &t) {
return t.name() == key &&
(t.type() != SimpleTag::StringType ||
t.trackUid() != 0 ||
translateTag(t.name(), t.targetTypeValue()).isEmpty());
}) > 0) {
setNeedsRender(true);
}
bool result = false;
for(const auto &property : value) {
if(property.value("name").value<String>() == key &&
(property.contains("data") || property.contains("value") )) {
SimpleTag::TargetTypeValue targetTypeValue;
switch(Variant targetTypeValueVar = property.value("targetTypeValue", 0);
targetTypeValueVar.type()) {
case Variant::UInt:
targetTypeValue = static_cast<SimpleTag::TargetTypeValue>(targetTypeValueVar.value<unsigned int>());
break;
case Variant::LongLong:
targetTypeValue = static_cast<SimpleTag::TargetTypeValue>(targetTypeValueVar.value<long long>());
break;
case Variant::ULongLong:
targetTypeValue = static_cast<SimpleTag::TargetTypeValue>(targetTypeValueVar.value<unsigned long long>());
break;
default:
targetTypeValue = static_cast<SimpleTag::TargetTypeValue>(targetTypeValueVar.value<int>());
}
auto language = property.value("language").value<String>();
const bool defaultLanguage = property.value("defaultLanguage", true).value<bool>();
const auto trackUid = property.value("trackUid", 0ULL).value<unsigned long long>();
d->tags.append(property.contains("data")
? SimpleTag(key, property.value("data").value<ByteVector>(),
targetTypeValue, language, defaultLanguage, trackUid)
: SimpleTag(key, property.value("value").value<String>(),
targetTypeValue, language, defaultLanguage, trackUid));
setNeedsRender(true);
result = true;
}
}
return result;
}

View File

@@ -0,0 +1,150 @@
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_MATROSKATAG_H
#define TAGLIB_MATROSKATAG_H
#include <memory>
#include "tag.h"
#include "tlist.h"
#include "matroskaelement.h"
#include "matroskasimpletag.h"
namespace TagLib {
class File;
namespace EBML {
class MkTags;
}
namespace Matroska {
//! List of tag attributes.
using SimpleTagsList = List<SimpleTag>;
//! Matroska tag implementation.
class TAGLIB_EXPORT Tag : public TagLib::Tag
#ifndef DO_NOT_DOCUMENT
, private Element
#endif
{
public:
/*!
* Constructs a Matroska tag.
*/
Tag();
~Tag() override;
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) 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;
PropertyMap properties() const override;
PropertyMap setProperties(const PropertyMap &propertyMap) override;
void removeUnsupportedProperties(const StringList &properties) override;
/*!
* Returns the names of the binary simple tags.
*/
StringList complexPropertyKeys() const override;
/*!
* Get the binary simple tags as maps with keys "data", "name",
* "targetTypeValue", "language", "defaultLanguage".
* The attached files such as pictures with key "PICTURE" are available
* with Matroska::File::complexProperties().
*/
List<VariantMap> complexProperties(const String &key) const override;
/*!
* Set the binary simple tags as maps with keys "data", "name",
* "targetTypeValue", "language", "defaultLanguage".
* The attached files such as pictures with key "PICTURE" can be set
* with Matroska::File::setComplexProperties().
*
* Returns \c true if \c key can be stored as binary simple tags.
*/
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
/*!
* Add a tag attribute.
*/
void addSimpleTag(const SimpleTag &tag);
/*!
* Add multiple tag attributes.
*/
void addSimpleTags(const SimpleTagsList &simpleTags);
/*!
* Insert a tag attribute at position \a index.
*/
void insertSimpleTag(unsigned int index, const SimpleTag &tag);
/*!
* Remove a tag attribute at position \a index.
*/
void removeSimpleTag(unsigned int index);
/*!
* Remove a tag attribute.
*/
void removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue,
unsigned long long trackUid = 0);
/*!
* Remove all tag attributes.
*/
void clearSimpleTags();
/*!
* Get list of all tag attributes.
*/
const SimpleTagsList &simpleTagsList() const;
private:
friend class File;
friend class EBML::MkTags;
class TagPrivate;
bool setTag(const String &key, const String &value);
void setSegmentTitle(const String &title);
// private Element implementation
ByteVector renderInternal() override;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<TagPrivate> d;
};
}
}
#endif

View File

@@ -6,6 +6,7 @@
#cmakedefine TAGLIB_WITH_APE 1
#cmakedefine TAGLIB_WITH_ASF 1
#cmakedefine TAGLIB_WITH_DSF 1
#cmakedefine TAGLIB_WITH_MATROSKA 1
#cmakedefine TAGLIB_WITH_MOD 1
#cmakedefine TAGLIB_WITH_MP4 1
#cmakedefine TAGLIB_WITH_RIFF 1

View File

@@ -6,107 +6,111 @@
- Vorbis Comments are not included in the table because they use the names from
the *Key* column.
- %APE tags will also use these keys unless another name can be found in its column.
- %Matroska tags will also use these keys unless another name can be found in its column,
additionally, the target level is listed, e.g. /50 for Album, /30 for Track (default).
| Key | %ID3v2 | %RIFF | %MP4 | %APE | %ASF |
| -------------------------- | ------ | ----- | -------------------------------------------------------- | ----------------------- | --------------------------------- |
| ACOUSTID_FINGERPRINT | | | | | Acoustid/Fingerprint |
| ACOUSTID_ID | | | | | Acoustid/Id |
| ALBUM | TALB | IPRD | &copy;alb | | WM/AlbumTitle |
| ALBUMARTIST | TPE2 | | aART | ALBUM ARTIST | WM/AlbumArtist |
| ALBUMARTISTSORT | TSO2 | | soaa | | WM/AlbumArtistSortOrder |
| ALBUMSORT | TSOA | | soal | | WM/AlbumSortOrder |
| ARRANGER | | IENG | | | |
| ARTIST | TPE1 | IART | &copy;ART | | |
| ARTISTS | | | \----:com.apple.iTunes:ARTISTS | | WM/ARTISTS |
| ARTISTSORT | TSOP | | soar | | WM/ArtistSortOrder |
| ARTISTWEBPAGE | WOAR | IBSU | | | WM/AuthorURL |
| ASIN | | | \----:com.apple.iTunes:ASIN | | |
| AUDIOSOURCEWEBPAGE | WOAS | | | | ASIN |
| BARCODE | | | \----:com.apple.iTunes:BARCODE | | WM/Barcode |
| BPM | TBPM | IBPM | tmpo | | WM/BeatsPerMinute |
| CATALOGNUMBER | | | \----:com.apple.iTunes:CATALOGNUMBER | | WM/CatalogNo |
| COMMENT | COMM | ICMT | &copy;cmt | | |
| COMPILATION | TCMP | | cpil | | |
| COMPOSER | TCOM | IMUS | &copy;wrt | | WM/Composer |
| COMPOSERSORT | TSOC | | soco | | |
| CONDUCTOR | TPE3 | | \----:com.apple.iTunes:CONDUCTOR | | WM/Conductor |
| COPYRIGHT | TCOP | ICOP | cprt | | |
| COPYRIGHTURL | WCOP | | | | |
| DATE | TDRC | ICRD | &copy;day | YEAR | WM/Year |
| DISCNUMBER | TPOS | | disk | DISC | WM/PartOfSet |
| DISCSUBTITLE | TSST | PRT1 | \----:com.apple.iTunes:DISCSUBTITLE | | WM/SetSubTitle |
| DJMIXER | | | \----:com.apple.iTunes:DJMIXER | | |
| ENCODEDBY | TENC | ITCH | &copy;enc | | WM/EncodedBy |
| ENCODING | TSSE | ISFT | &copy;too | | WM/EncodingSettings |
| ENCODINGTIME | TDEN | IDIT | | | WM/EncodingTime |
| ENGINEER | | | \----:com.apple.iTunes:ENGINEER | | |
| FILETYPE | TFLT | | | | |
| FILEWEBPAGE | WOAF | | | | WM/AudioFileURL |
| GAPLESSPLAYBACK | | | pgap | | |
| GENRE | TCON | IGNR | &copy;gen | | WM/Genre |
| GROUPING | GRP1 | | &copy;grp | | |
| INITIALKEY | TKEY | | | | WM/InitialKey |
| INVOLVEDPEOPLE | TIPL | | | | |
| ISRC | TSRC | ISRC | \----:com.apple.iTunes:ISRC | | WM/ISRC |
| LABEL | TPUB | IPUB | \----:com.apple.iTunes:LABEL | | WM/Publisher |
| LANGUAGE | TLAN | ILNG | \----:com.apple.iTunes:LANGUAGE | | WM/Language |
| LENGTH | TLEN | | | | |
| LICENSE | | | \----:com.apple.iTunes:LICENSE | | |
| LYRICIST | TEXT | IWRI | \----:com.apple.iTunes:LYRICIST | | WM/Writer |
| LYRICS | USLT | | &copy;lyr | | WM/Lyrics |
| MEDIA | TMED | IMED | \----:com.apple.iTunes:MEDIA | | WM/Media |
| MIXER | | | \----:com.apple.iTunes:MIXER | | |
| MOOD | TMOO | | \----:com.apple.iTunes:MOOD | | WM/Mood |
| MOVEMENTCOUNT | | | &copy;mvc | | |
| MOVEMENTNAME | MVNM | | &copy;mvn | | |
| MOVEMENTNUMBER | MVIN | | &copy;mvi | | |
| MUSICBRAINZ_ALBUMID | | | \----:com.apple.iTunes:MusicBrainz Album Id | | MusicBrainz/Album Id |
| MUSICBRAINZ_ALBUMARTISTID | | | \----:com.apple.iTunes:MusicBrainz Album Artist Id | | MusicBrainz/Album Artist Id |
| MUSICBRAINZ_ARTISTID | | | \----:com.apple.iTunes:MusicBrainz Artist Id | | MusicBrainz/Artist Id |
| MUSICBRAINZ_RELEASEGROUPID | | | \----:com.apple.iTunes:MusicBrainz Release Group Id | | MusicBrainz/Release Group Id |
| MUSICBRAINZ_RELEASETRACKID | | | \----:com.apple.iTunes:MusicBrainz Release Track Id | | MusicBrainz/Release Track Id |
| MUSICBRAINZ_TRACKID | | | \----:com.apple.iTunes:MusicBrainz Track Id | | MusicBrainz/Track Id |
| MUSICBRAINZ_WORKID | | | \----:com.apple.iTunes:MusicBrainz Work Id | | MusicBrainz/Work Id |
| MUSICIANCREDITS | TMCL | | | | |
| MUSICIP_PUID | | | | | MusicIP/PUID |
| ORIGINALALBUM | TOAL | | | | WM/OriginalAlbumTitle |
| ORIGINALARTIST | TOPE | | | | WM/OriginalArtist |
| ORIGINALDATE | TDOR | | \----:com.apple.iTunes:ORIGINALDATE | | WM/OriginalReleaseYear |
| ORIGINALFILENAME | TOFN | | | | WM/OriginalFilename |
| ORIGINALLYRICIST | TOLY | | | | WM/OriginalLyricist |
| OWNER | TOWN | | ownr | | |
| PAYMENTWEBPAGE | WPAY | | | | |
| PERFORMER | | ISTR | | | |
| PLAYLISTDELAY | TDLY | | | | |
| PODCAST | PCST | | pcst | | |
| PODCASTCATEGORY | TCAT | | catg | | |
| PODCASTDESC | TDES | | desc | | |
| PODCASTID | TGID | | egid | | |
| PODCASTURL | WFED | | purl | | |
| PRODUCEDNOTICE | TPRO | | | | |
| PRODUCER | | | \----:com.apple.iTunes:PRODUCER | | WM/Producer |
| PUBLISHERWEBPAGE | WPUB | | | | |
| RADIOSTATION | TRSN | | | | |
| RADIOSTATIONOWNER | TRSO | | | | |
| RADIOSTATIONWEBPAGE | WORS | | | | |
| RELEASECOUNTRY | | ICNT | \----:com.apple.iTunes:MusicBrainz Album Release Country | | MusicBrainz/Album Release Country |
| RELEASEDATE | TDRL | | \----:com.apple.iTunes:RELEASEDATE | | |
| RELEASESTATUS | | | \----:com.apple.iTunes:MusicBrainz Album Status | MUSICBRAINZ_ALBUMSTATUS | MusicBrainz/Album Status |
| RELEASETYPE | | | \----:com.apple.iTunes:MusicBrainz Album Type | MUSICBRAINZ_ALBUMTYPE | MusicBrainz/Album Type |
| REMIXER | TPE4 | IEDT | \----:com.apple.iTunes:REMIXER | MIXARTIST | WM/ModifiedBy |
| SCRIPT | | | \----:com.apple.iTunes:SCRIPT | | WM/Script |
| SHOWSORT | | | sosn | | |
| SHOWWORKMOVEMENT | | | shwm | | |
| SUBTITLE | TIT3 | | \----:com.apple.iTunes:SUBTITLE | | WM/SubTitle |
| TAGGINGDATE | TDTG | | | | |
| TITLE | TIT2 | INAM | &copy;nam | | |
| TITLESORT | TSOT | | sonm | | WM/TitleSortOrder |
| TRACKNUMBER | TRCK | IPRT | trkn | TRACK | WM/TrackNumber |
| TVEPISODE | | | tves | | |
| TVEPISODEID | | | tven | | |
| TVNETWORK | | | tvnn | | |
| TVSEASON | | | tvsn | | |
| TVSHOW | | | tvsh | | |
| URL | WXXX | | | | |
| WORK | TIT1 | | &copy;wrk | | WM/ContentGroupDescription |
| Key | %ID3v2 | %RIFF | %MP4 | %APE | %ASF | %Matroska |
| -------------------------- | ------ | ----- | -------------------------------------------------------- | ----------------------- | --------------------------------- | ----------------------------- |
| ACOUSTID_FINGERPRINT | | | | | Acoustid/Fingerprint | |
| ACOUSTID_ID | | | | | Acoustid/Id | |
| ALBUM | TALB | IPRD | &copy;alb | | WM/AlbumTitle | TITLE/50 |
| ALBUMARTIST | TPE2 | | aART | ALBUM ARTIST | WM/AlbumArtist | ARTIST/50 |
| ALBUMARTISTSORT | TSO2 | | soaa | | WM/AlbumArtistSortOrder | ARTISTSORT/50 |
| ALBUMSORT | TSOA | | soal | | WM/AlbumSortOrder | TITLESORT/50 |
| ARRANGER | | IENG | | | | |
| ARTIST | TPE1 | IART | &copy;ART | | | |
| ARTISTS | | | \----:com.apple.iTunes:ARTISTS | | WM/ARTISTS | |
| ARTISTSORT | TSOP | | soar | | WM/ArtistSortOrder | |
| ARTISTWEBPAGE | WOAR | IBSU | | | WM/AuthorURL | |
| ASIN | | | \----:com.apple.iTunes:ASIN | | | |
| AUDIOSOURCEWEBPAGE | WOAS | | | | ASIN | |
| BARCODE | | | \----:com.apple.iTunes:BARCODE | | WM/Barcode | |
| BPM | TBPM | IBPM | tmpo | | WM/BeatsPerMinute | |
| CATALOGNUMBER | | | \----:com.apple.iTunes:CATALOGNUMBER | | WM/CatalogNo | CATALOG_NUMBER |
| COMMENT | COMM | ICMT | &copy;cmt | | | |
| COMPILATION | TCMP | | cpil | | | |
| COMPOSER | TCOM | IMUS | &copy;wrt | | WM/Composer | |
| COMPOSERSORT | TSOC | | soco | | | |
| CONDUCTOR | TPE3 | | \----:com.apple.iTunes:CONDUCTOR | | WM/Conductor | |
| COPYRIGHT | TCOP | ICOP | cprt | | | |
| COPYRIGHTURL | WCOP | | | | | |
| DATE | TDRC | ICRD | &copy;day | YEAR | WM/Year | DATE_RECORDED/30 |
| DISCNUMBER | TPOS | | disk | DISC | WM/PartOfSet | PART_NUMBER/50 |
| DISCSUBTITLE | TSST | PRT1 | \----:com.apple.iTunes:DISCSUBTITLE | | WM/SetSubTitle | |
| DISCTOTAL | | | | | | TOTAL_PARTS/50 |
| DJMIXER | | | \----:com.apple.iTunes:DJMIXER | | | MIXED_BY |
| ENCODEDBY | TENC | ITCH | &copy;enc | | WM/EncodedBy | ENCODER |
| ENCODING | TSSE | ISFT | &copy;too | | WM/EncodingSettings | ENCODER_SETTINGS |
| ENCODINGTIME | TDEN | IDIT | | | WM/EncodingTime | DATE_ENCODED |
| ENGINEER | | | \----:com.apple.iTunes:ENGINEER | | | |
| FILETYPE | TFLT | | | | | |
| FILEWEBPAGE | WOAF | | | | WM/AudioFileURL | |
| GAPLESSPLAYBACK | | | pgap | | | |
| GENRE | TCON | IGNR | &copy;gen | | WM/Genre | |
| GROUPING | GRP1 | | &copy;grp | | | |
| INITIALKEY | TKEY | | | | WM/InitialKey | INITIAL_KEY |
| INVOLVEDPEOPLE | TIPL | | | | | |
| ISRC | TSRC | ISRC | \----:com.apple.iTunes:ISRC | | WM/ISRC | |
| LABEL | TPUB | IPUB | \----:com.apple.iTunes:LABEL | | WM/Publisher | LABEL_CODE |
| LANGUAGE | TLAN | ILNG | \----:com.apple.iTunes:LANGUAGE | | WM/Language | |
| LENGTH | TLEN | | | | | |
| LICENSE | | | \----:com.apple.iTunes:LICENSE | | | |
| LYRICIST | TEXT | IWRI | \----:com.apple.iTunes:LYRICIST | | WM/Writer | |
| LYRICS | USLT | | &copy;lyr | | WM/Lyrics | |
| MEDIA | TMED | IMED | \----:com.apple.iTunes:MEDIA | | WM/Media | ORIGINAL_MEDIA_TYPE |
| MIXER | | | \----:com.apple.iTunes:MIXER | | | |
| MOOD | TMOO | | \----:com.apple.iTunes:MOOD | | WM/Mood | |
| MOVEMENTCOUNT | | | &copy;mvc | | | |
| MOVEMENTNAME | MVNM | | &copy;mvn | | | |
| MOVEMENTNUMBER | MVIN | | &copy;mvi | | | |
| MUSICBRAINZ_ALBUMID | | | \----:com.apple.iTunes:MusicBrainz Album Id | | MusicBrainz/Album Id | MUSICBRAINZ_ALBUMID/50 |
| MUSICBRAINZ_ALBUMARTISTID | | | \----:com.apple.iTunes:MusicBrainz Album Artist Id | | MusicBrainz/Album Artist Id | MUSICBRAINZ_ALBUMARTISTID/50 |
| MUSICBRAINZ_ARTISTID | | | \----:com.apple.iTunes:MusicBrainz Artist Id | | MusicBrainz/Artist Id | |
| MUSICBRAINZ_RELEASEGROUPID | | | \----:com.apple.iTunes:MusicBrainz Release Group Id | | MusicBrainz/Release Group Id | MUSICBRAINZ_RELEASEGROUPID/50 |
| MUSICBRAINZ_RELEASETRACKID | | | \----:com.apple.iTunes:MusicBrainz Release Track Id | | MusicBrainz/Release Track Id | |
| MUSICBRAINZ_TRACKID | | | \----:com.apple.iTunes:MusicBrainz Track Id | | MusicBrainz/Track Id | |
| MUSICBRAINZ_WORKID | | | \----:com.apple.iTunes:MusicBrainz Work Id | | MusicBrainz/Work Id | |
| MUSICIANCREDITS | TMCL | | | | | |
| MUSICIP_PUID | | | | | MusicIP/PUID | |
| ORIGINALALBUM | TOAL | | | | WM/OriginalAlbumTitle | |
| ORIGINALARTIST | TOPE | | | | WM/OriginalArtist | |
| ORIGINALDATE | TDOR | | \----:com.apple.iTunes:ORIGINALDATE | | WM/OriginalReleaseYear | |
| ORIGINALFILENAME | TOFN | | | | WM/OriginalFilename | |
| ORIGINALLYRICIST | TOLY | | | | WM/OriginalLyricist | |
| OWNER | TOWN | | ownr | | | PURCHASE_OWNER |
| PAYMENTWEBPAGE | WPAY | | | | | |
| PERFORMER | | ISTR | | | | |
| PLAYLISTDELAY | TDLY | | | | | |
| PODCAST | PCST | | pcst | | | |
| PODCASTCATEGORY | TCAT | | catg | | | |
| PODCASTDESC | TDES | | desc | | | |
| PODCASTID | TGID | | egid | | | |
| PODCASTURL | WFED | | purl | | | |
| PRODUCEDNOTICE | TPRO | | | | | |
| PRODUCER | | | \----:com.apple.iTunes:PRODUCER | | WM/Producer | |
| PUBLISHERWEBPAGE | WPUB | | | | | |
| RADIOSTATION | TRSN | | | | | |
| RADIOSTATIONOWNER | TRSO | | | | | |
| RADIOSTATIONWEBPAGE | WORS | | | | | |
| RELEASECOUNTRY | | ICNT | \----:com.apple.iTunes:MusicBrainz Album Release Country | | MusicBrainz/Album Release Country | |
| RELEASEDATE | TDRL | | \----:com.apple.iTunes:RELEASEDATE | | | DATE_RELEASED/30 |
| RELEASESTATUS | | | \----:com.apple.iTunes:MusicBrainz Album Status | MUSICBRAINZ_ALBUMSTATUS | MusicBrainz/Album Status | |
| RELEASETYPE | | | \----:com.apple.iTunes:MusicBrainz Album Type | MUSICBRAINZ_ALBUMTYPE | MusicBrainz/Album Type | |
| REMIXER | TPE4 | IEDT | \----:com.apple.iTunes:REMIXER | MIXARTIST | WM/ModifiedBy | REMIXED_BY |
| SCRIPT | | | \----:com.apple.iTunes:SCRIPT | | WM/Script | |
| SHOWSORT | | | sosn | | | |
| SHOWWORKMOVEMENT | | | shwm | | | |
| SUBTITLE | TIT3 | | \----:com.apple.iTunes:SUBTITLE | | WM/SubTitle | |
| TAGGINGDATE | TDTG | | | | | DATE_TAGGED |
| TITLE | TIT2 | INAM | &copy;nam | | | |
| TITLESORT | TSOT | | sonm | | WM/TitleSortOrder | |
| TRACKNUMBER | TRCK | IPRT | trkn | TRACK | WM/TrackNumber | PART_NUMBER/30 |
| TRACKTOTAL | | | | | | TOTAL_PARTS/30 |
| TVEPISODE | | | tves | | | |
| TVEPISODEID | | | tven | | | |
| TVNETWORK | | | tvnn | | | |
| TVSEASON | | | tvsn | | | |
| TVSHOW | | | tvsh | | | |
| URL | WXXX | | | | | |
| WORK | TIT1 | | &copy;wrk | | WM/ContentGroupDescription | |
*/

View File

@@ -495,6 +495,30 @@ int String::toInt(bool *ok) const
return static_cast<int>(value);
}
long long String::toLongLong(bool *ok, int base) const
{
const wchar_t *beginPtr = d->data.c_str();
wchar_t *endPtr;
errno = 0;
const long long value = ::wcstoll(beginPtr, &endPtr, base);
if(ok) {
*ok = errno == 0 && endPtr > beginPtr && *endPtr == L'\0';
}
return value;
}
unsigned long long String::toULongLong(bool *ok, int base) const
{
const wchar_t *beginPtr = d->data.c_str();
wchar_t *endPtr;
errno = 0;
const unsigned long long value = ::wcstoull(beginPtr, &endPtr, base);
if(ok) {
*ok = errno == 0 && endPtr > beginPtr && *endPtr == L'\0';
}
return value;
}
String String::stripWhiteSpace() const
{
static const wchar_t *WhiteSpaceChars = L"\t\n\f\r ";
@@ -527,6 +551,11 @@ String String::fromLongLong(long long n) // static
return std::to_string(n);
}
String String::fromULongLong(unsigned long long n) // static
{
return std::to_string(n);
}
wchar_t &String::operator[](int i)
{
detach();

View File

@@ -359,6 +359,24 @@ namespace TagLib {
*/
int toInt(bool *ok = nullptr) const;
/*!
* Convert the string to an integer.
*
* If the conversion was successful, it sets the value of \a *ok to
* \c true and returns the integer. Otherwise it sets \a *ok to \c false
* and the result is undefined.
*/
long long toLongLong(bool *ok = nullptr, int base = 10) const;
/*!
* Convert the string to an integer.
*
* If the conversion was successful, it sets the value of \a *ok to
* \c true and returns the integer. Otherwise it sets \a *ok to \c false
* and the result is undefined.
*/
unsigned long long toULongLong(bool *ok = nullptr, int base = 10) const;
/*!
* Returns a string with the leading and trailing whitespace stripped.
*/
@@ -384,6 +402,11 @@ namespace TagLib {
*/
static String fromLongLong(long long n);
/*!
* Converts the base-10 integer \a n to a string.
*/
static String fromULongLong(unsigned long long n);
/*!
* Returns a reference to the character at position \a i.
*/

View File

@@ -65,6 +65,11 @@ IF(WITH_SHORTEN)
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/shorten
)
ENDIF()
IF(WITH_MATROSKA)
SET(test_HDR_DIRS ${test_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/matroska
)
ENDIF()
INCLUDE_DIRECTORIES(${test_HDR_DIRS})
SET(test_runner_SRCS
@@ -152,6 +157,11 @@ IF(WITH_SHORTEN)
test_shorten.cpp
)
ENDIF()
IF(WITH_MATROSKA)
SET(test_runner_SRCS ${test_runner_SRCS}
test_matroska.cpp
)
ENDIF()
IF(BUILD_BINDINGS)
SET(test_runner_SRCS ${test_runner_SRCS}
test_tag_c.cpp

BIN
tests/data/no-tags.mka Normal file

Binary file not shown.

BIN
tests/data/no-tags.webm Normal file

Binary file not shown.

BIN
tests/data/optimized.mkv Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -472,6 +472,11 @@ public:
#endif
#ifdef TAGLIB_WITH_SHORTEN
CPPUNIT_ASSERT(extensions.contains("shn"));
#endif
#ifdef TAGLIB_WITH_MATROSKA
CPPUNIT_ASSERT(extensions.contains("mkv"));
CPPUNIT_ASSERT(extensions.contains("mka"));
CPPUNIT_ASSERT(extensions.contains("webm"));
#endif
}

1196
tests/test_matroska.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -148,6 +148,15 @@
#include "trueaudiofile.h"
#include "trueaudioproperties.h"
#endif
#ifdef TAGLIB_WITH_MATROSKA
#include "matroskaattachedfile.h"
#include "matroskaattachments.h"
#include "matroskachapteredition.h"
#include "matroskachapters.h"
#include "matroskafile.h"
#include "matroskaproperties.h"
#include "matroskatag.h"
#endif
#include <cppunit/extensions/HelperMacros.h>
@@ -297,6 +306,19 @@ public:
#ifdef TAGLIB_WITH_TRUEAUDIO
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::TrueAudio::File));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::TrueAudio::Properties));
#endif
#ifdef TAGLIB_WITH_MATROSKA
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::File));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Properties));
CPPUNIT_ASSERT_EQUAL(classSize(3, true), sizeof(TagLib::Matroska::Tag));
CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::Matroska::Element));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Attachments));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Chapters));
CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::ChapterEdition));
CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::Chapter));
CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::Chapter::Display));
CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::SimpleTag));
CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::AttachedFile));
#endif
}