mirror of
https://github.com/taglib/taglib.git
synced 2026-06-07 14:59:24 -04:00
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b94b93762 | ||
|
|
8511827fa1 | ||
|
|
b02ff63916 | ||
|
|
f1e8dac084 | ||
|
|
d1460b6fbf | ||
|
|
43190d30ed | ||
|
|
4c43f1c577 | ||
|
|
59ed19d12f | ||
|
|
1e7bdae284 | ||
|
|
e07b956fda | ||
|
|
5e1cb4081d | ||
|
|
e7e4f0958c | ||
|
|
497c040f04 | ||
|
|
05c2c8671e | ||
|
|
85b6a9eb93 | ||
|
|
5c70f0071f | ||
|
|
ae171ee237 | ||
|
|
78c7208bc9 | ||
|
|
0df52e3993 | ||
|
|
ba2441b378 | ||
|
|
c5ea13bb34 | ||
|
|
4a73d73b20 | ||
|
|
9c56f191e5 | ||
|
|
77f6b9add5 | ||
|
|
a64e7543f8 | ||
|
|
d466b72eea | ||
|
|
c3a0e1d0a2 | ||
|
|
13751f5a6b | ||
|
|
4da5ac2de4 | ||
|
|
193091fe2e | ||
|
|
5d63187a8b | ||
|
|
f32b503f56 | ||
|
|
d6a2134cf3 | ||
|
|
abadbb6768 | ||
|
|
49510e7d5a | ||
|
|
7f2f2ddcaf | ||
|
|
0368c0239a | ||
|
|
9411bb161f | ||
|
|
78298769de | ||
|
|
c43d2b3fc1 | ||
|
|
3db0d48f4b | ||
|
|
de6e2b15c8 | ||
|
|
b7f0225f0d | ||
|
|
f4117f873c | ||
|
|
cf86f20b36 | ||
|
|
74d93db166 | ||
|
|
397b6c1de3 | ||
|
|
51f431c96a | ||
|
|
11e3eb05bd | ||
|
|
2c01b63433 | ||
|
|
d83d751443 | ||
|
|
f4e7a742c3 | ||
|
|
68a514f4a0 | ||
|
|
607cd5bdf4 | ||
|
|
b625be5690 | ||
|
|
d2bf7e72d2 | ||
|
|
280d6306d2 | ||
|
|
aef78068b3 | ||
|
|
81815e1091 | ||
|
|
b9122afaca | ||
|
|
1d3a375765 | ||
|
|
241b3b2921 | ||
|
|
48104959b2 | ||
|
|
c817cc0a47 | ||
|
|
7a5a10102e | ||
|
|
d47d28f0f8 | ||
|
|
3566b00596 | ||
|
|
98bc68d16e | ||
|
|
8d02484804 | ||
|
|
cfade6d082 | ||
|
|
f7ab162514 | ||
|
|
5a6f1f96f8 | ||
|
|
6fa3db1e51 | ||
|
|
4546c00417 | ||
|
|
f94843614f | ||
|
|
975eaac3ca | ||
|
|
6d019a894c | ||
|
|
6342f00e8b | ||
|
|
b4e79a4a27 | ||
|
|
80837485cf | ||
|
|
47e9b9a17c | ||
|
|
8c7d3368da | ||
|
|
b4a4b42254 | ||
|
|
70c6a0c75f | ||
|
|
c67e434939 | ||
|
|
5d44f3eeac | ||
|
|
9c042984d2 | ||
|
|
49be371caa | ||
|
|
cbb3de6836 | ||
|
|
11efd2dd10 | ||
|
|
ab31d11c8d | ||
|
|
3c917caf52 | ||
|
|
fa96a6458e | ||
|
|
e831f0929f |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Ubuntu
|
||||
run: sudo apt install -y libcppunit-dev libutfcpp-dev zlib1g-dev
|
||||
|
||||
2
3rdparty/utfcpp
vendored
2
3rdparty/utfcpp
vendored
Submodule 3rdparty/utfcpp updated: df857efc5b...63d64de49f
39
CHANGELOG.md
39
CHANGELOG.md
@@ -1,3 +1,42 @@
|
||||
TagLib 2.3 (May 10, 2026)
|
||||
=========================
|
||||
|
||||
* MP4: Support for chapters (Nero and QuickTime).
|
||||
* WAV: Support for BEXT and iXML chunks.
|
||||
* FLAC: Support for BEXT and iXML application blocks.
|
||||
* Opus: New audio property `outputGain()`.
|
||||
* Speed up Matroska reading by using seek head for element lookup.
|
||||
* Speed up Matroska writing by offering multiple write style modes.
|
||||
* More tolerant handling of files with oversized RIFF chunks, zero size ID3v2
|
||||
frames and Matroska chapters without edition.
|
||||
* Avoid wrong content-based detection as MPEG files.
|
||||
* Fix bitrate calculations for MPEG ADTS and MP4 ESDS.
|
||||
* Fix data race with multi-threaded use of `MP4::ItemFactory`.
|
||||
* Fix unbounded recursion in EBML/Matroska `MasterElement` and MP4 atoms.
|
||||
* Limit number of MP4 atoms at top level.
|
||||
* Fix writing too many offsets when updating MP4 stco/co64 atoms.
|
||||
* Fix k bounds in Shorten Rice-Golomb coding.
|
||||
|
||||
TagLib 2.2.1 (Mar 7, 2026)
|
||||
==========================
|
||||
|
||||
* Support edition, chapter and attachment UIDs in Matroska simple tags.
|
||||
* Avoid duplicates in Matroska complex property keys.
|
||||
|
||||
TagLib 2.2 (Feb 18, 2026)
|
||||
=========================
|
||||
|
||||
* Support for Matroska (MKA, MKV) and WebM files.
|
||||
* Support for NI STEM in MP4 files.
|
||||
* New method isDsd() in WavPack Properties.
|
||||
* Stricter verification of ID3v2 frames.
|
||||
* Fix setting the last header flag in Ogg FLAC files.
|
||||
* Fix reading of the last page in Ogg streams.
|
||||
* Avoid corrupting invalid Ogg FLAC files without Vorbis comment.
|
||||
* Windows: Support MP4 files with 64-bit atoms.
|
||||
* Fix use of property keys with non-ASCII characters in C bindings.
|
||||
* Fix building with Android NDK 29.
|
||||
|
||||
TagLib 2.1.1 (June 30, 2025)
|
||||
============================
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.10...3.31)
|
||||
|
||||
project(taglib)
|
||||
|
||||
@@ -12,7 +12,7 @@ include(CMakePackageConfigHelpers)
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
|
||||
if(APPLE)
|
||||
option(BUILD_FRAMEWORK "Build an OS X framework" OFF)
|
||||
option(BUILD_FRAMEWORK "Build a macOS framework" OFF)
|
||||
if(BUILD_FRAMEWORK)
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
#set(CMAKE_MACOSX_RPATH 1)
|
||||
@@ -92,14 +92,15 @@ endif()
|
||||
# Minor version: increase it if you add ABI compatible features.
|
||||
# Patch version: increase it for bug fix releases.
|
||||
set(TAGLIB_SOVERSION_MAJOR 2)
|
||||
set(TAGLIB_SOVERSION_MINOR 1)
|
||||
set(TAGLIB_SOVERSION_PATCH 1)
|
||||
set(TAGLIB_SOVERSION_MINOR 3)
|
||||
set(TAGLIB_SOVERSION_PATCH 0)
|
||||
|
||||
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()
|
||||
|
||||
@@ -63,12 +63,13 @@ 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, WebM (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) |
|
||||
| `WITH_SHORTEN` | Build with Shorten (default ON) |
|
||||
| `WITH_TRUEAUDIO` | Build with TrueAudio (default ON) |
|
||||
| `WITH_VORBIS` | Build with Vorbis, FLAC, Ogg, Opus (default ON) |
|
||||
| `WITH_VORBIS` | Build with FLAC, Ogg, Opus, Speex (default ON) |
|
||||
|
||||
Note that disabling formats will remove exported symbols from the library and
|
||||
thus break binary compatibility. These options should therefore only be used
|
||||
|
||||
24
README.md
24
README.md
@@ -7,10 +7,11 @@
|
||||
https://taglib.org/
|
||||
|
||||
TagLib is a library for reading and editing the metadata of several
|
||||
popular audio formats. Currently, it supports both ID3v1 and ID3v2
|
||||
for MP3 files, [Ogg Vorbis][] comments and ID3 tags
|
||||
in [FLAC][], MPC, Speex, WavPack, TrueAudio, WAV, AIFF, MP4, APE, ASF,
|
||||
DSF, DFF and AAC files.
|
||||
popular audio formats. Currently, it supports various metadata containers such
|
||||
as [ID3v1][], [ID3v2][] and [Vorbis][] comments for MP3, MP4, AAC,
|
||||
[Ogg][], [Opus][], [FLAC][], [Speex][], [APE][], [MPC][], [WavPack][],
|
||||
[WAV][], [AIFF][], [TrueAudio][], [Matroska][], [WebM][], ASF, WMA, DSF, DFF and
|
||||
tracker (MOD, XM, S3M, IT) files.
|
||||
|
||||
TagLib is distributed under the [GNU Lesser General Public License][]
|
||||
(LGPL) and [Mozilla Public License][] (MPL). Essentially that means that
|
||||
@@ -18,7 +19,20 @@ it may be used in proprietary applications, but if changes are made to
|
||||
TagLib they must be contributed back to the project. Please review the
|
||||
licenses if you are considering using TagLib in your project.
|
||||
|
||||
[Ogg Vorbis]: https://xiph.org/vorbis/
|
||||
[ID3v1]: https://id3.org/ID3v1
|
||||
[ID3v2]: https://id3.org/Home
|
||||
[Vorbis]: https://xiph.org/vorbis/
|
||||
[Ogg]: https://www.xiph.org/ogg/
|
||||
[Opus]: https://opus-codec.org/
|
||||
[FLAC]: https://xiph.org/flac/
|
||||
[Speex]: https://www.speex.org/
|
||||
[APE]: https://www.monkeysaudio.com/
|
||||
[MPC]: https://musepack.net/
|
||||
[WavPack]: https://www.wavpack.com/
|
||||
[WAV]: https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
|
||||
[AIFF]: https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/AIFF.html
|
||||
[TrueAudio]: https://sourceforge.net/projects/tta/
|
||||
[Matroska]: https://www.matroska.org/
|
||||
[WebM]: https://www.webmproject.org/
|
||||
[GNU Lesser General Public License]: https://www.gnu.org/licenses/lgpl.html
|
||||
[Mozilla Public License]: https://www.mozilla.org/MPL/MPL-1.1.html
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
@@ -469,12 +477,13 @@ void _taglib_property_set(TagLib_File *file, const char* prop, const char* value
|
||||
return;
|
||||
|
||||
auto tfile = reinterpret_cast<FileRef *>(file);
|
||||
auto propStr = charArrayToString(prop);
|
||||
PropertyMap map = tfile->tag()->properties();
|
||||
|
||||
if(value) {
|
||||
auto property = map.find(prop);
|
||||
auto property = map.find(propStr);
|
||||
if(property == map.end()) {
|
||||
map.insert(prop, StringList(charArrayToString(value)));
|
||||
map.insert(propStr, StringList(charArrayToString(value)));
|
||||
}
|
||||
else {
|
||||
if(append) {
|
||||
@@ -486,7 +495,7 @@ void _taglib_property_set(TagLib_File *file, const char* prop, const char* value
|
||||
}
|
||||
}
|
||||
else {
|
||||
map.erase(prop);
|
||||
map.erase(propStr);
|
||||
}
|
||||
|
||||
tfile->setProperties(map);
|
||||
@@ -531,7 +540,7 @@ char **taglib_property_get(const TagLib_File *file, const char *prop)
|
||||
|
||||
const PropertyMap map = reinterpret_cast<const FileRef *>(file)->properties();
|
||||
|
||||
auto property = map.find(prop);
|
||||
auto property = map.find(charArrayToString(prop));
|
||||
if(property == map.end())
|
||||
return NULL;
|
||||
|
||||
@@ -573,16 +582,17 @@ bool _taglib_complex_property_set(
|
||||
return false;
|
||||
|
||||
auto tfile = reinterpret_cast<FileRef *>(file);
|
||||
auto keyStr = charArrayToString(key);
|
||||
|
||||
if(value == NULL) {
|
||||
return tfile->setComplexProperties(key, {});
|
||||
return tfile->setComplexProperties(keyStr, {});
|
||||
}
|
||||
|
||||
VariantMap map;
|
||||
const TagLib_Complex_Property_Attribute** attrPtr = value;
|
||||
while(*attrPtr) {
|
||||
const TagLib_Complex_Property_Attribute *attr = *attrPtr;
|
||||
String attrKey(attr->key);
|
||||
auto attrKey = charArrayToString(attr->key);
|
||||
switch(attr->value.type) {
|
||||
case TagLib_Variant_Void:
|
||||
map.insert(attrKey, Variant());
|
||||
@@ -627,8 +637,8 @@ bool _taglib_complex_property_set(
|
||||
++attrPtr;
|
||||
}
|
||||
|
||||
return append ? tfile->setComplexProperties(key, tfile->complexProperties(key).append(map))
|
||||
: tfile->setComplexProperties(key, {map});
|
||||
return append ? tfile->setComplexProperties(keyStr, tfile->complexProperties(keyStr).append(map))
|
||||
: tfile->setComplexProperties(keyStr, {map});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -676,7 +686,7 @@ TagLib_Complex_Property_Attribute*** taglib_complex_property_get(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const auto variantMaps = reinterpret_cast<const FileRef *>(file)->complexProperties(key);
|
||||
const auto variantMaps = reinterpret_cast<const FileRef *>(file)->complexProperties(charArrayToString(key));
|
||||
if(variantMaps.isEmpty()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/*!
|
||||
|
||||
@@ -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}
|
||||
|
||||
139
examples/matroskareader.cpp
Normal file
139
examples/matroskareader.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#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) "[1;32m" s "[0m"
|
||||
#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)
|
||||
);
|
||||
}
|
||||
if(auto editionUid = t.editionUid()) {
|
||||
PRINT_PRETTY("Edition UID",
|
||||
TagLib::Utils::formatString("%llu",editionUid).toCString(false)
|
||||
);
|
||||
}
|
||||
if(auto chapterUid = t.chapterUid()) {
|
||||
PRINT_PRETTY("Chapter UID",
|
||||
TagLib::Utils::formatString("%llu",chapterUid).toCString(false)
|
||||
);
|
||||
}
|
||||
if(auto attachmentUid = t.attachmentUid()) {
|
||||
PRINT_PRETTY("Attachment UID",
|
||||
TagLib::Utils::formatString("%llu",attachmentUid).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;
|
||||
}
|
||||
45
examples/matroskawriter.cpp
Normal file
45
examples/matroskawriter.cpp
Normal 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;
|
||||
}
|
||||
@@ -117,4 +117,3 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -187,7 +194,12 @@ if(WITH_MP4)
|
||||
mp4/mp4item.h
|
||||
mp4/mp4properties.h
|
||||
mp4/mp4coverart.h
|
||||
mp4/mp4stem.h
|
||||
mp4/mp4itemfactory.h
|
||||
mp4/mp4chapter.h
|
||||
mp4/mp4chapterholder.h
|
||||
mp4/mp4nerochapterlist.h
|
||||
mp4/mp4qtchapterlist.h
|
||||
)
|
||||
endif()
|
||||
if(WITH_MOD)
|
||||
@@ -220,6 +232,42 @@ 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
|
||||
matroska/matroskawritestyle.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
|
||||
@@ -327,7 +375,11 @@ if(WITH_MP4)
|
||||
mp4/mp4item.cpp
|
||||
mp4/mp4properties.cpp
|
||||
mp4/mp4coverart.cpp
|
||||
mp4/mp4stem.cpp
|
||||
mp4/mp4itemfactory.cpp
|
||||
mp4/mp4chapter.cpp
|
||||
mp4/mp4nerochapterlist.cpp
|
||||
mp4/mp4qtchapterlist.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -410,6 +462,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 +522,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 +530,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>
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace TagLib {
|
||||
void setReadOnly(bool readOnly);
|
||||
|
||||
/*!
|
||||
* Return \c true if the item is read-only.
|
||||
* Returns \c true if the item is read-only.
|
||||
*/
|
||||
bool isReadOnly() const;
|
||||
|
||||
|
||||
@@ -65,12 +65,8 @@ namespace
|
||||
if(name.size() != 4)
|
||||
return false;
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(name[i] < 32 || name[i] > 126)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return std::none_of(name.cbegin(), name.cend(),
|
||||
[](unsigned char c) { return c < 32 || c > 126; });
|
||||
}
|
||||
|
||||
enum {
|
||||
@@ -316,7 +312,7 @@ void DSDIFF::File::removeRootChunk(unsigned int i)
|
||||
unsigned long long chunkSize = d->chunks[i].size + d->chunks[i].padding + 12;
|
||||
|
||||
d->size -= chunkSize;
|
||||
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
insert(ByteVector::fromULongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
|
||||
removeBlock(d->chunks[i].offset - 12, chunkSize);
|
||||
|
||||
@@ -350,7 +346,7 @@ void DSDIFF::File::setRootChunkData(unsigned int i, const ByteVector &data)
|
||||
// First we update the global size
|
||||
|
||||
d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
|
||||
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
insert(ByteVector::fromULongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
|
||||
// Now update the specific chunk
|
||||
|
||||
@@ -387,7 +383,7 @@ void DSDIFF::File::setRootChunkData(const ByteVector &name, const ByteVector &da
|
||||
|
||||
// First we update the global size
|
||||
d->size += (offset & 1) + ((data.size() + 1) & ~1) + 12;
|
||||
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
insert(ByteVector::fromULongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
|
||||
// Now add the chunk to the file
|
||||
const unsigned long long fileLength = length();
|
||||
@@ -414,12 +410,12 @@ void DSDIFF::File::removeChildChunk(unsigned int i, unsigned int childChunkNum)
|
||||
|
||||
unsigned long long removedChunkTotalSize = childChunks[i].size + childChunks[i].padding + 12;
|
||||
d->size -= removedChunkTotalSize;
|
||||
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
insert(ByteVector::fromULongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
|
||||
// Update child chunk size
|
||||
|
||||
d->chunks[d->childChunkIndex[childChunkNum]].size -= removedChunkTotalSize;
|
||||
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
|
||||
insert(ByteVector::fromULongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
|
||||
d->endianness == BigEndian),
|
||||
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
|
||||
// Remove the chunk
|
||||
@@ -466,13 +462,13 @@ void DSDIFF::File::setChildChunkData(unsigned int i,
|
||||
|
||||
d->size += ((data.size() + 1) & ~1) - (childChunks[i].size + childChunks[i].padding);
|
||||
|
||||
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
insert(ByteVector::fromULongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
|
||||
// And the PROP chunk size
|
||||
|
||||
d->chunks[d->childChunkIndex[childChunkNum]].size +=
|
||||
((data.size() + 1) & ~1) - (childChunks[i].size + childChunks[i].padding);
|
||||
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
|
||||
insert(ByteVector::fromULongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
|
||||
d->endianness == BigEndian),
|
||||
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
|
||||
|
||||
@@ -542,13 +538,13 @@ void DSDIFF::File::setChildChunkData(const ByteVector &name,
|
||||
// First we update the global size
|
||||
|
||||
d->size += (offset & 1) + ((data.size() + 1) & ~1) + 12;
|
||||
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
insert(ByteVector::fromULongLong(d->size, d->endianness == BigEndian), 4, 8);
|
||||
|
||||
// And the child chunk size
|
||||
|
||||
d->chunks[d->childChunkIndex[childChunkNum]].size += (offset & 1)
|
||||
+ ((data.size() + 1) & ~1) + 12;
|
||||
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
|
||||
insert(ByteVector::fromULongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
|
||||
d->endianness == BigEndian),
|
||||
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
|
||||
|
||||
@@ -610,14 +606,14 @@ void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesSty
|
||||
bool bigEndian = d->endianness == BigEndian;
|
||||
|
||||
d->type = readBlock(4);
|
||||
d->size = readBlock(8).toLongLong(bigEndian);
|
||||
d->size = readBlock(8).toULongLong(bigEndian);
|
||||
d->format = readBlock(4);
|
||||
|
||||
// + 12: chunk header at least, fix for additional junk bytes
|
||||
|
||||
while(tell() + 12 <= length()) {
|
||||
ByteVector chunkName = readBlock(4);
|
||||
unsigned long long chunkSize = readBlock(8).toLongLong(bigEndian);
|
||||
unsigned long long chunkSize = readBlock(8).toULongLong(bigEndian);
|
||||
|
||||
if(!isValidChunkID(chunkName)) {
|
||||
debug("DSDIFF::File::read() -- Chunk '" + chunkName + "' has invalid ID");
|
||||
@@ -670,14 +666,14 @@ void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesSty
|
||||
}
|
||||
else if(d->chunks[i].name == "DST ") {
|
||||
// Now decode the chunks inside the DST chunk to read the DST Frame Information one
|
||||
long long dstChunkEnd = d->chunks[i].offset + d->chunks[i].size;
|
||||
unsigned long long dstChunkEnd = d->chunks[i].offset + d->chunks[i].size;
|
||||
seek(d->chunks[i].offset);
|
||||
|
||||
audioDataSizeinBytes = d->chunks[i].size;
|
||||
|
||||
while(tell() + 12 <= dstChunkEnd) {
|
||||
while(static_cast<unsigned long long>(tell()) + 12 <= dstChunkEnd) {
|
||||
ByteVector dstChunkName = readBlock(4);
|
||||
long long dstChunkSize = readBlock(8).toLongLong(bigEndian);
|
||||
unsigned long long dstChunkSize = readBlock(8).toULongLong(bigEndian);
|
||||
|
||||
if(!isValidChunkID(dstChunkName)) {
|
||||
debug("DSDIFF::File::read() -- DST Chunk '" + dstChunkName + "' has invalid ID");
|
||||
@@ -685,7 +681,7 @@ void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesSty
|
||||
break;
|
||||
}
|
||||
|
||||
if(static_cast<long long>(tell()) + dstChunkSize > dstChunkEnd) {
|
||||
if(tell() + dstChunkSize > dstChunkEnd) {
|
||||
debug("DSDIFF::File::read() -- DST Chunk '" + dstChunkName
|
||||
+ "' has invalid size (larger than the DST chunk)");
|
||||
setValid(false);
|
||||
@@ -712,14 +708,14 @@ void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesSty
|
||||
}
|
||||
}
|
||||
else if(d->chunks[i].name == "PROP") {
|
||||
d->childChunkIndex[PROPChunk] = i;
|
||||
d->childChunkIndex[PROPChunk] = static_cast<int>(i);
|
||||
// Now decodes the chunks inside the PROP chunk
|
||||
long long propChunkEnd = d->chunks[i].offset + d->chunks[i].size;
|
||||
unsigned long long propChunkEnd = d->chunks[i].offset + d->chunks[i].size;
|
||||
// +4 to remove the 'SND ' marker at beginning of 'PROP' chunk
|
||||
seek(d->chunks[i].offset + 4);
|
||||
while(tell() + 12 <= propChunkEnd) {
|
||||
while(static_cast<unsigned long long>(tell()) + 12 <= propChunkEnd) {
|
||||
ByteVector propChunkName = readBlock(4);
|
||||
long long propChunkSize = readBlock(8).toLongLong(bigEndian);
|
||||
unsigned long long propChunkSize = readBlock(8).toULongLong(bigEndian);
|
||||
|
||||
if(!isValidChunkID(propChunkName)) {
|
||||
debug("DSDIFF::File::read() -- PROP Chunk '" + propChunkName + "' has invalid ID");
|
||||
@@ -727,7 +723,7 @@ void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesSty
|
||||
break;
|
||||
}
|
||||
|
||||
if(static_cast<long long>(tell()) + propChunkSize > propChunkEnd) {
|
||||
if(tell() + propChunkSize > propChunkEnd) {
|
||||
debug("DSDIFF::File::read() -- PROP Chunk '" + propChunkName
|
||||
+ "' has invalid size (larger than the PROP chunk)");
|
||||
setValid(false);
|
||||
@@ -755,17 +751,17 @@ void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesSty
|
||||
}
|
||||
}
|
||||
else if(d->chunks[i].name == "DIIN") {
|
||||
d->childChunkIndex[DIINChunk] = i;
|
||||
d->childChunkIndex[DIINChunk] = static_cast<int>(i);
|
||||
d->hasDiin = true;
|
||||
|
||||
// Now decode the chunks inside the DIIN chunk
|
||||
|
||||
long long diinChunkEnd = d->chunks[i].offset + d->chunks[i].size;
|
||||
unsigned long long diinChunkEnd = d->chunks[i].offset + d->chunks[i].size;
|
||||
seek(d->chunks[i].offset);
|
||||
|
||||
while(tell() + 12 <= diinChunkEnd) {
|
||||
while(static_cast<unsigned long long>(tell()) + 12 <= diinChunkEnd) {
|
||||
ByteVector diinChunkName = readBlock(4);
|
||||
long long diinChunkSize = readBlock(8).toLongLong(bigEndian);
|
||||
unsigned long long diinChunkSize = readBlock(8).toULongLong(bigEndian);
|
||||
|
||||
if(!isValidChunkID(diinChunkName)) {
|
||||
debug("DSDIFF::File::read() -- DIIN Chunk '" + diinChunkName + "' has invalid ID");
|
||||
@@ -773,7 +769,7 @@ void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesSty
|
||||
break;
|
||||
}
|
||||
|
||||
if(static_cast<long long>(tell()) + diinChunkSize > diinChunkEnd) {
|
||||
if(tell() + diinChunkSize > diinChunkEnd) {
|
||||
debug("DSDIFF::File::read() -- DIIN Chunk '" + diinChunkName
|
||||
+ "' has invalid size (larger than the DIIN chunk)");
|
||||
setValid(false);
|
||||
@@ -829,7 +825,7 @@ void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesSty
|
||||
if(d->childChunks[PROPChunk][i].name == "ID3 " ||
|
||||
d->childChunks[PROPChunk][i].name == "id3 ") {
|
||||
if(d->hasID3v2) {
|
||||
d->duplicateID3V2chunkIndex = i;
|
||||
d->duplicateID3V2chunkIndex = static_cast<int>(i);
|
||||
// ID3V2 tag has already been found at root level
|
||||
continue;
|
||||
}
|
||||
@@ -917,7 +913,7 @@ void DSDIFF::File::writeChunk(const ByteVector &name, const ByteVector &data,
|
||||
combined.append(ByteVector(leadingPadding, '\x00'));
|
||||
|
||||
combined.append(name);
|
||||
combined.append(ByteVector::fromLongLong(data.size(), d->endianness == BigEndian));
|
||||
combined.append(ByteVector::fromULongLong(data.size(), d->endianness == BigEndian));
|
||||
combined.append(data);
|
||||
if((data.size() & 0x01) != 0)
|
||||
combined.append('\x00');
|
||||
|
||||
@@ -46,8 +46,8 @@ public:
|
||||
FilePrivate &operator=(const FilePrivate &) = delete;
|
||||
|
||||
const ID3v2::FrameFactory *ID3v2FrameFactory;
|
||||
long long fileSize = 0;
|
||||
long long metadataOffset = 0;
|
||||
unsigned long long fileSize = 0;
|
||||
unsigned long long metadataOffset = 0;
|
||||
std::unique_ptr<Properties> properties;
|
||||
std::unique_ptr<ID3v2::Tag> tag;
|
||||
};
|
||||
@@ -116,17 +116,17 @@ bool DSF::File::save(ID3v2::Version version)
|
||||
// Three things must be updated: the file size, the tag data, and the metadata offset
|
||||
|
||||
if(d->tag->isEmpty()) {
|
||||
long long newFileSize = d->metadataOffset ? d->metadataOffset : d->fileSize;
|
||||
unsigned long long newFileSize = d->metadataOffset ? d->metadataOffset : d->fileSize;
|
||||
|
||||
// Update the file size
|
||||
if(d->fileSize != newFileSize) {
|
||||
insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
|
||||
insert(ByteVector::fromULongLong(newFileSize, false), 12, 8);
|
||||
d->fileSize = newFileSize;
|
||||
}
|
||||
|
||||
// Update the metadata offset to 0 since there is no longer a tag
|
||||
if(d->metadataOffset) {
|
||||
insert(ByteVector::fromLongLong(0ULL, false), 20, 8);
|
||||
insert(ByteVector::fromULongLong(0ULL, false), 20, 8);
|
||||
d->metadataOffset = 0;
|
||||
}
|
||||
|
||||
@@ -136,19 +136,19 @@ bool DSF::File::save(ID3v2::Version version)
|
||||
else {
|
||||
ByteVector tagData = d->tag->render(version);
|
||||
|
||||
long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize;
|
||||
long long newFileSize = newMetadataOffset + tagData.size();
|
||||
long long oldTagSize = d->fileSize - newMetadataOffset;
|
||||
unsigned long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize;
|
||||
unsigned long long newFileSize = newMetadataOffset + tagData.size();
|
||||
unsigned long long oldTagSize = d->fileSize - newMetadataOffset;
|
||||
|
||||
// Update the file size
|
||||
if(d->fileSize != newFileSize) {
|
||||
insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
|
||||
insert(ByteVector::fromULongLong(newFileSize, false), 12, 8);
|
||||
d->fileSize = newFileSize;
|
||||
}
|
||||
|
||||
// Update the metadata offset
|
||||
if(d->metadataOffset != newMetadataOffset) {
|
||||
insert(ByteVector::fromLongLong(newMetadataOffset, false), 20, 8);
|
||||
insert(ByteVector::fromULongLong(newMetadataOffset, false), 20, 8);
|
||||
d->metadataOffset = newMetadataOffset;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle)
|
||||
return;
|
||||
}
|
||||
|
||||
long long dsdHeaderSize = readBlock(8).toLongLong(false);
|
||||
unsigned long long dsdHeaderSize = readBlock(8).toULongLong(false);
|
||||
|
||||
// Integrity check
|
||||
if(dsdHeaderSize != 28) {
|
||||
@@ -184,16 +184,16 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle)
|
||||
return;
|
||||
}
|
||||
|
||||
d->fileSize = readBlock(8).toLongLong(false);
|
||||
d->fileSize = readBlock(8).toULongLong(false);
|
||||
|
||||
// File is malformed or corrupted, allow trailing garbage
|
||||
if(d->fileSize > length()) {
|
||||
if(d->fileSize > static_cast<unsigned long long>(length())) {
|
||||
debug("DSF::File::read() -- File is corrupted wrong length");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
d->metadataOffset = readBlock(8).toLongLong(false);
|
||||
d->metadataOffset = readBlock(8).toULongLong(false);
|
||||
|
||||
// File is malformed or corrupted
|
||||
if(d->metadataOffset > d->fileSize) {
|
||||
@@ -210,7 +210,7 @@ void DSF::File::read(AudioProperties::ReadStyle propertiesStyle)
|
||||
return;
|
||||
}
|
||||
|
||||
long long fmtHeaderSize = readBlock(8).toLongLong(false);
|
||||
unsigned long long fmtHeaderSize = readBlock(8).toULongLong(false);
|
||||
if(fmtHeaderSize != 52) {
|
||||
debug("DSF::File::read() -- File is corrupted, wrong FMT header size");
|
||||
setValid(false);
|
||||
|
||||
@@ -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, audioPropertiesStyle);
|
||||
#endif
|
||||
|
||||
// if file is not valid, leave it to content-based detection.
|
||||
|
||||
@@ -239,8 +246,7 @@ namespace
|
||||
{
|
||||
File *file = nullptr;
|
||||
|
||||
if(MPEG::File::isSupported(stream))
|
||||
file = new MPEG::File(stream, readAudioProperties, audioPropertiesStyle);
|
||||
if(false);
|
||||
#ifdef TAGLIB_WITH_VORBIS
|
||||
else if(Ogg::Vorbis::File::isSupported(stream))
|
||||
file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
|
||||
@@ -289,6 +295,12 @@ 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
|
||||
else if(MPEG::File::isSupported(stream))
|
||||
file = new MPEG::File(stream, readAudioProperties, audioPropertiesStyle);
|
||||
|
||||
// isSupported() only does a quick check, so double check the file here.
|
||||
|
||||
@@ -513,6 +525,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;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ public:
|
||||
|
||||
std::unique_ptr<Properties> properties;
|
||||
ByteVector xiphCommentData;
|
||||
String iXMLData;
|
||||
ByteVector bextData;
|
||||
List<FLAC::MetadataBlock *> blocks;
|
||||
|
||||
offset_t flacStart { 0 };
|
||||
@@ -241,6 +243,52 @@ bool FLAC::File::save()
|
||||
|
||||
d->xiphCommentData = xiphComment()->render(false);
|
||||
|
||||
// Drop any APPLICATION blocks we recognize as iXML or bext from the block
|
||||
// list. Recognized blocks were normally extracted to d->iXMLData /
|
||||
// d->bextData during scan() and never added here, but this also catches
|
||||
// entries inserted after scan() (defensive).
|
||||
for(auto it = d->blocks.begin(); it != d->blocks.end();) {
|
||||
if((*it)->code() == MetadataBlock::Application) {
|
||||
const ByteVector blockData = (*it)->render();
|
||||
if(blockData.size() >= 4) {
|
||||
const ByteVector appId = blockData.mid(0, 4);
|
||||
ByteVector innerId;
|
||||
if(appId == "riff" && blockData.size() >= 12)
|
||||
innerId = blockData.mid(4, 4);
|
||||
else if(appId == "iXML" || appId == "bext")
|
||||
innerId = appId;
|
||||
|
||||
if(innerId == "iXML" || innerId == "bext") {
|
||||
delete *it;
|
||||
it = d->blocks.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
// Append fresh APPLICATION/"riff" blocks for iXML and bext if non-empty.
|
||||
// Per FLAC foreign-metadata convention the payload is a RIFF chunk:
|
||||
// <4 byte FOURCC><4 byte LE size><data>.
|
||||
if(!d->iXMLData.isEmpty()) {
|
||||
const ByteVector xml = d->iXMLData.data(String::UTF8);
|
||||
ByteVector payload;
|
||||
payload.append("riff");
|
||||
payload.append("iXML");
|
||||
payload.append(ByteVector::fromUInt(xml.size(), false));
|
||||
payload.append(xml);
|
||||
d->blocks.append(new UnknownMetadataBlock(MetadataBlock::Application, payload));
|
||||
}
|
||||
if(!d->bextData.isEmpty()) {
|
||||
ByteVector payload;
|
||||
payload.append("riff");
|
||||
payload.append("bext");
|
||||
payload.append(ByteVector::fromUInt(d->bextData.size(), false));
|
||||
payload.append(d->bextData);
|
||||
d->blocks.append(new UnknownMetadataBlock(MetadataBlock::Application, payload));
|
||||
}
|
||||
|
||||
// Replace metadata blocks
|
||||
|
||||
MetadataBlock *commentBlock =
|
||||
@@ -433,6 +481,26 @@ void FLAC::File::removePictures()
|
||||
}
|
||||
}
|
||||
|
||||
String FLAC::File::iXMLData() const
|
||||
{
|
||||
return d->iXMLData;
|
||||
}
|
||||
|
||||
void FLAC::File::setiXMLData(const String &data)
|
||||
{
|
||||
d->iXMLData = data;
|
||||
}
|
||||
|
||||
ByteVector FLAC::File::BEXTData() const
|
||||
{
|
||||
return d->bextData;
|
||||
}
|
||||
|
||||
void FLAC::File::setBEXTData(const ByteVector &data)
|
||||
{
|
||||
d->bextData = data;
|
||||
}
|
||||
|
||||
void FLAC::File::strip(int tags)
|
||||
{
|
||||
if(tags & ID3v1)
|
||||
@@ -462,6 +530,16 @@ bool FLAC::File::hasID3v2Tag() const
|
||||
return d->ID3v2Location >= 0;
|
||||
}
|
||||
|
||||
bool FLAC::File::hasiXMLData() const
|
||||
{
|
||||
return !d->iXMLData.isEmpty();
|
||||
}
|
||||
|
||||
bool FLAC::File::hasBEXTData() const
|
||||
{
|
||||
return !d->bextData.isEmpty();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -613,6 +691,49 @@ void FLAC::File::scan()
|
||||
else if(blockType == MetadataBlock::Padding) {
|
||||
// Skip all padding blocks.
|
||||
}
|
||||
else if(blockType == MetadataBlock::Application && data.size() >= 4) {
|
||||
// APPLICATION block (RFC 9639 § 8.4):
|
||||
// <4 bytes> big-endian application ID (ASCII FOURCC in practice)
|
||||
// <n bytes> application-defined data
|
||||
//
|
||||
// We recognize two conventions for carrying RIFF iXML / bext metadata:
|
||||
// 1. App ID "riff" — IANA-registered FLAC foreign-metadata wrapper.
|
||||
// Payload is a RIFF chunk: <4 byte FOURCC><4 byte LE size><data>.
|
||||
// 2. App ID "iXML" or "bext" — direct, used by some third-party tools
|
||||
// (e.g. Sequoia). Payload is the chunk data verbatim.
|
||||
//
|
||||
// Other application IDs (and "riff" wrapping FOURCCs we don't recognize)
|
||||
// fall through to UnknownMetadataBlock so they round-trip unchanged.
|
||||
const ByteVector appId = data.mid(0, 4);
|
||||
ByteVector innerId;
|
||||
ByteVector innerData;
|
||||
|
||||
if(appId == "riff" && data.size() >= 12) {
|
||||
innerId = data.mid(4, 4);
|
||||
const unsigned int innerSize = data.toUInt(8U, false);
|
||||
innerData = data.mid(12, innerSize);
|
||||
}
|
||||
else if(appId == "iXML" || appId == "bext") {
|
||||
innerId = appId;
|
||||
innerData = data.mid(4);
|
||||
}
|
||||
|
||||
if(innerId == "iXML") {
|
||||
if(d->iXMLData.isEmpty())
|
||||
d->iXMLData = String(innerData, String::UTF8);
|
||||
else
|
||||
debug("FLAC::File::scan() -- multiple iXML blocks found, discarding");
|
||||
}
|
||||
else if(innerId == "bext") {
|
||||
if(d->bextData.isEmpty())
|
||||
d->bextData = innerData;
|
||||
else
|
||||
debug("FLAC::File::scan() -- multiple BEXT blocks found, discarding");
|
||||
}
|
||||
else {
|
||||
block = new UnknownMetadataBlock(blockType, data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
block = new UnknownMetadataBlock(blockType, data);
|
||||
}
|
||||
|
||||
@@ -296,6 +296,52 @@ namespace TagLib {
|
||||
*/
|
||||
void addPicture(Picture *picture);
|
||||
|
||||
/*!
|
||||
* Returns the raw iXML data as a String. Empty if no iXML metadata
|
||||
* is present. Read from an APPLICATION metadata block (RFC 9639 § 8.4)
|
||||
* carrying either the FLAC foreign-metadata application ID "riff"
|
||||
* (with an iXML RIFF chunk as payload) or the direct application ID
|
||||
* "iXML" used by some third-party tools.
|
||||
*
|
||||
* \see setiXMLData()
|
||||
* \see hasiXMLData()
|
||||
*/
|
||||
String iXMLData() const;
|
||||
|
||||
/*!
|
||||
* Sets the iXML data. Pass an empty string to remove the iXML
|
||||
* APPLICATION block on save. On save, the data is written using the
|
||||
* FLAC foreign-metadata convention: an APPLICATION block with
|
||||
* application ID "riff" wrapping an iXML RIFF chunk.
|
||||
*
|
||||
* \see iXMLData()
|
||||
* \see hasiXMLData()
|
||||
*/
|
||||
void setiXMLData(const String &data);
|
||||
|
||||
/*!
|
||||
* Returns the raw BEXT (Broadcast Audio Extension) data as a
|
||||
* ByteVector. Empty if no BEXT metadata is present. Read from an
|
||||
* APPLICATION metadata block (RFC 9639 § 8.4) carrying either the FLAC
|
||||
* foreign-metadata application ID "riff" (with a bext RIFF chunk as
|
||||
* payload) or the direct application ID "bext".
|
||||
*
|
||||
* \see setBEXTData()
|
||||
* \see hasBEXTData()
|
||||
*/
|
||||
ByteVector BEXTData() const;
|
||||
|
||||
/*!
|
||||
* Sets the BEXT data. Pass an empty ByteVector to remove the BEXT
|
||||
* APPLICATION block on save. On save, the data is written using the
|
||||
* FLAC foreign-metadata convention: an APPLICATION block with
|
||||
* application ID "riff" wrapping a bext RIFF chunk.
|
||||
*
|
||||
* \see BEXTData()
|
||||
* \see hasBEXTData()
|
||||
*/
|
||||
void setBEXTData(const ByteVector &data);
|
||||
|
||||
/*!
|
||||
* This will remove the tags that match the OR-ed together TagTypes from
|
||||
* the file. By default it removes all tags.
|
||||
@@ -332,6 +378,22 @@ namespace TagLib {
|
||||
*/
|
||||
bool hasID3v2Tag() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file on disk actually has iXML data
|
||||
* stored in an APPLICATION metadata block.
|
||||
*
|
||||
* \see iXMLData()
|
||||
*/
|
||||
bool hasiXMLData() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the file on disk actually has BEXT data
|
||||
* stored in an APPLICATION metadata block.
|
||||
*
|
||||
* \see BEXTData()
|
||||
*/
|
||||
bool hasBEXTData() const;
|
||||
|
||||
/*!
|
||||
* Returns whether or not the given \a stream can be opened as a FLAC
|
||||
* file.
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace TagLib {
|
||||
int bitsPerSample() const;
|
||||
|
||||
/*!
|
||||
* Return the number of sample frames.
|
||||
* Returns the number of sample frames.
|
||||
*/
|
||||
unsigned long long sampleFrames() const;
|
||||
|
||||
|
||||
70
taglib/matroska/ebml/ebmlbinaryelement.cpp
Normal file
70
taglib/matroska/ebml/ebmlbinaryelement.cpp
Normal 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;
|
||||
}
|
||||
51
taglib/matroska/ebml/ebmlbinaryelement.h
Normal file
51
taglib/matroska/ebml/ebmlbinaryelement.h
Normal 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
|
||||
216
taglib/matroska/ebml/ebmlelement.cpp
Normal file
216
taglib/matroska/ebml/ebmlelement.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
/***************************************************************************
|
||||
* 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::MkTagEditionUID);
|
||||
RETURN_ELEMENT_FOR_CASE(Id::MkTagChapterUID);
|
||||
RETURN_ELEMENT_FOR_CASE(Id::MkTagAttachmentUID);
|
||||
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);
|
||||
}
|
||||
255
taglib/matroska/ebml/ebmlelement.h
Normal file
255
taglib/matroska/ebml/ebmlelement.h
Normal file
@@ -0,0 +1,255 @@
|
||||
/***************************************************************************
|
||||
* 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,
|
||||
MkTagEditionUID = 0x63C9,
|
||||
MkTagChapterUID = 0x63C4,
|
||||
MkTagAttachmentUID = 0x63C6,
|
||||
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::MkTagEditionUID> { using type = UIntElement; };
|
||||
template <> struct GetElementTypeById<Element::Id::MkTagChapterUID> { using type = UIntElement; };
|
||||
template <> struct GetElementTypeById<Element::Id::MkTagAttachmentUID> { 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
|
||||
109
taglib/matroska/ebml/ebmlfloatelement.cpp
Normal file
109
taglib/matroska/ebml/ebmlfloatelement.cpp
Normal 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;
|
||||
}
|
||||
59
taglib/matroska/ebml/ebmlfloatelement.h
Normal file
59
taglib/matroska/ebml/ebmlfloatelement.h
Normal 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
|
||||
144
taglib/matroska/ebml/ebmlmasterelement.cpp
Normal file
144
taglib/matroska/ebml/ebmlmasterelement.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/***************************************************************************
|
||||
* 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 "tdebug.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, int depth)
|
||||
{
|
||||
static constexpr int MAX_EBML_DEPTH = 64;
|
||||
if(depth > MAX_EBML_DEPTH) {
|
||||
debug("EBML: Maximum nesting depth exceeded");
|
||||
return false;
|
||||
}
|
||||
const offset_t maxOffset = file.tell() + dataSize;
|
||||
std::unique_ptr<Element> element;
|
||||
while((element = findNextElement(file, maxOffset))) {
|
||||
if(auto master = dynamic_cast<MasterElement *>(element.get())) {
|
||||
if(!master->read(file, depth + 1))
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
if(!element->read(file))
|
||||
return false;
|
||||
}
|
||||
elements.push_back(std::move(element));
|
||||
}
|
||||
return file.tell() == maxOffset;
|
||||
}
|
||||
|
||||
bool EBML::MasterElement::read(File &file)
|
||||
{
|
||||
return read(file, 0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
70
taglib/matroska/ebml/ebmlmasterelement.h
Normal file
70
taglib/matroska/ebml/ebmlmasterelement.h
Normal 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/ *
|
||||
***************************************************************************/
|
||||
|
||||
#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:
|
||||
bool read(File &file, int depth);
|
||||
|
||||
offset_t offset;
|
||||
offset_t padding = 0;
|
||||
offset_t minRenderSize = 0;
|
||||
std::list<std::unique_ptr<Element>> elements;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
81
taglib/matroska/ebml/ebmlmkattachments.cpp
Normal file
81
taglib/matroska/ebml/ebmlmkattachments.cpp
Normal 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() + padding);
|
||||
|
||||
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;
|
||||
}
|
||||
47
taglib/matroska/ebml/ebmlmkattachments.h
Normal file
47
taglib/matroska/ebml/ebmlmkattachments.h
Normal 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
|
||||
146
taglib/matroska/ebml/ebmlmkchapters.cpp
Normal file
146
taglib/matroska/ebml/ebmlmkchapters.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
/***************************************************************************
|
||||
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;
|
||||
|
||||
namespace {
|
||||
|
||||
Matroska::Chapter parseChapterAtom(
|
||||
const std::unique_ptr<EBML::Element> &atomElement)
|
||||
{
|
||||
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 = EBML::element_cast<EBML::Element::Id::MkChapterAtom>(atomElement);
|
||||
for(const auto &chapterChild : *chapterAtom) {
|
||||
if(const EBML::Element::Id cid = chapterChild->getId(); cid == EBML::Element::Id::MkChapterUID)
|
||||
chapterUid = EBML::element_cast<EBML::Element::Id::MkChapterUID>(chapterChild)->getValue();
|
||||
else if(cid == EBML::Element::Id::MkChapterTimeStart)
|
||||
chapterTimeStart = EBML::element_cast<EBML::Element::Id::MkChapterTimeStart>(chapterChild)->getValue();
|
||||
else if(cid == EBML::Element::Id::MkChapterTimeEnd)
|
||||
chapterTimeEnd = EBML::element_cast<EBML::Element::Id::MkChapterTimeEnd>(chapterChild)->getValue();
|
||||
else if(cid == EBML::Element::Id::MkChapterFlagHidden)
|
||||
chapterHidden = EBML::element_cast<EBML::Element::Id::MkChapterFlagHidden>(chapterChild)->getValue() != 0;
|
||||
else if (cid == EBML::Element::Id::MkChapterDisplay) {
|
||||
const auto display = EBML::element_cast<EBML::Element::Id::MkChapterDisplay>(chapterChild);
|
||||
String displayString;
|
||||
String displayLanguage;
|
||||
for(const auto &displayChild : *display) {
|
||||
if (const EBML::Element::Id did = displayChild->getId(); did == EBML::Element::Id::MkChapString)
|
||||
displayString = EBML::element_cast<EBML::Element::Id::MkChapString>(displayChild)->getValue();
|
||||
else if(did == EBML::Element::Id::MkChapLanguage)
|
||||
displayLanguage = EBML::element_cast<EBML::Element::Id::MkChapLanguage>(displayChild)->getValue();
|
||||
}
|
||||
if(!displayString.isEmpty()) {
|
||||
chapterDisplays.append(Matroska::Chapter::Display(displayString, displayLanguage));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Matroska::Chapter(chapterTimeStart, chapterTimeEnd, chapterDisplays,
|
||||
chapterUid, chapterHidden);
|
||||
}
|
||||
|
||||
} // namespae
|
||||
|
||||
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() + padding);
|
||||
|
||||
// Collect any orphan ChapterAtom elements not wrapped in an EditionEntry.
|
||||
// The Matroska spec requires ChapterAtom to be inside an EditionEntry, but
|
||||
// some muxers produce files with ChapterAtom directly under Chapters.
|
||||
// MKVToolNix and FFmpeg handle this case by treating the orphan atoms as
|
||||
// belonging to an implicit default edition.
|
||||
List<Matroska::Chapter> orphanChapters;
|
||||
|
||||
for(const auto &element : elements) {
|
||||
if(element->getId() == Id::MkChapterAtom) {
|
||||
if(auto chapter = parseChapterAtom(element); chapter.uid()) {
|
||||
orphanChapters.append(chapter);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
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) {
|
||||
if(auto chapter = parseChapterAtom(editionChild); chapter.uid()) {
|
||||
editionChapters.append(chapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!editionChapters.isEmpty()) {
|
||||
chapters->addChapterEdition(Matroska::ChapterEdition(
|
||||
editionChapters, editionIsDefault, editionIsOrdered, editionUid));
|
||||
}
|
||||
}
|
||||
|
||||
// If orphan chapters were found, wrap them in an implicit default edition
|
||||
// so they are not silently lost.
|
||||
if (!orphanChapters.isEmpty()) {
|
||||
chapters->addChapterEdition(Matroska::ChapterEdition(
|
||||
orphanChapters, true, false, 0));
|
||||
}
|
||||
|
||||
return chapters;
|
||||
}
|
||||
52
taglib/matroska/ebml/ebmlmkchapters.h
Normal file
52
taglib/matroska/ebml/ebmlmkchapters.h
Normal 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
|
||||
89
taglib/matroska/ebml/ebmlmkcues.cpp
Normal file
89
taglib/matroska/ebml/ebmlmkcues.cpp
Normal 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;
|
||||
}
|
||||
47
taglib/matroska/ebml/ebmlmkcues.h
Normal file
47
taglib/matroska/ebml/ebmlmkcues.h
Normal 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
|
||||
72
taglib/matroska/ebml/ebmlmkinfo.cpp
Normal file
72
taglib/matroska/ebml/ebmlmkinfo.cpp
Normal 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);
|
||||
}
|
||||
52
taglib/matroska/ebml/ebmlmkinfo.h
Normal file
52
taglib/matroska/ebml/ebmlmkinfo.h
Normal 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
|
||||
73
taglib/matroska/ebml/ebmlmkseekhead.cpp
Normal file
73
taglib/matroska/ebml/ebmlmkseekhead.cpp
Normal 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;
|
||||
}
|
||||
47
taglib/matroska/ebml/ebmlmkseekhead.h
Normal file
47
taglib/matroska/ebml/ebmlmkseekhead.h
Normal 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
|
||||
296
taglib/matroska/ebml/ebmlmksegment.cpp
Normal file
296
taglib/matroska/ebml/ebmlmksegment.cpp
Normal file
@@ -0,0 +1,296 @@
|
||||
/***************************************************************************
|
||||
* 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;
|
||||
|
||||
namespace {
|
||||
|
||||
template <EBML::Element::Id Id, typename ElementType>
|
||||
std::unique_ptr<ElementType> readElementAt(File &file,
|
||||
offset_t offset,
|
||||
offset_t maxOffset)
|
||||
{
|
||||
if(offset < 0 || offset >= maxOffset) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
file.seek(offset);
|
||||
auto element = EBML::Element::factory(file);
|
||||
if(!element || element->getId() != Id) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto typed = EBML::element_cast<Id>(std::move(element));
|
||||
if(!typed || !typed->read(file)) {
|
||||
return nullptr;
|
||||
}
|
||||
return typed;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
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)
|
||||
{
|
||||
return readLimited(file, dataSize);
|
||||
}
|
||||
|
||||
bool EBML::MkSegment::readLimited(File &file, offset_t scanLimit)
|
||||
{
|
||||
const offset_t filePos = file.tell();
|
||||
const offset_t maxOffset = filePos + dataSize;
|
||||
const offset_t maxScanOffset = filePos + std::min(scanLimit, dataSize);
|
||||
// When scanLimit is less than dataSize, the caller has requested a
|
||||
// fast/limited scan (e.g. AudioProperties::Fast). In that case and if the
|
||||
// file has been opened in read-only mode, we skip parsing the Cues element,
|
||||
// which can be tens of MB on large files, causing severe slowdowns over
|
||||
// network filesystems, and do not have to be updated in read-only mode.
|
||||
const bool skipCues = file.readOnly() && scanLimit < dataSize;
|
||||
MasterElement *pendingPaddingTarget = nullptr;
|
||||
offset_t accumulatedPadding = 0;
|
||||
std::unique_ptr<Element> element;
|
||||
while((element = findNextElement(file, maxScanOffset))) {
|
||||
if(const Id id = element->getId(); id == Id::MkSeekHead) {
|
||||
seekHead = element_cast<Id::MkSeekHead>(std::move(element));
|
||||
if(!seekHead->read(file))
|
||||
return false;
|
||||
// We have a seek head, let's use it for faster access to the other elements
|
||||
if(const auto elementAfterSeekHead = findNextElement(file, maxScanOffset);
|
||||
elementAfterSeekHead && elementAfterSeekHead->getId() == Id::VoidElement)
|
||||
seekHead->setPadding(elementAfterSeekHead->getSize());
|
||||
const offset_t segDataOffset = segmentDataOffset();
|
||||
const auto matroskaSeekHead = parseSeekHead();
|
||||
const auto accumulateVoidPadding = [&](MasterElement *target) {
|
||||
offset_t accPadding = 0;
|
||||
while(const auto next = findNextElement(file, maxOffset)) {
|
||||
if(next->getId() != Id::VoidElement)
|
||||
break;
|
||||
accPadding += next->getSize();
|
||||
next->skipData(file);
|
||||
}
|
||||
if(accPadding > 0)
|
||||
target->setPadding(accPadding);
|
||||
};
|
||||
|
||||
// Build a work list of seek entries. Some muxers (e.g. MakeMKV,
|
||||
// mkvmerge) write a small primary SeekHead at the start of the segment
|
||||
// that only references a secondary SeekHead at the end of the file,
|
||||
// which in turn lists Info / Tracks / Tags / Chapters / Attachments.
|
||||
// Follow such MkSeekHead -> MkSeekHead chains so the real entries are
|
||||
// not silently dropped.
|
||||
List<std::pair<unsigned int, offset_t>> entries =
|
||||
matroskaSeekHead->entryList();
|
||||
// Guard against pathological / circular chains.
|
||||
int chainedSeekHeadsFollowed = 0;
|
||||
constexpr int MAX_CHAINED_SEEKHEADS = 8;
|
||||
|
||||
for(unsigned int i = 0; i < entries.size(); ++i) {
|
||||
const auto &[idValue, relativeOffset] = entries[i];
|
||||
const offset_t absoluteOffset = segDataOffset + relativeOffset;
|
||||
switch(static_cast<Id>(idValue)) {
|
||||
case Id::MkSeekHead: {
|
||||
if(chainedSeekHeadsFollowed++ >= MAX_CHAINED_SEEKHEADS)
|
||||
break;
|
||||
auto chained = readElementAt<Id::MkSeekHead, MkSeekHead>(
|
||||
file, absoluteOffset, maxOffset);
|
||||
if(!chained)
|
||||
break;
|
||||
if(const auto parsed = chained->parse(segDataOffset)) {
|
||||
for(const auto &entry : parsed->entryList())
|
||||
entries.append(entry);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Id::MkCues:
|
||||
if(!skipCues) {
|
||||
if(!((cues = readElementAt<Id::MkCues, MkCues>(
|
||||
file, absoluteOffset, maxOffset))))
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Id::MkInfo:
|
||||
if(!((info = readElementAt<Id::MkInfo, MkInfo>(
|
||||
file, absoluteOffset, maxOffset))))
|
||||
return false;
|
||||
break;
|
||||
case Id::MkTracks:
|
||||
if(!((tracks = readElementAt<Id::MkTracks, MkTracks>(
|
||||
file, absoluteOffset, maxOffset))))
|
||||
return false;
|
||||
break;
|
||||
case Id::MkTags:
|
||||
if(!((tags = readElementAt<Id::MkTags, MkTags>(
|
||||
file, absoluteOffset, maxOffset))))
|
||||
return false;
|
||||
accumulateVoidPadding(tags.get());
|
||||
break;
|
||||
case Id::MkAttachments:
|
||||
if(!((attachments = readElementAt<Id::MkAttachments, MkAttachments>(
|
||||
file, absoluteOffset, maxOffset))))
|
||||
return false;
|
||||
accumulateVoidPadding(attachments.get());
|
||||
break;
|
||||
case Id::MkChapters:
|
||||
if(!((chapters = readElementAt<Id::MkChapters, MkChapters>(
|
||||
file, absoluteOffset, maxOffset))))
|
||||
return false;
|
||||
accumulateVoidPadding(chapters.get());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(id == Id::VoidElement) {
|
||||
if(pendingPaddingTarget) {
|
||||
accumulatedPadding += element->getSize();
|
||||
pendingPaddingTarget->setPadding(accumulatedPadding);
|
||||
}
|
||||
element->skipData(file);
|
||||
}
|
||||
else if(id == Id::MkCues) {
|
||||
pendingPaddingTarget = nullptr;
|
||||
accumulatedPadding = 0;
|
||||
if(!skipCues) {
|
||||
cues = element_cast<Id::MkCues>(std::move(element));
|
||||
if(!cues->read(file))
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
element->skipData(file);
|
||||
}
|
||||
}
|
||||
else if(id == Id::MkInfo) {
|
||||
pendingPaddingTarget = nullptr;
|
||||
accumulatedPadding = 0;
|
||||
info = element_cast<Id::MkInfo>(std::move(element));
|
||||
if(!info->read(file))
|
||||
return false;
|
||||
}
|
||||
else if(id == Id::MkTracks) {
|
||||
pendingPaddingTarget = nullptr;
|
||||
accumulatedPadding = 0;
|
||||
tracks = element_cast<Id::MkTracks>(std::move(element));
|
||||
if(!tracks->read(file))
|
||||
return false;
|
||||
}
|
||||
else if(id == Id::MkTags) {
|
||||
pendingPaddingTarget = nullptr;
|
||||
accumulatedPadding = 0;
|
||||
tags = element_cast<Id::MkTags>(std::move(element));
|
||||
if(!tags->read(file))
|
||||
return false;
|
||||
pendingPaddingTarget = tags.get();
|
||||
}
|
||||
else if(id == Id::MkAttachments) {
|
||||
pendingPaddingTarget = nullptr;
|
||||
accumulatedPadding = 0;
|
||||
attachments = element_cast<Id::MkAttachments>(std::move(element));
|
||||
if(!attachments->read(file))
|
||||
return false;
|
||||
pendingPaddingTarget = attachments.get();
|
||||
}
|
||||
else if(id == Id::MkChapters) {
|
||||
pendingPaddingTarget = nullptr;
|
||||
accumulatedPadding = 0;
|
||||
chapters = element_cast<Id::MkChapters>(std::move(element));
|
||||
if(!chapters->read(file))
|
||||
return false;
|
||||
pendingPaddingTarget = chapters.get();
|
||||
}
|
||||
else {
|
||||
pendingPaddingTarget = nullptr;
|
||||
accumulatedPadding = 0;
|
||||
element->skipData(file);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
77
taglib/matroska/ebml/ebmlmksegment.h
Normal file
77
taglib/matroska/ebml/ebmlmksegment.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/***************************************************************************
|
||||
* 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;
|
||||
bool readLimited(File &file, offset_t scanLimit);
|
||||
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
|
||||
131
taglib/matroska/ebml/ebmlmktags.cpp
Normal file
131
taglib/matroska/ebml/ebmlmktags.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/***************************************************************************
|
||||
* 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() + padding);
|
||||
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;
|
||||
unsigned long long edtionUid = 0;
|
||||
unsigned long long chapterUid = 0;
|
||||
unsigned long long attachmentUid = 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();
|
||||
}
|
||||
else if(id == Id::MkTagEditionUID) {
|
||||
edtionUid = element_cast<Id::MkTagEditionUID>(targetsChild)->getValue();
|
||||
}
|
||||
else if(id == Id::MkTagChapterUID) {
|
||||
chapterUid = element_cast<Id::MkTagChapterUID>(targetsChild)->getValue();
|
||||
}
|
||||
else if(id == Id::MkTagAttachmentUID) {
|
||||
attachmentUid = element_cast<Id::MkTagAttachmentUID>(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, edtionUid, chapterUid, attachmentUid)
|
||||
: Matroska::SimpleTag(tagName, *tagValueBinary,
|
||||
targetTypeValue, language, defaultLanguageFlag,
|
||||
trackUid, edtionUid, chapterUid, attachmentUid));
|
||||
}
|
||||
}
|
||||
return mTag;
|
||||
}
|
||||
47
taglib/matroska/ebml/ebmlmktags.h
Normal file
47
taglib/matroska/ebml/ebmlmktags.h
Normal 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
|
||||
86
taglib/matroska/ebml/ebmlmktracks.cpp
Normal file
86
taglib/matroska/ebml/ebmlmktracks.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
taglib/matroska/ebml/ebmlmktracks.h
Normal file
52
taglib/matroska/ebml/ebmlmktracks.h
Normal 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
|
||||
100
taglib/matroska/ebml/ebmlstringelement.cpp
Normal file
100
taglib/matroska/ebml/ebmlstringelement.cpp
Normal 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)
|
||||
{
|
||||
}
|
||||
65
taglib/matroska/ebml/ebmlstringelement.h
Normal file
65
taglib/matroska/ebml/ebmlstringelement.h
Normal 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
|
||||
95
taglib/matroska/ebml/ebmluintelement.cpp
Normal file
95
taglib/matroska/ebml/ebmluintelement.cpp
Normal 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;
|
||||
}
|
||||
50
taglib/matroska/ebml/ebmluintelement.h
Normal file
50
taglib/matroska/ebml/ebmluintelement.h
Normal 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
|
||||
122
taglib/matroska/ebml/ebmlutils.cpp
Normal file
122
taglib/matroska/ebml/ebmlutils.cpp
Normal 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);
|
||||
}
|
||||
78
taglib/matroska/ebml/ebmlutils.h
Normal file
78
taglib/matroska/ebml/ebmlutils.h
Normal 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
|
||||
73
taglib/matroska/ebml/ebmlvoidelement.cpp
Normal file
73
taglib/matroska/ebml/ebmlvoidelement.cpp
Normal 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();
|
||||
}
|
||||
51
taglib/matroska/ebml/ebmlvoidelement.h
Normal file
51
taglib/matroska/ebml/ebmlvoidelement.h
Normal 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
|
||||
99
taglib/matroska/matroskaattachedfile.cpp
Normal file
99
taglib/matroska/matroskaattachedfile.cpp
Normal 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;
|
||||
}
|
||||
110
taglib/matroska/matroskaattachedfile.h
Normal file
110
taglib/matroska/matroskaattachedfile.h
Normal 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
|
||||
150
taglib/matroska/matroskaattachments.cpp
Normal file
150
taglib/matroska/matroskaattachments.cpp
Normal 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/ *
|
||||
***************************************************************************/
|
||||
|
||||
#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));
|
||||
}
|
||||
// Pad to the previous size so the element keeps its slot in the file,
|
||||
// unless this element is the trailing element of the segment in
|
||||
// AvoidInsert mode -- shrinking from the end never inserts anything.
|
||||
if(writeStyle() != WriteStyle::Compact &&
|
||||
!(writeStyle() == WriteStyle::AvoidInsert && isTrailingInSegment())) {
|
||||
const auto beforeSize = sizeRenderedOrWritten();
|
||||
if(beforeSize > 0)
|
||||
attachments.setMinRenderSize(beforeSize);
|
||||
}
|
||||
return attachments.render();
|
||||
}
|
||||
83
taglib/matroska/matroskaattachments.h
Normal file
83
taglib/matroska/matroskaattachments.h
Normal 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
|
||||
157
taglib/matroska/matroskachapter.cpp
Normal file
157
taglib/matroska/matroskachapter.cpp
Normal 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;
|
||||
}
|
||||
178
taglib/matroska/matroskachapter.h
Normal file
178
taglib/matroska/matroskachapter.h
Normal 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
|
||||
102
taglib/matroska/matroskachapteredition.cpp
Normal file
102
taglib/matroska/matroskachapteredition.cpp
Normal 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;
|
||||
}
|
||||
108
taglib/matroska/matroskachapteredition.h
Normal file
108
taglib/matroska/matroskachapteredition.h
Normal 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
|
||||
160
taglib/matroska/matroskachapters.cpp
Normal file
160
taglib/matroska/matroskachapters.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
/***************************************************************************
|
||||
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));
|
||||
}
|
||||
// Pad to the previous size so the element keeps its slot in the file,
|
||||
// unless this element is the trailing element of the segment in
|
||||
// AvoidInsert mode -- shrinking from the end never inserts anything.
|
||||
if(writeStyle() != WriteStyle::Compact &&
|
||||
!(writeStyle() == WriteStyle::AvoidInsert && isTrailingInSegment())) {
|
||||
const auto beforeSize = sizeRenderedOrWritten();
|
||||
if(beforeSize > 0)
|
||||
chapters.setMinRenderSize(beforeSize);
|
||||
}
|
||||
return chapters.render();
|
||||
}
|
||||
87
taglib/matroska/matroskachapters.h
Normal file
87
taglib/matroska/matroskachapters.h
Normal 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
|
||||
312
taglib/matroska/matroskacues.cpp
Normal file
312
taglib/matroska/matroskacues.cpp
Normal 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;
|
||||
}
|
||||
116
taglib/matroska/matroskacues.h
Normal file
116
taglib/matroska/matroskacues.h
Normal 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
|
||||
240
taglib/matroska/matroskaelement.cpp
Normal file
240
taglib/matroska/matroskaelement.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/***************************************************************************
|
||||
* 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"
|
||||
#include "ebmlvoidelement.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;
|
||||
WriteStyle writeStyle = WriteStyle::Compact;
|
||||
bool isLastElement = true;
|
||||
bool isTrailingInSegment = false;
|
||||
offset_t appendOffset = 0;
|
||||
// Populated during render() for AvoidInsert+grow+non-last: the offset and
|
||||
// original size of the slot that should be overwritten with a Void element.
|
||||
offset_t voidAtOffset = 0;
|
||||
offset_t voidAtSize = 0;
|
||||
};
|
||||
|
||||
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(e->writeStyle == WriteStyle::AvoidInsert && !e->isLastElement
|
||||
&& afterSize > beforeSize && beforeSize > 0) {
|
||||
// Record old slot for void-overwrite, move element to end of segment.
|
||||
e->voidAtOffset = e->offset;
|
||||
e->voidAtSize = beforeSize;
|
||||
e->offset = e->appendOffset;
|
||||
// Notify listeners that a new element of afterSize bytes appeared at
|
||||
// appendOffset (which is past all other elements, so no offset shifts).
|
||||
if(!emitSizeChanged(static_cast<offset_t>(afterSize))) {
|
||||
return false;
|
||||
}
|
||||
// Update appendOffset for any subsequent AvoidInsert-grow in this round.
|
||||
e->appendOffset += static_cast<offset_t>(afterSize);
|
||||
}
|
||||
else {
|
||||
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::setWriteStyle(WriteStyle style)
|
||||
{
|
||||
e->writeStyle = style;
|
||||
}
|
||||
|
||||
Matroska::WriteStyle Matroska::Element::writeStyle() const
|
||||
{
|
||||
return e->writeStyle;
|
||||
}
|
||||
|
||||
void Matroska::Element::setIsLastElement(bool isLast)
|
||||
{
|
||||
e->isLastElement = isLast;
|
||||
}
|
||||
|
||||
void Matroska::Element::setAppendOffset(offset_t appendOffset)
|
||||
{
|
||||
e->appendOffset = appendOffset;
|
||||
}
|
||||
|
||||
void Matroska::Element::setIsTrailingInSegment(bool isTrailing)
|
||||
{
|
||||
e->isTrailingInSegment = isTrailing;
|
||||
}
|
||||
|
||||
bool Matroska::Element::isTrailingInSegment() const
|
||||
{
|
||||
return e->isTrailingInSegment;
|
||||
}
|
||||
|
||||
bool Matroska::Element::wasMoved() const
|
||||
{
|
||||
// voidAtSize is set when the element was moved during render().
|
||||
// After write() it is cleared, but the caller checks before write().
|
||||
return e->voidAtOffset != 0 || e->voidAtSize != 0;
|
||||
}
|
||||
|
||||
void Matroska::Element::write(File &file)
|
||||
{
|
||||
if(e->voidAtSize > 0) {
|
||||
// AvoidInsert: overwrite the old slot with a Void element.
|
||||
const auto voidData = EBML::VoidElement::renderSize(e->voidAtSize);
|
||||
file.insert(voidData, e->voidAtOffset, e->voidAtSize);
|
||||
e->voidAtOffset = 0;
|
||||
// The element was moved to a new position (end of segment),
|
||||
// so there are no existing bytes to replace at the new offset.
|
||||
e->size = 0;
|
||||
e->voidAtSize = 0;
|
||||
}
|
||||
file.insert(e->data, e->offset, e->size);
|
||||
e->size = e->data.size();
|
||||
}
|
||||
86
taglib/matroska/matroskaelement.h
Normal file
86
taglib/matroska/matroskaelement.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/***************************************************************************
|
||||
* 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"
|
||||
#include "matroskawritestyle.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);
|
||||
|
||||
void setWriteStyle(WriteStyle style);
|
||||
WriteStyle writeStyle() const;
|
||||
void setIsLastElement(bool isLast);
|
||||
void setAppendOffset(offset_t appendOffset);
|
||||
bool wasMoved() const;
|
||||
//! Mark this element as the trailing element of the segment (no other
|
||||
//! element follows it in the file). Trailing elements may shrink even
|
||||
//! in non-Compact write styles because no offsets need to be preserved.
|
||||
void setIsTrailingInSegment(bool isTrailing);
|
||||
bool isTrailingInSegment() const;
|
||||
|
||||
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
|
||||
695
taglib/matroska/matroskafile.cpp
Normal file
695
taglib/matroska/matroskafile.cpp
Normal file
@@ -0,0 +1,695 @@
|
||||
/***************************************************************************
|
||||
* 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 {
|
||||
|
||||
constexpr offset_t FAST_SCAN_LIMIT = static_cast<offset_t>(512 * 1024);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
offset_t maxOffset = fileLength - tell();
|
||||
if (readStyle == Properties::ReadStyle::Fast && maxOffset > FAST_SCAN_LIMIT) {
|
||||
maxOffset = FAST_SCAN_LIMIT;
|
||||
}
|
||||
|
||||
// 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, maxOffset)
|
||||
)
|
||||
);
|
||||
if(!segment) {
|
||||
debug("Failed to find Matroska segment");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the segment into memory from file
|
||||
d->segment = segment->parseSegment();
|
||||
maxOffset = segment->getDataSize();
|
||||
if (readStyle == Properties::ReadStyle::Fast && maxOffset > FAST_SCAN_LIMIT) {
|
||||
maxOffset = FAST_SCAN_LIMIT;
|
||||
}
|
||||
if(!segment->readLimited(*this, maxOffset)) {
|
||||
debug("Failed to read segment");
|
||||
setValid(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the elements
|
||||
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()
|
||||
{
|
||||
return save(WriteStyle::Compact);
|
||||
}
|
||||
|
||||
bool Matroska::File::save(WriteStyle writeStyle)
|
||||
{
|
||||
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);
|
||||
|
||||
// Configure write style on each data element. Determines whether elements
|
||||
// may be padded (DoNotShrink/AvoidInsert) or moved to the end (AvoidInsert).
|
||||
// New elements (no prior size) are always written compactly.
|
||||
if(writeStyle != WriteStyle::Compact) {
|
||||
// Determine which existing data element has the highest file offset
|
||||
// (i.e., is "last" among the data elements, before cues/seekHead/segment).
|
||||
// New elements always go after existing ones and are treated as compact.
|
||||
const Element *lastDataElement = nullptr;
|
||||
for(const auto element : renderList) {
|
||||
if(element->size() > 0)
|
||||
lastDataElement = element;
|
||||
}
|
||||
|
||||
// For AvoidInsert: an existing data element (Tags, Chapters, Attachments)
|
||||
// located before the LAST Cluster must not be grown in-place. Doing so
|
||||
// would shift later clusters and invalidate their cue positions. Such
|
||||
// elements are voided at their original position and appended at the
|
||||
// end of the segment instead. The boundary is the maximum cluster offset
|
||||
// (derived from cue-point cluster positions). If no cue points are
|
||||
// available, the Cues element offset is used as a safe upper bound
|
||||
// (Cues are always after the last Cluster). A value of 0 means
|
||||
// "no boundary" – any offset compares >= 0, so the boundary check is
|
||||
// a no-op in non-AvoidInsert modes.
|
||||
offset_t audioBoundary = 0;
|
||||
if(writeStyle == WriteStyle::AvoidInsert && d->cues) {
|
||||
const offset_t segDataOffset = d->segment->dataOffset();
|
||||
for(const auto &cp : d->cues->cuePointList()) {
|
||||
for(const auto &ct : cp->cueTrackList()) {
|
||||
audioBoundary = std::max(audioBoundary,
|
||||
segDataOffset + ct->getClusterPosition());
|
||||
}
|
||||
}
|
||||
if(audioBoundary == 0)
|
||||
audioBoundary = d->cues->offset();
|
||||
}
|
||||
|
||||
for(const auto element : renderList) {
|
||||
if(element->size() > 0) {
|
||||
element->setWriteStyle(writeStyle);
|
||||
// An element is "last" only if it has the highest data-element
|
||||
// offset AND sits past the last cluster. The latter is always true
|
||||
// when audioBoundary == 0 (DoNotShrink, or AvoidInsert without cues).
|
||||
element->setIsLastElement(element == lastDataElement
|
||||
&& element->offset() >= audioBoundary);
|
||||
}
|
||||
}
|
||||
|
||||
// For AvoidInsert: identify the segment-trailing element (highest offset
|
||||
// among data elements, Cues, SeekHead). The trailing element may shrink
|
||||
// without padding -- there is nothing after it whose offset would shift,
|
||||
// so a trailing void would be wasted space.
|
||||
if(writeStyle == WriteStyle::AvoidInsert) {
|
||||
Element *trailing = nullptr;
|
||||
offset_t maxOffset = 0;
|
||||
const auto consider = [&](Element *e) {
|
||||
if(e && e->size() > 0 && e->offset() > maxOffset) {
|
||||
maxOffset = e->offset();
|
||||
trailing = e;
|
||||
}
|
||||
};
|
||||
for(const auto element : renderList)
|
||||
consider(element);
|
||||
consider(d->cues.get());
|
||||
consider(d->seekHead.get());
|
||||
if(trailing)
|
||||
trailing->setIsTrailingInSegment(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Initialize appendOffset for AvoidInsert elements at the start of each round.
|
||||
if(writeStyle == WriteStyle::AvoidInsert) {
|
||||
const offset_t appendOffset = d->segment->endOffset();
|
||||
for(const auto element : renderList)
|
||||
element->setAppendOffset(appendOffset);
|
||||
}
|
||||
for(const auto element : renderList) {
|
||||
if(element->needsRender()) {
|
||||
rendering = true;
|
||||
if(!element->render()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
++renderRound;
|
||||
}
|
||||
|
||||
// For AvoidInsert: elements that were moved during rendering may have
|
||||
// stale offsets if in-place elements grew after the move was computed.
|
||||
// Re-assign their offsets sequentially from the correct position.
|
||||
if(writeStyle == WriteStyle::AvoidInsert) {
|
||||
// Collect moved elements in render order (= ascending original-offset order
|
||||
// = order they appear in renderList before any re-sort).
|
||||
List<Element *> movedElements;
|
||||
offset_t totalMovedSize = 0;
|
||||
for(const auto element : renderList) {
|
||||
if(element->wasMoved()) {
|
||||
movedElements.append(element);
|
||||
totalMovedSize += static_cast<offset_t>(element->data().size());
|
||||
}
|
||||
}
|
||||
if(!movedElements.isEmpty()) {
|
||||
// The segment end includes in-place growths AND all moved element sizes.
|
||||
// The moved elements start right after all in-place content.
|
||||
offset_t appendAt = d->segment->endOffset() - totalMovedSize;
|
||||
for(const auto element : movedElements) {
|
||||
element->setOffset(appendAt);
|
||||
appendAt += static_cast<offset_t>(element->data().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For elements that were moved to the end by AvoidInsert, update their
|
||||
// seek head entry to reflect the new file position.
|
||||
if(writeStyle == WriteStyle::AvoidInsert && d->seekHead) {
|
||||
const offset_t segDataOffset = d->segment->dataOffset();
|
||||
for(const auto element : renderList) {
|
||||
if(element->wasMoved()) {
|
||||
d->seekHead->updateEntry(element->id(), element->offset() - segDataOffset);
|
||||
}
|
||||
}
|
||||
// Re-render the seekHead (and anything it affects) after updating entries.
|
||||
// The seekHead slot was pre-padded, so this should not cause size changes.
|
||||
d->seekHead->setNeedsRender(true);
|
||||
for(const auto element : renderList) {
|
||||
if(element->needsRender()) {
|
||||
if(!element->render())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write out to file
|
||||
renderList.sort(sortAscending);
|
||||
for(const auto element : renderList)
|
||||
element->write(*this);
|
||||
|
||||
return true;
|
||||
}
|
||||
194
taglib/matroska/matroskafile.h
Normal file
194
taglib/matroska/matroskafile.h
Normal file
@@ -0,0 +1,194 @@
|
||||
/***************************************************************************
|
||||
* 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"
|
||||
#include "matroskawritestyle.h"
|
||||
|
||||
//! An implementation of Matroska metadata
|
||||
namespace TagLib::Matroska {
|
||||
class Tag;
|
||||
class Attachments;
|
||||
class Chapters;
|
||||
|
||||
//! An implementation of TagLib::File with Matroska specific methods
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Save the file with the specified write style.
|
||||
*
|
||||
* This returns \c true if the save was successful.
|
||||
*/
|
||||
bool save(WriteStyle style);
|
||||
|
||||
/*!
|
||||
* 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
|
||||
155
taglib/matroska/matroskaproperties.cpp
Normal file
155
taglib/matroska/matroskaproperties.cpp
Normal 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;
|
||||
}
|
||||
127
taglib/matroska/matroskaproperties.h
Normal file
127
taglib/matroska/matroskaproperties.h
Normal 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
|
||||
148
taglib/matroska/matroskaseekhead.cpp
Normal file
148
taglib/matroska/matroskaseekhead.cpp
Normal 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 "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()});
|
||||
setNeedsRender(true);
|
||||
}
|
||||
|
||||
void Matroska::SeekHead::addEntry(ID id, offset_t offset)
|
||||
{
|
||||
entries.append({id, offset});
|
||||
setNeedsRender(true);
|
||||
}
|
||||
|
||||
void Matroska::SeekHead::updateEntry(ID id, offset_t newOffset)
|
||||
{
|
||||
for(auto &entry : entries) {
|
||||
if(entry.first == id) {
|
||||
entry.second = newOffset;
|
||||
setNeedsRender(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const List<std::pair<unsigned int, offset_t>> &Matroska::SeekHead::entryList() const
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
57
taglib/matroska/matroskaseekhead.h
Normal file
57
taglib/matroska/matroskaseekhead.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/***************************************************************************
|
||||
* 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 updateEntry(ID id, offset_t offset);
|
||||
const List<std::pair<unsigned int, offset_t>> &entryList() const;
|
||||
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
|
||||
76
taglib/matroska/matroskasegment.cpp
Normal file
76
taglib/matroska/matroskasegment.cpp
Normal 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/ *
|
||||
***************************************************************************/
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
offset_t Matroska::Segment::endOffset() const
|
||||
{
|
||||
return dataOffset() + dataSize;
|
||||
}
|
||||
47
taglib/matroska/matroskasegment.h
Normal file
47
taglib/matroska/matroskasegment.h
Normal 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_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;
|
||||
offset_t endOffset() const;
|
||||
|
||||
private:
|
||||
ByteVector renderInternal() override;
|
||||
|
||||
offset_t sizeLength;
|
||||
offset_t dataSize;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
188
taglib/matroska/matroskasimpletag.cpp
Normal file
188
taglib/matroska/matroskasimpletag.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
/***************************************************************************
|
||||
* 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, unsigned long long editionUid,
|
||||
unsigned long long chapterUid, unsigned long long attachmentUid) :
|
||||
value(value), name(name), language(language), trackUid(trackUid),
|
||||
editionUid(editionUid), chapterUid(chapterUid), attachmentUid(attachmentUid),
|
||||
targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {}
|
||||
SimpleTagPrivate(const String &name, const ByteVector &value,
|
||||
TargetTypeValue targetTypeValue, const String &language, bool defaultLanguage,
|
||||
unsigned long long trackUid, unsigned long long editionUid,
|
||||
unsigned long long chapterUid, unsigned long long attachmentUid) :
|
||||
value(value), name(name), language(language), trackUid(trackUid),
|
||||
editionUid(editionUid), chapterUid(chapterUid), attachmentUid(attachmentUid),
|
||||
targetTypeValue(targetTypeValue), defaultLanguageFlag(defaultLanguage) {}
|
||||
|
||||
const std::variant<String, ByteVector> value;
|
||||
const String name;
|
||||
const String language;
|
||||
const unsigned long long trackUid;
|
||||
const unsigned long long editionUid;
|
||||
const unsigned long long chapterUid;
|
||||
const unsigned long long attachmentUid;
|
||||
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, 0, 0, 0))
|
||||
{
|
||||
}
|
||||
|
||||
Matroska::SimpleTag::SimpleTag(const String &name, const String &value,
|
||||
TargetTypeValue targetTypeValue,
|
||||
const String &language, bool defaultLanguage,
|
||||
unsigned long long trackUid,
|
||||
unsigned long long editionUid,
|
||||
unsigned long long chapterUid,
|
||||
unsigned long long attachmentUid) :
|
||||
d(std::make_unique<SimpleTagPrivate>(name, value, targetTypeValue,
|
||||
language, defaultLanguage, trackUid, editionUid, chapterUid, attachmentUid))
|
||||
{
|
||||
}
|
||||
|
||||
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, 0, 0, 0))
|
||||
{
|
||||
}
|
||||
|
||||
Matroska::SimpleTag::SimpleTag(const String &name, const ByteVector &value,
|
||||
TargetTypeValue targetTypeValue,
|
||||
const String &language, bool defaultLanguage,
|
||||
unsigned long long trackUid,
|
||||
unsigned long long editionUid,
|
||||
unsigned long long chapterUid,
|
||||
unsigned long long attachmentUid) :
|
||||
d(std::make_unique<SimpleTagPrivate>(name, value, targetTypeValue,
|
||||
language, defaultLanguage, trackUid, editionUid, chapterUid, attachmentUid))
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
unsigned long long Matroska::SimpleTag::editionUid() const
|
||||
{
|
||||
return d->editionUid;
|
||||
}
|
||||
|
||||
unsigned long long Matroska::SimpleTag::chapterUid() const
|
||||
{
|
||||
return d->chapterUid;
|
||||
}
|
||||
|
||||
unsigned long long Matroska::SimpleTag::attachmentUid() const
|
||||
{
|
||||
return d->attachmentUid;
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
185
taglib/matroska/matroskasimpletag.h
Normal file
185
taglib/matroska/matroskasimpletag.h
Normal file
@@ -0,0 +1,185 @@
|
||||
/***************************************************************************
|
||||
* 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 string simple tag.
|
||||
*/
|
||||
// BIC: merge with constructor above
|
||||
SimpleTag(const String& name, const String& value, TargetTypeValue targetTypeValue, const String& language,
|
||||
bool defaultLanguage, unsigned long long trackUid, unsigned long long editionUid,
|
||||
unsigned long long chapterUid = 0, unsigned long long attachmentUid = 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 binary simple tag.
|
||||
*/
|
||||
// BIC: merge with constructor above
|
||||
SimpleTag(const String& name, const ByteVector& value, TargetTypeValue targetTypeValue, const String& language,
|
||||
bool defaultLanguage, unsigned long long trackUid, unsigned long long editionUid,
|
||||
unsigned long long chapterUid = 0, unsigned long long attachmentUid = 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 UID that identifies the edition that the tags belong to,
|
||||
* zero if not defined, the tag applies to all editions
|
||||
*/
|
||||
unsigned long long editionUid() const;
|
||||
|
||||
/*!
|
||||
* Returns the UID that identifies the chapter that the tags belong to,
|
||||
* zero if not defined, the tag applies to all chapters
|
||||
*/
|
||||
unsigned long long chapterUid() const;
|
||||
|
||||
/*!
|
||||
* Returns the UID that identifies the attachment that the tags belong to,
|
||||
* zero if not defined, the tag applies to all attachments
|
||||
*/
|
||||
unsigned long long attachmentUid() 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
|
||||
670
taglib/matroska/matroskatag.cpp
Normal file
670
taglib/matroska/matroskatag.cpp
Normal file
@@ -0,0 +1,670 @@
|
||||
/***************************************************************************
|
||||
* 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)
|
||||
{
|
||||
removeSimpleTag(name, targetTypeValue, trackUid, 0, 0, 0);
|
||||
}
|
||||
|
||||
void Matroska::Tag::removeSimpleTag(const String& name,
|
||||
SimpleTag::TargetTypeValue targetTypeValue,
|
||||
unsigned long long trackUid, unsigned long long editionUid,
|
||||
unsigned long long chapterUid, unsigned long long attachmentUid)
|
||||
{
|
||||
const auto it = std::find_if(d->tags.begin(), d->tags.end(),
|
||||
[&name, targetTypeValue, trackUid, editionUid, chapterUid, attachmentUid](
|
||||
const SimpleTag &t) {
|
||||
return t.name() == name && t.targetTypeValue() == targetTypeValue &&
|
||||
t.trackUid() == trackUid && t.editionUid() == editionUid &&
|
||||
t.chapterUid() == chapterUid && t.attachmentUid() == attachmentUid;
|
||||
}
|
||||
);
|
||||
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 editionUid = tag.editionUid();
|
||||
auto chapterUid = tag.chapterUid();
|
||||
auto attachmentUid = tag.attachmentUid();
|
||||
auto it = std::find_if(targetList.begin(),
|
||||
targetList.end(),
|
||||
[&](const auto &list) {
|
||||
const auto &simpleTag = list.front();
|
||||
return simpleTag.targetTypeValue() == targetTypeValue &&
|
||||
simpleTag.trackUid() == trackUid &&
|
||||
simpleTag.editionUid() == editionUid &&
|
||||
simpleTag.chapterUid() == chapterUid &&
|
||||
simpleTag.attachmentUid() == attachmentUid;
|
||||
}
|
||||
);
|
||||
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();
|
||||
const auto editionUid = frontTag.editionUid();
|
||||
const auto chapterUid = frontTag.chapterUid();
|
||||
const auto attachmentUid = frontTag.attachmentUid();
|
||||
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));
|
||||
}
|
||||
if(editionUid != 0) {
|
||||
auto element = EBML::make_unique_element<EBML::Element::Id::MkTagEditionUID>();
|
||||
element->setValue(editionUid);
|
||||
targets->appendElement(std::move(element));
|
||||
}
|
||||
if(chapterUid != 0) {
|
||||
auto element = EBML::make_unique_element<EBML::Element::Id::MkTagChapterUID>();
|
||||
element->setValue(chapterUid);
|
||||
targets->appendElement(std::move(element));
|
||||
}
|
||||
if(attachmentUid != 0) {
|
||||
auto element = EBML::make_unique_element<EBML::Element::Id::MkTagAttachmentUID>();
|
||||
element->setValue(attachmentUid);
|
||||
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));
|
||||
}
|
||||
// Pad to the previous size so the element keeps its slot in the file,
|
||||
// unless this element is the trailing element of the segment in
|
||||
// AvoidInsert mode -- shrinking from the end never inserts anything,
|
||||
// so the trailing void would be wasted space.
|
||||
if(writeStyle() != WriteStyle::Compact &&
|
||||
!(writeStyle() == WriteStyle::AvoidInsert && isTrailingInSegment())) {
|
||||
const auto beforeSize = sizeRenderedOrWritten();
|
||||
if(beforeSize > 0)
|
||||
tags.setMinRenderSize(beforeSize);
|
||||
}
|
||||
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 && t.editionUid() == 0 && t.chapterUid() == 0
|
||||
&& t.attachmentUid() == 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 &&
|
||||
simpleTag.editionUid() == 0 && simpleTag.chapterUid() == 0 &&
|
||||
simpleTag.attachmentUid() == 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 && it->editionUid() == 0 &&
|
||||
it->chapterUid() == 0 && it->attachmentUid() == 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 || t.editionUid() != 0 ||
|
||||
t.chapterUid() != 0 || t.attachmentUid() != 0 ||
|
||||
translateTag(t.name(), t.targetTypeValue()).isEmpty()) &&
|
||||
!keys.contains(t.name())) {
|
||||
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 || t.editionUid() != 0 ||
|
||||
t.chapterUid() != 0 || t.attachmentUid() != 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());
|
||||
}
|
||||
if(t.editionUid()) {
|
||||
property.insert("editionUid", t.editionUid());
|
||||
}
|
||||
if(t.chapterUid()) {
|
||||
property.insert("chapterUid", t.chapterUid());
|
||||
}
|
||||
if(t.attachmentUid()) {
|
||||
property.insert("attachmentUid", t.attachmentUid());
|
||||
}
|
||||
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 || t.editionUid() != 0 ||
|
||||
t.chapterUid() != 0 || t.attachmentUid() != 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>();
|
||||
const auto editionUid = property.value("editionUid", 0ULL).value<unsigned long long>();
|
||||
const auto chapterUid = property.value("chapterUid", 0ULL).value<unsigned long long>();
|
||||
const auto attachmentUid = property.value("attachmentUid", 0ULL).value<unsigned long long>();
|
||||
d->tags.append(property.contains("data")
|
||||
? SimpleTag(key, property.value("data").value<ByteVector>(),
|
||||
targetTypeValue, language, defaultLanguage, trackUid,
|
||||
editionUid, chapterUid, attachmentUid)
|
||||
: SimpleTag(key, property.value("value").value<String>(),
|
||||
targetTypeValue, language, defaultLanguage, trackUid,
|
||||
editionUid, chapterUid, attachmentUid));
|
||||
setNeedsRender(true);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
158
taglib/matroska/matroskatag.h
Normal file
158
taglib/matroska/matroskatag.h
Normal file
@@ -0,0 +1,158 @@
|
||||
/***************************************************************************
|
||||
* 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 a tag attribute.
|
||||
*/
|
||||
// BIC: Merge with the method above
|
||||
void removeSimpleTag(const String &name, SimpleTag::TargetTypeValue targetTypeValue,
|
||||
unsigned long long trackUid, unsigned long long editionUid,
|
||||
unsigned long long chapterUid = 0, unsigned long long attachmentUid = 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
|
||||
49
taglib/matroska/matroskawritestyle.h
Normal file
49
taglib/matroska/matroskawritestyle.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2026 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_MATROSKAWRITESTYLE_H
|
||||
#define TAGLIB_MATROSKAWRITESTYLE_H
|
||||
|
||||
namespace TagLib::Matroska {
|
||||
/*!
|
||||
* Controls the trade-off between file size and write speed when saving.
|
||||
* Mode of writing tags, attachments and chapters to the file.
|
||||
* For very large files and/or slow (network) filesystems, using
|
||||
* \c AvoidInsert will reduce write time significantly.
|
||||
*/
|
||||
enum class WriteStyle {
|
||||
//! Write tags, attachments and chapters as compact as possible (default).
|
||||
Compact,
|
||||
//! Do not shrink elements; add void padding when content gets smaller.
|
||||
//! Allow inserts when content gets larger.
|
||||
DoNotShrink,
|
||||
//! Like \c DoNotShrink but also avoid inserts for non-last elements:
|
||||
//! replace a growing non-last element with a void of the old size and
|
||||
//! append the new element at the end of the segment.
|
||||
AvoidInsert
|
||||
};
|
||||
}
|
||||
|
||||
#endif //TAGLIB_MATROSKAWRITESTYLE_H
|
||||
@@ -37,7 +37,7 @@ namespace {
|
||||
constexpr std::array containers {
|
||||
"moov", "udta", "mdia", "meta", "ilst",
|
||||
"stbl", "minf", "moof", "traf", "trak",
|
||||
"stsd"
|
||||
"stsd", "stem"
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
AtomList children;
|
||||
};
|
||||
|
||||
MP4::Atom::Atom(File *file)
|
||||
MP4::Atom::Atom(File *file, int depth)
|
||||
: d(std::make_unique<AtomPrivate>(file->tell()))
|
||||
{
|
||||
d->children.setAutoDelete(true);
|
||||
@@ -74,17 +74,7 @@ MP4::Atom::Atom(File *file)
|
||||
}
|
||||
else if(d->length == 1) {
|
||||
// The atom has a 64-bit length.
|
||||
if(const long long longLength = file->readBlock(8).toLongLong();
|
||||
longLength <= LONG_MAX) {
|
||||
// The actual length fits in long. That's always the case if long is 64-bit.
|
||||
d->length = static_cast<long>(longLength);
|
||||
}
|
||||
else {
|
||||
debug("MP4: 64-bit atoms are not supported");
|
||||
d->length = 0;
|
||||
file->seek(0, File::End);
|
||||
return;
|
||||
}
|
||||
d->length = file->readBlock(8).toLongLong();
|
||||
}
|
||||
|
||||
if(d->length < 8 || d->length > file->length() - d->offset) {
|
||||
@@ -96,6 +86,11 @@ MP4::Atom::Atom(File *file)
|
||||
|
||||
d->name = header.mid(4, 4);
|
||||
|
||||
if(d->name == "stem") {
|
||||
file->seek(d->length - 8, File::Current);
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto c : containers) {
|
||||
if(d->name == c) {
|
||||
if(d->name == "meta") {
|
||||
@@ -114,8 +109,13 @@ MP4::Atom::Atom(File *file)
|
||||
else if(d->name == "stsd") {
|
||||
file->seek(8, File::Current);
|
||||
}
|
||||
static constexpr int MAX_MP4_ATOM_DEPTH = 64;
|
||||
if(depth > MAX_MP4_ATOM_DEPTH) {
|
||||
debug("MP4: Maximum nesting depth exceeded");
|
||||
return;
|
||||
}
|
||||
while(file->tell() < d->offset + d->length) {
|
||||
auto child = new MP4::Atom(file);
|
||||
auto child = new MP4::Atom(file, depth + 1);
|
||||
d->children.append(child);
|
||||
if(child->d->length == 0)
|
||||
return;
|
||||
@@ -127,6 +127,11 @@ MP4::Atom::Atom(File *file)
|
||||
file->seek(d->offset + d->length);
|
||||
}
|
||||
|
||||
MP4::Atom::Atom(File *file)
|
||||
: Atom(file, 0)
|
||||
{
|
||||
}
|
||||
|
||||
MP4::Atom::~Atom() = default;
|
||||
|
||||
MP4::Atom *
|
||||
@@ -217,6 +222,8 @@ public:
|
||||
MP4::Atoms::Atoms(File *file) :
|
||||
d(std::make_unique<AtomsPrivate>())
|
||||
{
|
||||
static constexpr int MAX_MP4_ATOM_COUNT_PER_LEVEL = 5000;
|
||||
|
||||
d->atoms.setAutoDelete(true);
|
||||
|
||||
file->seek(0, File::End);
|
||||
@@ -227,6 +234,13 @@ MP4::Atoms::Atoms(File *file) :
|
||||
d->atoms.append(atom);
|
||||
if (atom->length() == 0)
|
||||
break;
|
||||
|
||||
if(d->atoms.size() > MAX_MP4_ATOM_COUNT_PER_LEVEL) {
|
||||
debug("MP4: Maximum atom count exceeded");
|
||||
// Make sure the file is detected as invalid.
|
||||
d->atoms.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,27 +35,48 @@ namespace TagLib {
|
||||
namespace MP4 {
|
||||
|
||||
enum AtomDataType {
|
||||
TypeImplicit = 0, // for use with tags for which no type needs to be indicated because only one type is allowed
|
||||
TypeUTF8 = 1, // without any count or null terminator
|
||||
TypeUTF16 = 2, // also known as UTF-16BE
|
||||
TypeSJIS = 3, // deprecated unless it is needed for special Japanese characters
|
||||
TypeHTML = 6, // the HTML file header specifies which HTML version
|
||||
TypeXML = 7, // the XML header must identify the DTD or schemas
|
||||
TypeUUID = 8, // also known as GUID; stored as 16 bytes in binary (valid as an ID)
|
||||
TypeISRC = 9, // stored as UTF-8 text (valid as an ID)
|
||||
TypeMI3P = 10, // stored as UTF-8 text (valid as an ID)
|
||||
TypeGIF = 12, // (deprecated) a GIF image
|
||||
TypeJPEG = 13, // a JPEG image
|
||||
TypePNG = 14, // a PNG image
|
||||
TypeURL = 15, // absolute, in UTF-8 characters
|
||||
TypeDuration = 16, // in milliseconds, 32-bit integer
|
||||
TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits
|
||||
TypeGenred = 18, // a list of enumerated values
|
||||
TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes
|
||||
TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit integer
|
||||
TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID)
|
||||
TypeBMP = 27, // Windows bitmap image
|
||||
TypeUndefined = 255 // undefined
|
||||
//! For use with tags for which no type needs to be indicated because only one type is allowed
|
||||
TypeImplicit = 0,
|
||||
//! Without any count or null terminator
|
||||
TypeUTF8 = 1,
|
||||
//! Also known as UTF-16BE
|
||||
TypeUTF16 = 2,
|
||||
//! Deprecated unless it is needed for special Japanese characters
|
||||
TypeSJIS = 3,
|
||||
//! The HTML file header specifies which HTML version
|
||||
TypeHTML = 6,
|
||||
//! The XML header must identify the DTD or schemas
|
||||
TypeXML = 7,
|
||||
//! Also known as GUID; stored as 16 bytes in binary (valid as an ID)
|
||||
TypeUUID = 8,
|
||||
//! Stored as UTF-8 text (valid as an ID)
|
||||
TypeISRC = 9,
|
||||
//! Stored as UTF-8 text (valid as an ID)
|
||||
TypeMI3P = 10,
|
||||
//! (Deprecated) A GIF image
|
||||
TypeGIF = 12,
|
||||
//! A JPEG image
|
||||
TypeJPEG = 13,
|
||||
//! A PNG image
|
||||
TypePNG = 14,
|
||||
//! Absolute, in UTF-8 characters
|
||||
TypeURL = 15,
|
||||
//! In milliseconds, 32-bit integer
|
||||
TypeDuration = 16,
|
||||
//! In UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits
|
||||
TypeDateTime = 17,
|
||||
//! A list of enumerated values
|
||||
TypeGenred = 18,
|
||||
//! A signed big-endian integer with length one of { 1,2,3,4,8 } bytes
|
||||
TypeInteger = 21,
|
||||
//! RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit integer
|
||||
TypeRIAAPA = 24,
|
||||
//! Universal Product Code, in text UTF-8 format (valid as an ID)
|
||||
TypeUPC = 25,
|
||||
//! Windows bitmap image
|
||||
TypeBMP = 27,
|
||||
//! Undefined
|
||||
TypeUndefined = 255
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_DOCUMENT
|
||||
@@ -89,6 +110,9 @@ namespace TagLib {
|
||||
const ByteVector &name() const;
|
||||
const AtomList &children() const;
|
||||
|
||||
protected:
|
||||
Atom(File *file, int depth);
|
||||
|
||||
private:
|
||||
class AtomPrivate;
|
||||
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
|
||||
|
||||
89
taglib/mp4/mp4chapter.cpp
Normal file
89
taglib/mp4/mp4chapter.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Ryan Francesconi
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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 "mp4chapter.h"
|
||||
#include "tstring.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
class MP4::Chapter::ChapterPrivate
|
||||
{
|
||||
public:
|
||||
ChapterPrivate() = default;
|
||||
~ChapterPrivate() = default;
|
||||
String title;
|
||||
long long startTime {0};
|
||||
};
|
||||
|
||||
MP4::Chapter::Chapter(const String &title, long long startTime) :
|
||||
d(std::make_unique<ChapterPrivate>())
|
||||
{
|
||||
d->title = title;
|
||||
d->startTime = startTime;
|
||||
}
|
||||
|
||||
MP4::Chapter::Chapter(const Chapter &other) :
|
||||
d(std::make_unique<ChapterPrivate>(*other.d))
|
||||
{
|
||||
}
|
||||
|
||||
MP4::Chapter::Chapter(Chapter &&other) noexcept = default;
|
||||
|
||||
MP4::Chapter::Chapter::~Chapter() = default;
|
||||
|
||||
MP4::Chapter &MP4::Chapter::Chapter::operator=(const Chapter &other)
|
||||
{
|
||||
Chapter(other).swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MP4::Chapter &MP4::Chapter::Chapter::operator=(
|
||||
Chapter &&other) noexcept = default;
|
||||
|
||||
bool MP4::Chapter::operator==(const Chapter &other) const
|
||||
{
|
||||
return title() == other.title() && startTime() == other.startTime();
|
||||
}
|
||||
|
||||
bool MP4::Chapter::operator!=(const Chapter &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
void MP4::Chapter::swap(Chapter &other) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, other.d);
|
||||
}
|
||||
|
||||
const String &MP4::Chapter::title() const
|
||||
{
|
||||
return d->title;
|
||||
}
|
||||
|
||||
long long MP4::Chapter::startTime() const
|
||||
{
|
||||
return d->startTime;
|
||||
}
|
||||
108
taglib/mp4/mp4chapter.h
Normal file
108
taglib/mp4/mp4chapter.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Ryan Francesconi
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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_MP4CHAPTER_H
|
||||
#define TAGLIB_MP4CHAPTER_H
|
||||
|
||||
#include <memory>
|
||||
#include "taglib_export.h"
|
||||
#include "tlist.h"
|
||||
|
||||
namespace TagLib {
|
||||
class String;
|
||||
namespace MP4 {
|
||||
|
||||
/*!
|
||||
* A single Nero-style chapter marker.
|
||||
*/
|
||||
class TAGLIB_EXPORT Chapter {
|
||||
public:
|
||||
/*!
|
||||
* Construct a chapter.
|
||||
*/
|
||||
Chapter(const String &title, long long startTime);
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Returns \c true if the chapter and \a other contain the same data.
|
||||
*/
|
||||
bool operator==(const Chapter &other) const;
|
||||
|
||||
/*!
|
||||
* Returns \c true if the chapter and \a other differ in data.
|
||||
*/
|
||||
bool operator!=(const Chapter &other) const;
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the object with the content of \a other.
|
||||
*/
|
||||
void swap(Chapter &other) noexcept;
|
||||
|
||||
/*!
|
||||
* Returns the title representing the chapter.
|
||||
*/
|
||||
const String &title() const;
|
||||
|
||||
/*!
|
||||
* Returns the start time in milliseconds.
|
||||
*/
|
||||
long long startTime() const;
|
||||
|
||||
private:
|
||||
class ChapterPrivate;
|
||||
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
|
||||
std::unique_ptr<ChapterPrivate> d;
|
||||
};
|
||||
|
||||
//! List of chapters.
|
||||
using ChapterList = List<Chapter>;
|
||||
|
||||
} // namespace MP4
|
||||
} // namespace TagLib
|
||||
|
||||
#endif
|
||||
126
taglib/mp4/mp4chapterholder.h
Normal file
126
taglib/mp4/mp4chapterholder.h
Normal file
@@ -0,0 +1,126 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2006 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_MP4CHAPTERHOLDER_H
|
||||
#define TAGLIB_MP4CHAPTERHOLDER_H
|
||||
|
||||
#include "mp4chapter.h"
|
||||
|
||||
namespace TagLib {
|
||||
class File;
|
||||
namespace MP4 {
|
||||
/*!
|
||||
* Base class to hold chapters and store modified state.
|
||||
*/
|
||||
class ChapterHolder {
|
||||
public:
|
||||
/*!
|
||||
* Get list of chapters.
|
||||
*/
|
||||
ChapterList chapters() const { return chapterList; }
|
||||
|
||||
/*!
|
||||
* Set list of chapters.
|
||||
*/
|
||||
void setChapters(const ChapterList &chapters) { chapterList = chapters; }
|
||||
|
||||
/*!
|
||||
* Returns \c true if the list of chapters has been modified.
|
||||
*/
|
||||
bool isModified() const { return modified; }
|
||||
|
||||
/*!
|
||||
* Set if the contained chapters are modified.
|
||||
*/
|
||||
void setModified(bool chaptersModified) { modified = chaptersModified; }
|
||||
|
||||
protected:
|
||||
ChapterList chapterList;
|
||||
bool modified = false;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Lazily fetch list of chapters.
|
||||
* @tparam T class derived from ChapterHolder and implementing read(File *)
|
||||
* @param holder unique pointer to holder, initially null
|
||||
* @param file file with chapters
|
||||
* @return list of chapters, empty if no chapters found.
|
||||
*/
|
||||
template <typename T>
|
||||
ChapterList getChaptersLazy(std::unique_ptr<T> &holder, TagLib::File *file)
|
||||
{
|
||||
if (!holder) {
|
||||
holder = std::make_unique<T>();
|
||||
holder->read(file);
|
||||
}
|
||||
return holder->chapters();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Lazily set a list of chapters.
|
||||
* @tparam T class derived from ChapterHolder
|
||||
* @param holder unique pointer to holder, initially null
|
||||
* @param chapters list of chapters to set
|
||||
*/
|
||||
template <typename T>
|
||||
void setChaptersLazy(std::unique_ptr<T> &holder, const ChapterList& chapters)
|
||||
{
|
||||
if (!holder) {
|
||||
holder = std::make_unique<T>();
|
||||
// The chapters have not been read before, so we do not know their
|
||||
// current state and mark them as modified. Otherwise, the check below
|
||||
// would not set the chapters if they are empty.
|
||||
holder->setModified(true);
|
||||
}
|
||||
if(holder->isModified() || holder->chapters() != chapters) {
|
||||
holder->setChapters(chapters);
|
||||
holder->setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Save a list of chapters if it has been modified.
|
||||
* @tparam T class derived from ChapterHolder and implementing write(File *)
|
||||
* @param holder unique pointer to holder, initially null
|
||||
* @param file file with chapters
|
||||
* @return true if write successful or not modified.
|
||||
*/
|
||||
template <typename T>
|
||||
bool saveChaptersIfModified(std::unique_ptr<T> &holder, TagLib::File *file)
|
||||
{
|
||||
if(holder && holder->isModified()) {
|
||||
if(holder->write(file)) {
|
||||
holder->setModified(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace MP4
|
||||
} // namespace TagLib
|
||||
|
||||
#endif
|
||||
@@ -30,6 +30,8 @@
|
||||
#include "tagutils.h"
|
||||
|
||||
#include "mp4itemfactory.h"
|
||||
#include "mp4nerochapterlist.h"
|
||||
#include "mp4qtchapterlist.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@@ -48,6 +50,8 @@ public:
|
||||
std::unique_ptr<MP4::Tag> tag;
|
||||
std::unique_ptr<MP4::Atoms> atoms;
|
||||
std::unique_ptr<MP4::Properties> properties;
|
||||
std::unique_ptr<MP4::NeroChapterList> neroChapterList;
|
||||
std::unique_ptr<MP4::QtChapterList> qtChapterList;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -111,6 +115,26 @@ MP4::Properties *MP4::File::audioProperties() const
|
||||
return d->properties.get();
|
||||
}
|
||||
|
||||
MP4::ChapterList MP4::File::neroChapters()
|
||||
{
|
||||
return getChaptersLazy(d->neroChapterList, this);
|
||||
}
|
||||
|
||||
void MP4::File::setNeroChapters(const ChapterList& chapters)
|
||||
{
|
||||
setChaptersLazy(d->neroChapterList, chapters);
|
||||
}
|
||||
|
||||
MP4::ChapterList MP4::File::qtChapters()
|
||||
{
|
||||
return getChaptersLazy(d->qtChapterList, this);
|
||||
}
|
||||
|
||||
void MP4::File::setQtChapters(const ChapterList& chapters)
|
||||
{
|
||||
setChaptersLazy(d->qtChapterList, chapters);
|
||||
}
|
||||
|
||||
void
|
||||
MP4::File::read(bool readProperties)
|
||||
{
|
||||
@@ -148,7 +172,9 @@ MP4::File::save()
|
||||
return false;
|
||||
}
|
||||
|
||||
return d->tag->save();
|
||||
return d->tag->save() &&
|
||||
saveChaptersIfModified(d->neroChapterList, this) &&
|
||||
saveChaptersIfModified(d->qtChapterList, this);
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "mp4tag.h"
|
||||
#include "tag.h"
|
||||
#include "mp4properties.h"
|
||||
#include "mp4chapter.h"
|
||||
|
||||
namespace TagLib {
|
||||
//! An implementation of MP4 (AAC, ALAC, ...) metadata
|
||||
@@ -130,6 +131,26 @@ namespace TagLib {
|
||||
*/
|
||||
Properties *audioProperties() const override;
|
||||
|
||||
/*!
|
||||
* Returns the Nero style chapters for this file.
|
||||
*/
|
||||
ChapterList neroChapters();
|
||||
|
||||
/*!
|
||||
* Sets the Nero style chapters for this file.
|
||||
*/
|
||||
void setNeroChapters(const ChapterList &chapters);
|
||||
|
||||
/*!
|
||||
* Returns the QuickTime chapters for this file.
|
||||
*/
|
||||
ChapterList qtChapters();
|
||||
|
||||
/*!
|
||||
* Sets the QuickTime style chapters for this file.
|
||||
*/
|
||||
void setQtChapters(const ChapterList &chapters);
|
||||
|
||||
/*!
|
||||
* Save the file.
|
||||
*
|
||||
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
StringList m_stringList;
|
||||
ByteVectorList m_byteVectorList;
|
||||
MP4::CoverArtList m_coverArtList;
|
||||
MP4::Stem m_stem;
|
||||
};
|
||||
|
||||
MP4::Item::Item() :
|
||||
@@ -130,6 +131,13 @@ MP4::Item::Item(const MP4::CoverArtList &value) :
|
||||
d->m_coverArtList = value;
|
||||
}
|
||||
|
||||
MP4::Item::Item(const MP4::Stem &value) :
|
||||
d(std::make_shared<ItemPrivate>())
|
||||
{
|
||||
d->type = Type::Stem;
|
||||
d->m_stem = value;
|
||||
}
|
||||
|
||||
void MP4::Item::setAtomDataType(MP4::AtomDataType type)
|
||||
{
|
||||
d->atomDataType = type;
|
||||
@@ -194,6 +202,12 @@ MP4::Item::toCoverArtList() const
|
||||
return d->m_coverArtList;
|
||||
}
|
||||
|
||||
MP4::Stem
|
||||
MP4::Item::toStem() const
|
||||
{
|
||||
return d->m_stem;
|
||||
}
|
||||
|
||||
bool
|
||||
MP4::Item::isValid() const
|
||||
{
|
||||
@@ -234,6 +248,8 @@ bool MP4::Item::operator==(const Item &other) const
|
||||
return toByteVectorList() == other.toByteVectorList();
|
||||
case Type::CoverArtList:
|
||||
return toCoverArtList() == other.toCoverArtList();
|
||||
case Type::Stem:
|
||||
return toStem() == other.toStem();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "tstringlist.h"
|
||||
#include "taglib_export.h"
|
||||
#include "mp4coverart.h"
|
||||
#include "mp4stem.h"
|
||||
|
||||
namespace TagLib {
|
||||
namespace MP4 {
|
||||
@@ -49,7 +50,8 @@ namespace TagLib {
|
||||
LongLong,
|
||||
StringList,
|
||||
ByteVectorList,
|
||||
CoverArtList
|
||||
CoverArtList,
|
||||
Stem,
|
||||
};
|
||||
|
||||
struct IntPair {
|
||||
@@ -80,6 +82,7 @@ namespace TagLib {
|
||||
Item(const StringList &value);
|
||||
Item(const ByteVectorList &value);
|
||||
Item(const CoverArtList &value);
|
||||
Item(const Stem &value);
|
||||
|
||||
void setAtomDataType(AtomDataType type);
|
||||
AtomDataType atomDataType() const;
|
||||
@@ -93,6 +96,7 @@ namespace TagLib {
|
||||
StringList toStringList() const;
|
||||
ByteVectorList toByteVectorList() const;
|
||||
CoverArtList toCoverArtList() const;
|
||||
Stem toStem() const;
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "mp4itemfactory.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "tbytevector.h"
|
||||
@@ -47,6 +48,8 @@ public:
|
||||
NameHandlerMap handlerTypeForName;
|
||||
Map<ByteVector, String> propertyKeyForName;
|
||||
Map<String, ByteVector> nameForPropertyKey;
|
||||
mutable std::once_flag handlerMapOnce;
|
||||
mutable std::once_flag propertyMapsOnce;
|
||||
};
|
||||
|
||||
ItemFactory ItemFactory::factory;
|
||||
@@ -87,6 +90,8 @@ std::pair<String, Item> ItemFactory::parseItem(
|
||||
return parseGnre(atom, data);
|
||||
case ItemHandlerType::Covr:
|
||||
return parseCovr(atom, data);
|
||||
case ItemHandlerType::Stem:
|
||||
return parseStem(atom, data);
|
||||
case ItemHandlerType::TextImplicit:
|
||||
return parseText(atom, data, -1);
|
||||
case ItemHandlerType::Text:
|
||||
@@ -128,6 +133,8 @@ ByteVector ItemFactory::renderItem(
|
||||
return renderInt(name, item);
|
||||
case ItemHandlerType::Covr:
|
||||
return renderCovr(name, item);
|
||||
case ItemHandlerType::Stem:
|
||||
return renderStem(name, item);
|
||||
case ItemHandlerType::TextImplicit:
|
||||
return renderText(name, item, TypeImplicit);
|
||||
case ItemHandlerType::Text:
|
||||
@@ -175,8 +182,8 @@ std::pair<ByteVector, Item> ItemFactory::itemFromProperty(
|
||||
case ItemHandlerType::TextImplicit:
|
||||
case ItemHandlerType::Text:
|
||||
return {name, values};
|
||||
|
||||
case ItemHandlerType::Covr:
|
||||
case ItemHandlerType::Stem:
|
||||
debug("MP4: Invalid item \"" + name + "\" for property");
|
||||
break;
|
||||
case ItemHandlerType::Unknown:
|
||||
@@ -222,6 +229,7 @@ std::pair<String, StringList> ItemFactory::itemToProperty(
|
||||
return {key, item.toStringList()};
|
||||
|
||||
case ItemHandlerType::Covr:
|
||||
case ItemHandlerType::Stem:
|
||||
debug("MP4: Invalid item \"" + itemName + "\" for property");
|
||||
break;
|
||||
case ItemHandlerType::Unknown:
|
||||
@@ -234,9 +242,11 @@ std::pair<String, StringList> ItemFactory::itemToProperty(
|
||||
|
||||
String ItemFactory::propertyKeyForName(const ByteVector &name) const
|
||||
{
|
||||
if(d->propertyKeyForName.isEmpty()) {
|
||||
std::call_once(d->propertyMapsOnce, [this] {
|
||||
d->propertyKeyForName = namePropertyMap();
|
||||
}
|
||||
for(const auto &[k, t] : std::as_const(d->propertyKeyForName))
|
||||
d->nameForPropertyKey[t] = k;
|
||||
});
|
||||
String key = d->propertyKeyForName.value(name);
|
||||
if(key.isEmpty() && name.startsWith(freeFormPrefix)) {
|
||||
key = name.mid(std::size(freeFormPrefix) - 1);
|
||||
@@ -246,14 +256,11 @@ String ItemFactory::propertyKeyForName(const ByteVector &name) const
|
||||
|
||||
ByteVector ItemFactory::nameForPropertyKey(const String &key) const
|
||||
{
|
||||
if(d->nameForPropertyKey.isEmpty()) {
|
||||
if(d->propertyKeyForName.isEmpty()) {
|
||||
d->propertyKeyForName = namePropertyMap();
|
||||
}
|
||||
for(const auto &[k, t] : std::as_const(d->propertyKeyForName)) {
|
||||
std::call_once(d->propertyMapsOnce, [this] {
|
||||
d->propertyKeyForName = namePropertyMap();
|
||||
for(const auto &[k, t] : std::as_const(d->propertyKeyForName))
|
||||
d->nameForPropertyKey[t] = k;
|
||||
}
|
||||
}
|
||||
});
|
||||
ByteVector name = d->nameForPropertyKey.value(key);
|
||||
if(name.isEmpty() && !key.isEmpty()) {
|
||||
const auto &firstChar = key[0];
|
||||
@@ -303,6 +310,7 @@ ItemFactory::NameHandlerMap ItemFactory::nameHandlerMap() const
|
||||
{"akID", ItemHandlerType::Byte},
|
||||
{"gnre", ItemHandlerType::Gnre},
|
||||
{"covr", ItemHandlerType::Covr},
|
||||
{"stem", ItemHandlerType::Stem},
|
||||
{"purl", ItemHandlerType::TextImplicit},
|
||||
{"egid", ItemHandlerType::TextImplicit},
|
||||
};
|
||||
@@ -311,9 +319,9 @@ ItemFactory::NameHandlerMap ItemFactory::nameHandlerMap() const
|
||||
ItemFactory::ItemHandlerType ItemFactory::handlerTypeForName(
|
||||
const ByteVector &name) const
|
||||
{
|
||||
if(d->handlerTypeForName.isEmpty()) {
|
||||
std::call_once(d->handlerMapOnce, [this] {
|
||||
d->handlerTypeForName = nameHandlerMap();
|
||||
}
|
||||
});
|
||||
auto type = d->handlerTypeForName.value(name, ItemHandlerType::Unknown);
|
||||
if (type == ItemHandlerType::Unknown && name.size() == 4) {
|
||||
type = ItemHandlerType::Text;
|
||||
@@ -633,6 +641,12 @@ std::pair<String, Item> ItemFactory::parseCovr(
|
||||
};
|
||||
}
|
||||
|
||||
std::pair<String, Item> ItemFactory::parseStem(
|
||||
const MP4::Atom *atom, const ByteVector &data)
|
||||
{
|
||||
return {atom->name(), Item(Stem(data))};
|
||||
}
|
||||
|
||||
|
||||
ByteVector ItemFactory::renderAtom(
|
||||
const ByteVector &name, const ByteVector &data)
|
||||
@@ -742,6 +756,13 @@ ByteVector ItemFactory::renderCovr(
|
||||
return renderAtom(name, data);
|
||||
}
|
||||
|
||||
ByteVector ItemFactory::renderStem(
|
||||
const ByteVector &name, const MP4::Item &item)
|
||||
{
|
||||
auto data = item.toStem().data();
|
||||
return renderAtom(name, data);
|
||||
}
|
||||
|
||||
ByteVector ItemFactory::renderFreeForm(
|
||||
const String &name, const MP4::Item &item)
|
||||
{
|
||||
|
||||
@@ -142,6 +142,7 @@ namespace TagLib {
|
||||
Byte,
|
||||
Gnre,
|
||||
Covr,
|
||||
Stem,
|
||||
TextImplicit,
|
||||
Text
|
||||
};
|
||||
@@ -214,6 +215,8 @@ namespace TagLib {
|
||||
const MP4::Atom *atom, const ByteVector &bytes);
|
||||
static std::pair<String, Item> parseCovr(
|
||||
const MP4::Atom *atom, const ByteVector &data);
|
||||
static std::pair<String, Item> parseStem(
|
||||
const MP4::Atom *atom, const ByteVector &data);
|
||||
|
||||
// Functions used by renderItem() to render atom data for items.
|
||||
static ByteVector renderAtom(
|
||||
@@ -242,6 +245,8 @@ namespace TagLib {
|
||||
const ByteVector &name, const MP4::Item &item);
|
||||
static ByteVector renderCovr(
|
||||
const ByteVector &name, const MP4::Item &item);
|
||||
static ByteVector renderStem(
|
||||
const ByteVector &name, const MP4::Item &item);
|
||||
|
||||
private:
|
||||
static ItemFactory factory;
|
||||
|
||||
320
taglib/mp4/mp4nerochapterlist.cpp
Normal file
320
taglib/mp4/mp4nerochapterlist.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Ryan Francesconi
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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 "mp4nerochapterlist.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "tdebug.h"
|
||||
#include "mp4file.h"
|
||||
#include "mp4atom.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace
|
||||
{
|
||||
ByteVector renderAtom(const ByteVector &name, const ByteVector &data)
|
||||
{
|
||||
return ByteVector::fromUInt(data.size() + 8) + name + data;
|
||||
}
|
||||
|
||||
// Update parent atom sizes along a path when child size changes by delta.
|
||||
// Mirrors MP4::Tag::updateParents().
|
||||
void updateParentSizes(TagLib::File *file, const MP4::AtomList &path,
|
||||
offset_t delta, int ignore = 0)
|
||||
{
|
||||
if(static_cast<int>(path.size()) <= ignore)
|
||||
return;
|
||||
|
||||
auto itEnd = path.end();
|
||||
std::advance(itEnd, 0 - ignore);
|
||||
|
||||
for(auto it = path.begin(); it != itEnd; ++it) {
|
||||
file->seek((*it)->offset());
|
||||
if(const long size = file->readBlock(4).toUInt(); size == 1) {
|
||||
// 64-bit size
|
||||
file->seek(4, TagLib::File::Current);
|
||||
const long long longSize = file->readBlock(8).toLongLong();
|
||||
file->seek((*it)->offset() + 8);
|
||||
file->writeBlock(ByteVector::fromLongLong(longSize + delta));
|
||||
}
|
||||
else {
|
||||
// 32-bit size
|
||||
file->seek((*it)->offset());
|
||||
file->writeBlock(ByteVector::fromUInt(static_cast<unsigned int>(size + delta)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update stco/co64/tfhd chunk offsets when file content shifts.
|
||||
// Mirrors MP4::Tag::updateOffsets().
|
||||
void updateChunkOffsets(TagLib::File *file, const MP4::Atoms *atoms,
|
||||
offset_t delta, offset_t offset)
|
||||
{
|
||||
if(const MP4::Atom *moov = atoms->find("moov")) {
|
||||
const MP4::AtomList stco = moov->findall("stco", true);
|
||||
for(const auto &atom : stco) {
|
||||
if(atom->offset() > offset)
|
||||
atom->addToOffset(delta);
|
||||
file->seek(atom->offset() + 12);
|
||||
ByteVector data = file->readBlock(atom->length() - 12);
|
||||
unsigned int count = data.toUInt();
|
||||
file->seek(atom->offset() + 16);
|
||||
unsigned int pos = 4;
|
||||
const unsigned int maxPos = data.size() - 4;
|
||||
while(count-- && pos <= maxPos) {
|
||||
auto o = static_cast<offset_t>(data.toUInt(pos));
|
||||
if(o > offset)
|
||||
o += delta;
|
||||
file->writeBlock(ByteVector::fromUInt(static_cast<unsigned int>(o)));
|
||||
pos += 4;
|
||||
}
|
||||
}
|
||||
|
||||
const MP4::AtomList co64 = moov->findall("co64", true);
|
||||
for(const auto &atom : co64) {
|
||||
if(atom->offset() > offset)
|
||||
atom->addToOffset(delta);
|
||||
file->seek(atom->offset() + 12);
|
||||
ByteVector data = file->readBlock(atom->length() - 12);
|
||||
unsigned int count = data.toUInt();
|
||||
file->seek(atom->offset() + 16);
|
||||
unsigned int pos = 4;
|
||||
const unsigned int maxPos = data.size() - 8;
|
||||
while(count-- && pos <= maxPos) {
|
||||
long long o = data.toLongLong(pos);
|
||||
if(o > offset)
|
||||
o += delta;
|
||||
file->writeBlock(ByteVector::fromLongLong(o));
|
||||
pos += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(const MP4::Atom *moof = atoms->find("moof")) {
|
||||
const MP4::AtomList tfhd = moof->findall("tfhd", true);
|
||||
for(const auto &atom : tfhd) {
|
||||
if(atom->offset() > offset)
|
||||
atom->addToOffset(delta);
|
||||
file->seek(atom->offset() + 9);
|
||||
ByteVector data = file->readBlock(atom->length() - 9);
|
||||
if(const unsigned int flags = data.toUInt(0, 3, true);
|
||||
flags & 1) {
|
||||
long long o = data.toLongLong(7U);
|
||||
if(o > offset)
|
||||
o += delta;
|
||||
file->seek(atom->offset() + 16);
|
||||
file->writeBlock(ByteVector::fromLongLong(o));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the binary payload for a chpl atom (version 1).
|
||||
ByteVector renderChplData(const MP4::ChapterList &chapters)
|
||||
{
|
||||
const unsigned int count = std::min(chapters.size(), 255U);
|
||||
|
||||
ByteVector data;
|
||||
// Version (1 byte) + flags (3 bytes) + reserved (4 bytes)
|
||||
data.append(static_cast<char>(0x01)); // version 1
|
||||
data.append(ByteVector(3, '\0')); // flags
|
||||
data.append(ByteVector(4, '\0')); // reserved
|
||||
|
||||
// Chapter count
|
||||
data.append(static_cast<char>(count & 0xFF));
|
||||
|
||||
unsigned int i = 0;
|
||||
for(const auto &ch : chapters) {
|
||||
if(i++ >= count)
|
||||
break;
|
||||
|
||||
// Start time: 8 bytes big-endian, on-disk format is 100-nanosecond units
|
||||
data.append(ByteVector::fromLongLong(ch.startTime() * 10000LL));
|
||||
|
||||
// Title: 1-byte length + UTF-8 bytes (max 255 bytes)
|
||||
ByteVector titleBytes = ch.title().data(String::UTF8);
|
||||
const unsigned int titleLen = std::min(titleBytes.size(), 255U);
|
||||
data.append(static_cast<char>(titleLen & 0xFF));
|
||||
if(titleLen > 0)
|
||||
data.append(titleBytes.mid(0, titleLen));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Parse the binary content of a chpl atom into a ChapterList.
|
||||
MP4::ChapterList parseChplData(const ByteVector &data)
|
||||
{
|
||||
MP4::ChapterList chapters;
|
||||
|
||||
// Minimum: version(1) + flags(3) + count(1) = 5 bytes (version 0 layout)
|
||||
if(data.size() < 5)
|
||||
return chapters;
|
||||
|
||||
unsigned int pos = 0;
|
||||
const auto version = static_cast<unsigned char>(data[pos++]);
|
||||
|
||||
// Skip flags (3 bytes)
|
||||
pos += 3;
|
||||
|
||||
// Version 1 has 4 reserved bytes
|
||||
if(version >= 1)
|
||||
pos += 4;
|
||||
|
||||
if(pos >= data.size())
|
||||
return chapters;
|
||||
|
||||
const unsigned int count = static_cast<unsigned char>(data[pos++]);
|
||||
|
||||
for(unsigned int i = 0; i < count && pos + 9 <= data.size(); ++i) {
|
||||
const long long startTime100ns = data.toLongLong(pos);
|
||||
pos += 8;
|
||||
|
||||
const unsigned int titleLen = static_cast<unsigned char>(data[pos++]);
|
||||
|
||||
String title;
|
||||
if(titleLen > 0 && pos + titleLen <= data.size()) {
|
||||
title = String(data.mid(pos, titleLen), String::UTF8);
|
||||
pos += titleLen;
|
||||
}
|
||||
|
||||
chapters.append(MP4::Chapter(title, startTime100ns / 10000LL));
|
||||
}
|
||||
|
||||
return chapters;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool MP4::NeroChapterList::read(TagLib::File *file)
|
||||
{
|
||||
const Atoms atoms(file);
|
||||
|
||||
const Atom *chpl = atoms.find("moov", "udta", "chpl");
|
||||
modified = false;
|
||||
chapterList.clear();
|
||||
if(chpl) {
|
||||
// Read the atom content (skip 8-byte atom header)
|
||||
file->seek(chpl->offset() + 8);
|
||||
const ByteVector data = file->readBlock(chpl->length() - 8);
|
||||
|
||||
chapterList = parseChplData(data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MP4::NeroChapterList::write(TagLib::File *file)
|
||||
{
|
||||
// Writing an empty list is equivalent to removing the chapters.
|
||||
if(chapterList.isEmpty())
|
||||
return remove(file);
|
||||
|
||||
const Atoms atoms(file);
|
||||
|
||||
if(!atoms.find("moov")) {
|
||||
debug("MP4ChapterList::write() -- No moov atom found");
|
||||
return false;
|
||||
}
|
||||
|
||||
const ByteVector chplPayload = renderChplData(chapterList);
|
||||
const ByteVector chplAtom = renderAtom("chpl", chplPayload);
|
||||
|
||||
if(const Atom *existingChpl = atoms.find("moov", "udta", "chpl")) {
|
||||
// Replace existing chpl atom
|
||||
const offset_t offset = existingChpl->offset();
|
||||
const offset_t oldLength = existingChpl->length();
|
||||
const offset_t delta = static_cast<offset_t>(chplAtom.size()) - oldLength;
|
||||
|
||||
file->insert(chplAtom, offset, oldLength);
|
||||
|
||||
if(delta != 0) {
|
||||
// Update parent sizes: moov and udta
|
||||
const AtomList parentPath = atoms.path("moov", "udta", "chpl");
|
||||
updateParentSizes(file, parentPath, delta, 1); // ignore chpl itself
|
||||
updateChunkOffsets(file, &atoms, delta, offset);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Need to insert a new chpl atom
|
||||
|
||||
if(AtomList udtaPath = atoms.path("moov", "udta"); udtaPath.size() == 2) {
|
||||
// udta exists -- insert chpl at the beginning of udta's content
|
||||
const offset_t insertOffset = udtaPath.back()->offset() + 8;
|
||||
file->insert(chplAtom, insertOffset, 0);
|
||||
|
||||
updateParentSizes(file, udtaPath, chplAtom.size());
|
||||
updateChunkOffsets(file, &atoms, chplAtom.size(), insertOffset);
|
||||
}
|
||||
else {
|
||||
// No udta -- insert udta + chpl at the beginning of moov's content
|
||||
const ByteVector udtaAtom = renderAtom("udta", chplAtom);
|
||||
|
||||
AtomList moovPath = atoms.path("moov");
|
||||
if(moovPath.isEmpty()) {
|
||||
debug("MP4ChapterList::write() -- No moov atom in path");
|
||||
return false;
|
||||
}
|
||||
|
||||
const offset_t insertOffset = moovPath.back()->offset() + 8;
|
||||
file->insert(udtaAtom, insertOffset, 0);
|
||||
|
||||
updateParentSizes(file, moovPath, udtaAtom.size());
|
||||
updateChunkOffsets(file, &atoms, udtaAtom.size(), insertOffset);
|
||||
}
|
||||
}
|
||||
|
||||
modified = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MP4::NeroChapterList::remove(TagLib::File *file)
|
||||
{
|
||||
const Atoms atoms(file);
|
||||
chapterList.clear();
|
||||
modified = false;
|
||||
|
||||
const Atom *chpl = atoms.find("moov", "udta", "chpl");
|
||||
if(!chpl) {
|
||||
// No chpl atom -- nothing to remove
|
||||
return true;
|
||||
}
|
||||
|
||||
const offset_t offset = chpl->offset();
|
||||
const offset_t length = chpl->length();
|
||||
|
||||
file->removeBlock(offset, length);
|
||||
|
||||
// Update parent sizes with negative delta
|
||||
const AtomList parentPath = atoms.path("moov", "udta", "chpl");
|
||||
updateParentSizes(file, parentPath, -length, 1); // ignore chpl itself
|
||||
updateChunkOffsets(file, &atoms, -length, offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
66
taglib/mp4/mp4nerochapterlist.h
Normal file
66
taglib/mp4/mp4nerochapterlist.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Ryan Francesconi
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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_MP4CHAPTERLIST_H
|
||||
#define TAGLIB_MP4CHAPTERLIST_H
|
||||
|
||||
#include "mp4chapterholder.h"
|
||||
|
||||
namespace TagLib {
|
||||
class File;
|
||||
namespace MP4 {
|
||||
|
||||
/*!
|
||||
* Reads, writes, and removes Nero-style chapter markers (chpl atom)
|
||||
* from MP4 files. Operates independently of MP4::Tag -- the chpl atom
|
||||
* lives at moov/udta/chpl, a sibling of the metadata ilst path.
|
||||
*/
|
||||
class NeroChapterList : public ChapterHolder
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Reads chapter markers from the already-opened \a file.
|
||||
* Returns \c false if the file has no chpl atom.
|
||||
*/
|
||||
bool read(TagLib::File *file);
|
||||
|
||||
/*!
|
||||
* Writes chapter markers to the already-opened \a file,
|
||||
* replacing any existing chpl atom.
|
||||
* The chapter count is capped at 255 (Nero format limit).
|
||||
* Returns \c true on success.
|
||||
*/
|
||||
bool write(TagLib::File *file);
|
||||
|
||||
/*!
|
||||
* Removes the chpl atom from the already-opened \a file.
|
||||
* Returns \c true on success, or if no chpl atom exists.
|
||||
*/
|
||||
bool remove(TagLib::File *file);
|
||||
};
|
||||
|
||||
} // namespace MP4
|
||||
} // namespace TagLib
|
||||
|
||||
#endif
|
||||
@@ -224,7 +224,7 @@ MP4::Properties::read(File *file, const Atoms *atoms)
|
||||
pos += 10;
|
||||
if(const unsigned int bitrateValue = data.toUInt(pos);
|
||||
bitrateValue != 0 || d->length <= 0) {
|
||||
d->bitrate = static_cast<int>((bitrateValue + 500) / 1000.0 + 0.5);
|
||||
d->bitrate = static_cast<int>(bitrateValue / 1000.0 + 0.5);
|
||||
}
|
||||
else {
|
||||
d->bitrate = static_cast<int>(
|
||||
|
||||
1307
taglib/mp4/mp4qtchapterlist.cpp
Normal file
1307
taglib/mp4/mp4qtchapterlist.cpp
Normal file
File diff suppressed because it is too large
Load Diff
78
taglib/mp4/mp4qtchapterlist.h
Normal file
78
taglib/mp4/mp4qtchapterlist.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Ryan Francesconi
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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_MP4QTCHAPTERLIST_H
|
||||
#define TAGLIB_MP4QTCHAPTERLIST_H
|
||||
|
||||
#include "mp4chapterholder.h"
|
||||
|
||||
namespace TagLib {
|
||||
class File;
|
||||
namespace MP4 {
|
||||
|
||||
/*!
|
||||
* Reads, writes, and removes QuickTime-style chapter tracks from MP4
|
||||
* files. A QT chapter track is a disabled text track (\c hdlr type
|
||||
* \c "text") referenced by a \c chap track-reference in the audio
|
||||
* track's \c tref box. This format is understood by QuickTime,
|
||||
* iTunes, Final Cut, Logic, DaVinci Resolve, Twisted Wave, and most
|
||||
* other Apple/macOS software.
|
||||
*
|
||||
* The existing \c MP4ChapterList class handles Nero-style \c chpl
|
||||
* atoms, which are a different (and less widely supported) chapter
|
||||
* format.
|
||||
*
|
||||
* Chapter times use the same 100-nanosecond unit convention as
|
||||
* \c MP4ChapterList so that existing \c Chapter / \c ChapterList
|
||||
* types can be shared.
|
||||
*/
|
||||
class QtChapterList : public ChapterHolder
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Reads chapter markers from the QuickTime chapter track in the
|
||||
* already-opened \a file.
|
||||
* Returns \c false if the file has no chapter track.
|
||||
*/
|
||||
bool read(TagLib::File *file);
|
||||
|
||||
/*!
|
||||
* Writes chapter markers as a QuickTime chapter track to the
|
||||
* already-opened \a file, replacing any existing chapter track.
|
||||
* Returns \c true on success.
|
||||
*/
|
||||
bool write(TagLib::File *file);
|
||||
|
||||
/*!
|
||||
* Removes the QuickTime chapter track and its \c tref/chap
|
||||
* reference from the already-opened \a file.
|
||||
* Returns \c true on success, or if no chapter track exists.
|
||||
*/
|
||||
bool remove(TagLib::File *file);
|
||||
};
|
||||
|
||||
} // namespace MP4
|
||||
} // namespace TagLib
|
||||
|
||||
#endif
|
||||
64
taglib/mp4/mp4stem.cpp
Normal file
64
taglib/mp4/mp4stem.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Antoine Colombier
|
||||
email : antoine@mixxx.org
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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 "mp4stem.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
MP4::Stem::Stem(const ByteVector &data) :
|
||||
d(std::make_shared<StemPrivate>())
|
||||
{
|
||||
d->data = data;
|
||||
}
|
||||
|
||||
MP4::Stem::Stem() = default;
|
||||
MP4::Stem::Stem(const Stem &) = default;
|
||||
MP4::Stem &MP4::Stem::operator=(const Stem &) = default;
|
||||
|
||||
void
|
||||
MP4::Stem::swap(Stem &item) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
|
||||
swap(d, item.d);
|
||||
}
|
||||
|
||||
MP4::Stem::~Stem() = default;
|
||||
|
||||
ByteVector
|
||||
MP4::Stem::data() const
|
||||
{
|
||||
return d->data;
|
||||
}
|
||||
|
||||
bool MP4::Stem::operator==(const Stem &other) const
|
||||
{
|
||||
return data() == other.data();
|
||||
}
|
||||
|
||||
bool MP4::Stem::operator!=(const Stem &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
78
taglib/mp4/mp4stem.h
Normal file
78
taglib/mp4/mp4stem.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/**************************************************************************
|
||||
copyright : (C) 2026 by Antoine Colombier
|
||||
email : antoine@mixxx.org
|
||||
**************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* 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_MP4STEM_H
|
||||
#define TAGLIB_MP4STEM_H
|
||||
|
||||
#include "tbytevector.h"
|
||||
#include "taglib_export.h"
|
||||
|
||||
namespace TagLib::MP4 {
|
||||
//! STEM
|
||||
class StemPrivate
|
||||
{
|
||||
public:
|
||||
ByteVector data;
|
||||
};
|
||||
|
||||
class TAGLIB_EXPORT Stem
|
||||
{
|
||||
public:
|
||||
Stem();
|
||||
explicit Stem(const ByteVector &data);
|
||||
~Stem();
|
||||
|
||||
Stem(const Stem &item);
|
||||
|
||||
/*!
|
||||
* Copies the contents of \a item into this Stem.
|
||||
*/
|
||||
Stem &operator=(const Stem &item);
|
||||
|
||||
/*!
|
||||
* Exchanges the content of the Stem with the content of \a item.
|
||||
*/
|
||||
void swap(Stem &item) noexcept;
|
||||
|
||||
//! The Stem data
|
||||
ByteVector data() const;
|
||||
|
||||
/*!
|
||||
* Returns \c true if the Stem and \a other contain the same data.
|
||||
*/
|
||||
bool operator==(const Stem &other) const;
|
||||
|
||||
/*!
|
||||
* Returns \c true if the Stem and \a other have different data.
|
||||
*/
|
||||
bool operator!=(const Stem &other) const;
|
||||
|
||||
private:
|
||||
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
|
||||
std::shared_ptr<StemPrivate> d;
|
||||
};
|
||||
} // namespace TagLib::MP4
|
||||
|
||||
#endif
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "mp4itemfactory.h"
|
||||
#include "mp4atom.h"
|
||||
#include "mp4coverart.h"
|
||||
#include "mp4stem.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@@ -65,15 +66,22 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms,
|
||||
d->atoms = atoms;
|
||||
|
||||
const MP4::Atom *ilst = atoms->find("moov", "udta", "meta", "ilst");
|
||||
if(!ilst) {
|
||||
//debug("Atom moov.udta.meta.ilst not found.");
|
||||
return;
|
||||
if(ilst) {
|
||||
for(const auto &atom : ilst->children()) {
|
||||
file->seek(atom->offset() + 8);
|
||||
ByteVector data = d->file->readBlock(atom->length() - 8);
|
||||
if(const auto &[name, itm] = d->factory->parseItem(atom, data);
|
||||
itm.isValid()) {
|
||||
addItem(name, itm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto &atom : ilst->children()) {
|
||||
file->seek(atom->offset() + 8);
|
||||
ByteVector data = d->file->readBlock(atom->length() - 8);
|
||||
if(const auto &[name, itm] = d->factory->parseItem(atom, data);
|
||||
const MP4::Atom *stem = atoms->find("moov", "udta", "stem");
|
||||
if(stem) {
|
||||
file->seek(stem->offset() + 8);
|
||||
ByteVector data = d->file->readBlock(stem->length() - 8);
|
||||
if(const auto &[name, itm] = d->factory->parseItem(stem, data);
|
||||
itm.isValid()) {
|
||||
addItem(name, itm);
|
||||
}
|
||||
@@ -100,18 +108,33 @@ MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) const
|
||||
bool
|
||||
MP4::Tag::save()
|
||||
{
|
||||
ByteVector data;
|
||||
ByteVector ilstData, stemData;
|
||||
for(const auto &[name, itm] : std::as_const(d->items)) {
|
||||
data.append(d->factory->renderItem(name, itm));
|
||||
if(name == "stem"){
|
||||
stemData.append(d->factory->renderItem(name, itm));
|
||||
} else {
|
||||
ilstData.append(d->factory->renderItem(name, itm));
|
||||
}
|
||||
}
|
||||
data = renderAtom("ilst", data);
|
||||
ilstData = renderAtom("ilst", ilstData);
|
||||
|
||||
AtomList path = d->atoms->path("moov", "udta", "meta", "ilst");
|
||||
if(path.size() == 4) {
|
||||
saveExisting(data, path);
|
||||
saveExisting(ilstData, path);
|
||||
}
|
||||
else {
|
||||
saveNew(data);
|
||||
ByteVector metaData = renderAtom("meta", ByteVector(4, '\0') +
|
||||
renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") +
|
||||
ByteVector(9, '\0')) +
|
||||
ilstData + padIlst(ilstData));
|
||||
saveNew(metaData);
|
||||
}
|
||||
|
||||
path = d->atoms->path("moov", "udta", "stem");
|
||||
if(path.size() == 3) {
|
||||
saveExisting(stemData, path);
|
||||
} else if (!stemData.isEmpty()) {
|
||||
saveNew(stemData);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -127,6 +150,11 @@ MP4::Tag::strip()
|
||||
saveExisting(ByteVector(), path);
|
||||
}
|
||||
|
||||
path = d->atoms->path("moov", "udta", "stem");
|
||||
if(path.size() == 3) {
|
||||
saveExisting(ByteVector(), path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -172,7 +200,8 @@ MP4::Tag::updateOffsets(offset_t delta, offset_t offset)
|
||||
unsigned int count = data.toUInt();
|
||||
d->file->seek(atom->offset() + 16);
|
||||
unsigned int pos = 4;
|
||||
while(count--) {
|
||||
const unsigned int maxPos = data.size() - 4;
|
||||
while(count-- && pos <= maxPos) {
|
||||
auto o = static_cast<offset_t>(data.toUInt(pos));
|
||||
if(o > offset) {
|
||||
o += delta;
|
||||
@@ -192,7 +221,8 @@ MP4::Tag::updateOffsets(offset_t delta, offset_t offset)
|
||||
unsigned int count = data.toUInt();
|
||||
d->file->seek(atom->offset() + 16);
|
||||
unsigned int pos = 4;
|
||||
while(count--) {
|
||||
const unsigned int maxPos = data.size() - 8;
|
||||
while(count-- && pos <= maxPos) {
|
||||
long long o = data.toLongLong(pos);
|
||||
if(o > offset) {
|
||||
o += delta;
|
||||
@@ -227,11 +257,6 @@ MP4::Tag::updateOffsets(offset_t delta, offset_t offset)
|
||||
void
|
||||
MP4::Tag::saveNew(ByteVector data)
|
||||
{
|
||||
data = renderAtom("meta", ByteVector(4, '\0') +
|
||||
renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") +
|
||||
ByteVector(9, '\0')) +
|
||||
data + padIlst(data));
|
||||
|
||||
AtomList path = d->atoms->path("moov", "udta");
|
||||
if(path.size() != 2) {
|
||||
path = d->atoms->path("moov");
|
||||
@@ -510,13 +535,17 @@ StringList MP4::Tag::complexPropertyKeys() const
|
||||
if(d->items.contains("covr")) {
|
||||
keys.append("PICTURE");
|
||||
}
|
||||
if(d->items.contains("stem")) {
|
||||
keys.append("STEM");
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
List<VariantMap> MP4::Tag::complexProperties(const String &key) const
|
||||
{
|
||||
List<VariantMap> props;
|
||||
if(const String uppercaseKey = key.upper(); uppercaseKey == "PICTURE") {
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
const CoverArtList pictures = d->items.value("covr").toCoverArtList();
|
||||
for(const CoverArt &picture : pictures) {
|
||||
String mimeType = "image/";
|
||||
@@ -543,12 +572,20 @@ List<VariantMap> MP4::Tag::complexProperties(const String &key) const
|
||||
props.append(property);
|
||||
}
|
||||
}
|
||||
else if(uppercaseKey == "STEM" && d->items.contains("stem")) {
|
||||
const Stem stem = d->items.value("stem").toStem();
|
||||
|
||||
VariantMap property;
|
||||
property.insert("manifest", stem.data());
|
||||
props.append(property);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
bool MP4::Tag::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
if(const String uppercaseKey = key.upper(); uppercaseKey == "PICTURE") {
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
CoverArtList pictures;
|
||||
for(const auto &property : value) {
|
||||
auto mimeType = property.value("mimeType").value<String>();
|
||||
@@ -568,6 +605,13 @@ bool MP4::Tag::setComplexProperties(const String &key, const List<VariantMap> &v
|
||||
}
|
||||
d->items["covr"] = pictures;
|
||||
}
|
||||
else if(uppercaseKey == "STEM") {
|
||||
if (!value.isEmpty()) {
|
||||
d->items["stem"] = Stem(value.front().value("manifest").value<ByteVector>());
|
||||
} else {
|
||||
d->items.erase("stem");
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user