Skip to content

Commit 2c01b63

Browse files
authored
Merge pull request #1149 from complexlogic/matroska
Another Matroska Attempt
2 parents 8c7d336 + d83d751 commit 2c01b63

81 files changed

Lines changed: 8846 additions & 105 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ include(ConfigureChecks.cmake)
100100
option(WITH_APE "Build with APE, MPC, WavPack" ON)
101101
option(WITH_ASF "Build with ASF" ON)
102102
option(WITH_DSF "Build with DSF" ON)
103+
option(WITH_MATROSKA "Build with Matroska" ON)
103104
option(WITH_MOD "Build with Tracker modules" ON)
104105
option(WITH_MP4 "Build with MP4" ON)
105106
option(WITH_RIFF "Build with AIFF, RIFF, WAV" ON)
@@ -197,6 +198,9 @@ endif()
197198
if(WITH_DSF)
198199
set(TAGLIB_WITH_DSF TRUE)
199200
endif()
201+
if(WITH_MATROSKA)
202+
set(TAGLIB_WITH_MATROSKA TRUE)
203+
endif()
200204
if(WITH_MOD)
201205
set(TAGLIB_WITH_MOD TRUE)
202206
endif()

INSTALL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ and ID3 tags cannot be disabled. The following CMake options are available:
6363
| `WITH_APE` | Build with APE, MPC, WavPack (default ON) |
6464
| `WITH_ASF` | Build with ASF (default ON) |
6565
| `WITH_DSF` | Build with DSF (default ON) |
66+
| `WITH_MATROSKA` | Build with Matroska (default ON) |
6667
| `WITH_MOD` | Build with Tracker modules (default ON) |
6768
| `WITH_MP4` | Build with MP4 (default ON) |
6869
| `WITH_RIFF` | Build with AIFF, RIFF, WAV (default ON) |

bindings/c/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ if(WITH_SHORTEN)
6363
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/shorten
6464
)
6565
endif()
66+
if(WITH_MATROSKA)
67+
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
68+
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/matroska
69+
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/matroska/ebml
70+
)
71+
endif()
6672
include_directories(${tag_c_HDR_DIRS})
6773

6874
set(tag_c_HDRS tag_c.h)

bindings/c/tag_c.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@
8080
#ifdef TAGLIB_WITH_SHORTEN
8181
#include "shortenfile.h"
8282
#endif
83+
#ifdef TAGLIB_WITH_MATROSKA
84+
#include "matroskafile.h"
85+
#endif
8386

8487
using namespace TagLib;
8588

@@ -241,6 +244,11 @@ TagLib_File *taglib_file_new_type_any_char(const T *filename, TagLib_File_Type t
241244
case TagLib_File_SHORTEN:
242245
file = new Shorten::File(filename);
243246
break;
247+
#endif
248+
#ifdef TAGLIB_WITH_MATROSKA
249+
case TagLib_File_MATROSKA:
250+
file = new Matroska::File(filename);
251+
break;
244252
#endif
245253
default:
246254
break;

bindings/c/tag_c.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ typedef enum {
132132
TagLib_File_Opus,
133133
TagLib_File_DSF,
134134
TagLib_File_DSDIFF,
135-
TagLib_File_SHORTEN
135+
TagLib_File_SHORTEN,
136+
TagLib_File_MATROSKA
136137
} TagLib_File_Type;
137138

138139
/*!

examples/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ include_directories(
22
${CMAKE_CURRENT_SOURCE_DIR}/../taglib
33
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit
44
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ape
5+
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/matroska
56
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg
67
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1
78
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2
@@ -38,6 +39,18 @@ target_link_libraries(framelist tag)
3839
add_executable(strip-id3v1 strip-id3v1.cpp)
3940
target_link_libraries(strip-id3v1 tag)
4041

42+
if(WITH_MATROSKA)
43+
########### next target ###############
44+
45+
add_executable(matroskareader matroskareader.cpp)
46+
target_link_libraries(matroskareader tag)
47+
48+
########### next target ###############
49+
50+
add_executable(matroskawriter matroskawriter.cpp)
51+
target_link_libraries(matroskawriter tag)
52+
endif()
53+
4154
install(TARGETS tagreader tagreader_c tagwriter framelist strip-id3v1
4255
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
4356
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}

examples/matroskareader.cpp

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#include <cstdio>
2+
#include "matroskafile.h"
3+
#include "matroskatag.h"
4+
#include "matroskasimpletag.h"
5+
#include "matroskaattachments.h"
6+
#include "matroskaattachedfile.h"
7+
#include "matroskachapters.h"
8+
#include "matroskachapteredition.h"
9+
#include "tstring.h"
10+
#include "tutils.h"
11+
#include "tbytevector.h"
12+
#define GREEN_TEXT(s) "" s ""
13+
#define PRINT_PRETTY(label, value) printf(" " GREEN_TEXT(label) ": %s\n", value)
14+
15+
int main(int argc, char *argv[])
16+
{
17+
if(argc != 2) {
18+
printf("Usage: matroskareader FILE\n");
19+
return 1;
20+
}
21+
TagLib::Matroska::File file(argv[1]);
22+
if(!file.isValid()) {
23+
printf("File is not valid\n");
24+
return 1;
25+
}
26+
auto tag = dynamic_cast<TagLib::Matroska::Tag*>(file.tag());
27+
if(!tag) {
28+
printf("File has no tag\n");
29+
return 0;
30+
}
31+
32+
const TagLib::Matroska::SimpleTagsList &list = tag->simpleTagsList();
33+
printf("Found %u tag(s):\n", list.size());
34+
35+
for(const TagLib::Matroska::SimpleTag &t : list) {
36+
PRINT_PRETTY("Tag Name", t.name().toCString(true));
37+
38+
if(t.type() == TagLib::Matroska::SimpleTag::StringType)
39+
PRINT_PRETTY("Tag Value", t.toString().toCString(true));
40+
else if(t.type() == TagLib::Matroska::SimpleTag::BinaryType)
41+
PRINT_PRETTY("Tag Value",
42+
TagLib::Utils::formatString("Binary with size %i", t.toByteVector().size()).toCString(false)
43+
);
44+
45+
auto targetTypeValue = static_cast<unsigned int>(t.targetTypeValue());
46+
PRINT_PRETTY("Target Type Value",
47+
targetTypeValue == 0 ? "None" : TagLib::Utils::formatString("%i", targetTypeValue).toCString(false)
48+
);
49+
if(auto trackUid = t.trackUid()) {
50+
PRINT_PRETTY("Track UID",
51+
TagLib::Utils::formatString("%llu",trackUid).toCString(false)
52+
);
53+
}
54+
const TagLib::String &language = t.language();
55+
PRINT_PRETTY("Language", !language.isEmpty() ? language.toCString(false) : "Not set");
56+
57+
printf("\n");
58+
}
59+
60+
TagLib::Matroska::Attachments *attachments = file.attachments();
61+
if(attachments) {
62+
const TagLib::Matroska::Attachments::AttachedFileList &list = attachments->attachedFileList();
63+
printf("Found %u attachment(s)\n", list.size());
64+
for(const auto &attachedFile : list) {
65+
PRINT_PRETTY("Filename", attachedFile.fileName().toCString(true));
66+
const TagLib::String &description = attachedFile.description();
67+
PRINT_PRETTY("Description", !description.isEmpty() ? description.toCString(true) : "None");
68+
const TagLib::String &mediaType = attachedFile.mediaType();
69+
PRINT_PRETTY("Media Type", !mediaType.isEmpty() ? mediaType.toCString(false) : "None");
70+
PRINT_PRETTY("Data Size",
71+
TagLib::Utils::formatString("%u byte(s)", attachedFile.data().size()).toCString(false)
72+
);
73+
PRINT_PRETTY("UID",
74+
TagLib::Utils::formatString("%llu", attachedFile.uid()).toCString(false)
75+
);
76+
}
77+
}
78+
else
79+
printf("File has no attachments\n");
80+
81+
TagLib::Matroska::Chapters *chapters = file.chapters();
82+
if(chapters) {
83+
printf("Chapters:\n");
84+
const TagLib::Matroska::Chapters::ChapterEditionList &editions = chapters->chapterEditionList();
85+
for(const auto &edition : editions) {
86+
if(edition.uid()) {
87+
PRINT_PRETTY("Edition UID", TagLib::Utils::formatString("%llu", edition.uid())
88+
.toCString(false));
89+
}
90+
PRINT_PRETTY("Edition Flags", TagLib::Utils::formatString("default=%d, ordered=%d",
91+
edition.isDefault(), edition.isOrdered())
92+
.toCString(false));
93+
printf("\n");
94+
for(const auto &chapter : edition.chapterList()) {
95+
PRINT_PRETTY("Chapter UID", TagLib::Utils::formatString("%llu", chapter.uid())
96+
.toCString(false));
97+
PRINT_PRETTY("Chapter flags", TagLib::Utils::formatString("hidden=%d", chapter.isHidden())
98+
.toCString(false));
99+
PRINT_PRETTY("Start-End", TagLib::Utils::formatString("%llu-%llu",
100+
chapter.timeStart(), chapter.timeEnd()).toCString(false));
101+
for(const auto &display : chapter.displayList()) {
102+
PRINT_PRETTY("Display", display.string().toCString(false));
103+
PRINT_PRETTY("Language", !display.language().isEmpty()
104+
? display.language().toCString(false) : "Not set");
105+
}
106+
printf("\n");
107+
}
108+
}
109+
}
110+
else
111+
printf("File has no chapters\n");
112+
113+
if(auto properties = dynamic_cast<const TagLib::Matroska::Properties *>(file.audioProperties())) {
114+
printf("Properties:\n");
115+
PRINT_PRETTY("Doc Type", properties->docType().toCString(false));
116+
PRINT_PRETTY("Doc Type Version", TagLib::String::number(properties->docTypeVersion()).toCString(false));
117+
PRINT_PRETTY("Codec Name", properties->codecName().toCString(true));
118+
PRINT_PRETTY("Bitrate", TagLib::String::number(properties->bitrate()).toCString(false));
119+
PRINT_PRETTY("Sample Rate", TagLib::String::number(properties->sampleRate()).toCString(false));
120+
PRINT_PRETTY("Channels", TagLib::String::number(properties->channels()).toCString(false));
121+
PRINT_PRETTY("Length [ms]", TagLib::String::number(properties->lengthInMilliseconds()).toCString(false));
122+
}
123+
return 0;
124+
}

examples/matroskawriter.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include <cstdio>
2+
#include "matroskafile.h"
3+
#include "matroskatag.h"
4+
#include "matroskasimpletag.h"
5+
#include "matroskaattachments.h"
6+
#include "matroskaattachedfile.h"
7+
#include "tfilestream.h"
8+
#include "tstring.h"
9+
#include "tutils.h"
10+
11+
int main(int argc, char *argv[])
12+
{
13+
if(argc != 3) {
14+
printf("Usage: matroskawriter FILE ARTWORK\n");
15+
return 1;
16+
}
17+
TagLib::Matroska::File file(argv[1]);
18+
if(!file.isValid()) {
19+
printf("File is not valid\n");
20+
return 1;
21+
}
22+
auto tag = file.tag(true);
23+
tag->clearSimpleTags();
24+
25+
tag->addSimpleTag(TagLib::Matroska::SimpleTag(
26+
"Test Name 1", TagLib::String("Test Value 1"),
27+
TagLib::Matroska::SimpleTag::TargetTypeValue::Track, "en"));
28+
29+
tag->addSimpleTag(TagLib::Matroska::SimpleTag(
30+
"Test Name 2", TagLib::String("Test Value 2"),
31+
TagLib::Matroska::SimpleTag::TargetTypeValue::Album));
32+
tag->setTitle("Test title");
33+
tag->setArtist("Test artist");
34+
tag->setYear(1969);
35+
36+
TagLib::FileStream image(argv[2]);
37+
auto data = image.readBlock(image.length());
38+
auto attachments = file.attachments(true);
39+
attachments->addAttachedFile(TagLib::Matroska::AttachedFile(
40+
data, "cover.jpg", "image/jpeg"));
41+
42+
file.save();
43+
44+
return 0;
45+
}

examples/tagwriter.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ void usage()
7171
std::cout << " -R <tagname> <tagvalue>" << std::endl;
7272
std::cout << " -I <tagname> <tagvalue>" << std::endl;
7373
std::cout << " -D <tagname>" << std::endl;
74+
std::cout << " -C <complex-property-key> <key1=val1,key2=val2,...>" << std::endl;
7475
std::cout << " -p <picturefile> <description> (\"\" \"\" to remove)" << std::endl;
7576
std::cout << std::endl;
7677

@@ -95,6 +96,102 @@ void checkForRejectedProperties(const TagLib::PropertyMap &tags)
9596
}
9697
}
9798

99+
/*!
100+
* Create a list of variant maps from a string.
101+
* The shorthand syntax in the string is kept simple, but should be sufficient
102+
* for testing. Multiple maps are separated by ';', values within a map are
103+
* assigned with key=value and separated by a ','. Types are detected, use
104+
* double quotes to force a string. A ByteVector can be constructed from the
105+
* contents of a file, the path is given after "file://". There is no escape
106+
* character, use hex codes for ',' (\x2C) or ';' (\x3B).
107+
*/
108+
TagLib::List<TagLib::VariantMap> parseComplexPropertyValues(const TagLib::String &str)
109+
{
110+
if(str.isEmpty() || str == "\"\"" || str == "''") {
111+
return {};
112+
}
113+
TagLib::List<TagLib::VariantMap> values;
114+
const auto valueStrs = str.split(";");
115+
for(const auto &valueStr : valueStrs) {
116+
TagLib::VariantMap value;
117+
const auto keyValStrs = valueStr.split(",");
118+
for(const auto &keyValStr : keyValStrs) {
119+
if(int equalPos = keyValStr.find('='); equalPos != -1) {
120+
TagLib::String key = keyValStr.substr(0, equalPos);
121+
TagLib::String valStr = keyValStr.substr(equalPos + 1);
122+
bool hasDot = false;
123+
bool hasNonNumeric = false;
124+
bool hasSign = false;
125+
for(auto it = valStr.cbegin(); it != valStr.cend(); ++it) {
126+
if(it == valStr.cbegin() && (*it == '-' || *it == '+')) {
127+
hasSign = true;
128+
}
129+
else if(*it == '.') {
130+
hasDot = true;
131+
}
132+
else if(*it < '0' || *it > '9') {
133+
hasNonNumeric = true;
134+
}
135+
}
136+
TagLib::Variant val;
137+
if(valStr == "null") {
138+
// keep empty variant
139+
}
140+
else if(valStr == "true" || valStr == "false") {
141+
val = TagLib::Variant(valStr == "true");
142+
}
143+
else if(!hasNonNumeric && hasDot) {
144+
val = TagLib::Variant(std::stod(valStr.to8Bit()));
145+
}
146+
else if(!hasNonNumeric && hasSign) {
147+
val = valStr.toLongLong(nullptr);
148+
}
149+
else if(!hasNonNumeric) {
150+
val = valStr.toULongLong(nullptr);
151+
}
152+
else if(valStr.startsWith("file://")) {
153+
auto filePath = valStr.substr(7 );
154+
if(isFile(filePath.toCString())) {
155+
std::ifstream fs;
156+
fs.open(filePath.toCString(), std::ios::in | std::ios::binary);
157+
std::stringstream buffer;
158+
buffer << fs.rdbuf();
159+
fs.close();
160+
TagLib::String buf(buffer.str());
161+
val = TagLib::Variant(buf.data(TagLib::String::Latin1));
162+
}
163+
else {
164+
std::cout << filePath.toCString() << " not found." << std::endl;
165+
val = TagLib::Variant(TagLib::ByteVector());
166+
}
167+
}
168+
else {
169+
int len = valStr.size();
170+
if(len >= 2 && valStr[0] == '"' && valStr[len - 1] == '"') {
171+
valStr = valStr.substr(1, len - 2);
172+
}
173+
int hexPos = 0;
174+
while((hexPos = valStr.find("\\x", hexPos)) != -1) {
175+
char ch;
176+
bool ok;
177+
if(static_cast<int>(valStr.length()) < hexPos + 4 ||
178+
(ch = static_cast<char>(
179+
valStr.substr(hexPos + 2, 2).toLongLong(&ok, 16)), !ok)) {
180+
break;
181+
}
182+
valStr = valStr.substr(0, hexPos) + ch + valStr.substr(hexPos + 4);
183+
++hexPos;
184+
}
185+
val = TagLib::Variant(valStr);
186+
}
187+
value.insert(key, val);
188+
}
189+
}
190+
values.append(value);
191+
}
192+
return values;
193+
}
194+
98195
int main(int argc, char *argv[])
99196
{
100197
TagLib::List<TagLib::FileRef> fileList;
@@ -170,6 +267,19 @@ int main(int argc, char *argv[])
170267
checkForRejectedProperties(f.setProperties(map));
171268
break;
172269
}
270+
case 'C': {
271+
if(i + 2 < argc) {
272+
numArgsConsumed = 3;
273+
if(!value.isEmpty()) {
274+
TagLib::List<TagLib::VariantMap> values = parseComplexPropertyValues(argv[i + 2]);
275+
f.setComplexProperties(value, values);
276+
}
277+
}
278+
else {
279+
usage();
280+
}
281+
break;
282+
}
173283
case 'p': {
174284
if(i + 2 < argc) {
175285
numArgsConsumed = 3;

0 commit comments

Comments
 (0)