75 Commits

Author SHA1 Message Date
Urs Fleisch
78298769de Version 2.2.1 2026-03-07 06:41:13 +01:00
Urs Fleisch
c43d2b3fc1 Avoid duplicates in StringList Matroska::Tag::complexPropertyKeys()
When using for example

examples/tagwriter -C GENRE \
"name=GENRE,targetTypeValue=50,value=Soft Rock;name=GENRE,targetTypeValue=50,value=Classic Rock" \
path/to/file.mka

the GENRE key was included twice and tagreader displayed the two genre
tags twice.
2026-02-28 07:51:04 +01:00
Urs Fleisch
3db0d48f4b Support edition, chapter and attachment UIDs in Matroska simple tags (#1311) 2026-02-24 06:53:23 +01:00
Urs Fleisch
de6e2b15c8 Version 2.2 2026-02-18 05:15:14 +01:00
Urs Fleisch
b7f0225f0d Do not allow unknown frames embedded in CTOC and CHAP (#1306)
This prevents the parsing of frames with specially constructed recursive
frame hierarchies from taking an extremely long time.
2026-02-15 08:38:14 +01:00
tsdgeos
f4117f873c wavpack: Fix infinite loop when reading broken files (#1304)
A crafted file can have blockSamples set to 0 and a blockSize so big
that when adding 8 it overflows and offset is 0 so it goes back to the
same position and loops forever
2026-02-04 17:31:59 +01:00
Damien Plisson
cf86f20b36 Add DSD information to WavPack properties (#1303) 2026-02-04 17:31:03 +01:00
Urs Fleisch
74d93db166 Update changelog for v2.2beta 2026-02-01 12:08:59 +01:00
Stephen Booth
397b6c1de3 Add check for ID3v2 frame data length (#1300)
Also fix some wrong frame sizes in the unit tests.

---------

Co-authored-by: Urs Fleisch <ufleisch@users.sourceforge.net>
2026-01-31 08:24:54 +01:00
Stephen Booth
51f431c96a Verify the ID3v2 version and revision are not 0xFF (#1301) 2026-01-31 08:21:17 +01:00
Antoine Colombier
11e3eb05bd MP4: Add support for NI STEM (#1299) 2026-01-25 13:33:54 +01:00
Urs Fleisch
2c01b63433 Merge pull request #1149 from complexlogic/matroska
Another Matroska Attempt
2026-01-24 15:27:22 +01:00
Urs Fleisch
d83d751443 Matroska: Fix build with older macOS and 32-bit Android compilers
When building for macOS < 10.14, the API for std::optional and
std::variant is restricted

    error: 'value' is unavailable: introduced in macOS 10.14
    error: 'get<..>' is unavailable: introduced in macOS 10.14

There was also an issue with Android armeabi-v7a where long is not
64 bit and a static assertion failed.
2026-01-24 14:59:10 +01:00
Urs Fleisch
f4e7a742c3 Improve Matroska API
Make AttachedFile immutable. This is consistent with SimpleTag and
Chapter and avoids using attached files which do not have all required
attributes.
Provide methods to insert and remove a single simple tag, so that
they can be modified without setting all of them while still not
exposing internal lists to the API.
Use DATE_RECORDED instead of DATE_RELEASED for year() and the "DATE"
property. This is more consistent with other tag formats, e.g. for ID3v2
"TDRC" is used, which is the recording time.
2026-01-24 14:59:10 +01:00
Urs Fleisch
68a514f4a0 Matroska: Avoid inlined header methods, only install public headers 2026-01-24 14:59:10 +01:00
Urs Fleisch
607cd5bdf4 Matroska: Fix issues reported by static analysis, formatting, docs 2026-01-24 14:59:10 +01:00
Urs Fleisch
b625be5690 Support for Matroska chapters 2026-01-24 14:59:10 +01:00
Urs Fleisch
d2bf7e72d2 Unit tests for Matroska 2026-01-24 14:59:10 +01:00
Urs Fleisch
280d6306d2 Update property mapping documentation for Matroska 2026-01-24 14:59:10 +01:00
Urs Fleisch
aef78068b3 Render when tags are modified 2026-01-24 14:59:10 +01:00
Urs Fleisch
81815e1091 Include doc type and version in properties 2026-01-24 14:59:10 +01:00
Urs Fleisch
b9122afaca Preserve track UID in simple tags
Some encoders write track specific DURATION tags, which should not be
removed.
2026-01-24 14:59:10 +01:00
Urs Fleisch
1d3a375765 Use segment title as fallback for returned tag title
Some applications like handbrake store the title only in the segment
info element.
2026-01-24 14:59:10 +01:00
Urs Fleisch
241b3b2921 Remove offset listeners, they are not used 2026-01-24 14:59:10 +01:00
Urs Fleisch
48104959b2 Integrate cues, fix element rendering and resizing 2026-01-24 14:59:10 +01:00
Urs Fleisch
c817cc0a47 tagwriter: Support setting of complex properties
A complex property can be set with

-C <complex-property-key> <key1=val1,key2=val2,...>

The second parameter can be set to "" to delete complex properties with the
given key. The set complex property values, a simple shorthand syntax can be
used. Multiple maps are separated by ';', values within a map are assigned
with key=value and separated by a ','. Types are automatically detected,
double quotes can be used to force a string. A ByteVector can be constructed
from the contents of a file with the path is given after "file://". There is
no escape, but hex codes are supported, e.g. "\x2C" to include a ',' and \x3B
to include a ';'.

Examples:

Set a GEOB frame in an ID3v2 tag:
examples/tagwriter -C GENERALOBJECT \
  'data=file://file.bin,description=My description,fileName=file.bin,mimeType=application/octet-stream' \
  file.mp3

Set an APIC frame in an ID3v2 tag (same as -p file.jpg 'My description'):
examples/tagwriter -C PICTURE \
  'data=file://file.jpg,description=My description,pictureType=Front Cover,mimeType=image/jpeg' \
  file.mp3

Set an attached file in a Matroska file:
examples/tagwriter -C file.bin \
  'fileName=file.bin,data=file://file.bin,mimeType=application/octet-stream' \
  file.mka

Set simple tag with target type in a Matroska file:
examples/tagwriter -C PART_NUMBER \
  name=PART_NUMBER,targetTypeValue=20,value=2 file.mka

Set simple tag with binary value in a Matroska file:
examples/tagwriter -C BINARY \
  name=BINARY,data=file://file.bin,targetTypeValue=60 file.mka
2026-01-24 14:59:10 +01:00
Urs Fleisch
7a5a10102e Set Matroska tags which are not in the property map using complex properties
Also all attached files can be accessed and modified using complex properties.
2026-01-24 14:59:10 +01:00
Urs Fleisch
d47d28f0f8 Fix and simplify Matroska simple tag
Avoid use of raw pointers, fix property interface.
2026-01-24 14:59:10 +01:00
Urs Fleisch
3566b00596 Clean up attachments
Avoid use of raw pointers.
2026-01-24 14:59:10 +01:00
Urs Fleisch
98bc68d16e Implement complex properties for Matroska 2026-01-24 14:59:10 +01:00
Urs Fleisch
8d02484804 Replace raw pointers by smart pointers
Also improve type safety and consistency.
2026-01-24 14:59:10 +01:00
Urs Fleisch
cfade6d082 Implement Matroska audio properties 2026-01-24 14:59:10 +01:00
Urs Fleisch
f7ab162514 Make tags and properties more flexible 2026-01-24 14:59:10 +01:00
Urs Fleisch
5a6f1f96f8 Apply TagLib code formatting, fix static analysis issues 2026-01-24 14:59:10 +01:00
Urs Fleisch
6fa3db1e51 C bindings, fileref, fix warnings 2026-01-24 14:59:10 +01:00
Urs Fleisch
4546c00417 Compile time configuration WITH_MATROSKA 2026-01-24 14:59:10 +01:00
complexlogic
f94843614f Initial cues work 2026-01-24 14:59:10 +01:00
complexlogic
975eaac3ca Fix seekhead 2026-01-24 14:59:10 +01:00
complexlogic
6d019a894c Initial attachments support 2026-01-24 14:59:10 +01:00
complexlogic
6342f00e8b Support tag language 2026-01-24 14:59:10 +01:00
complexlogic
b4e79a4a27 Fix Clang build 2026-01-24 14:59:10 +01:00
complexlogic
80837485cf Added write support 2026-01-24 14:59:10 +01:00
complexlogic
47e9b9a17c Initial matroska support 2026-01-24 14:59:10 +01:00
Stefan Brüns
8c7d3368da Fix reading of last page in ogg stream
When trying to read a packet from the last page the readPages() method
would return false for all packets on the last page.

Move the lastPage check just before trying to create the next page, and
after the packetIndex check of the last fetched page.
2026-01-18 20:08:09 +01:00
Stefan Brüns
b4a4b42254 Avoid corrupting an (invalid) FLAC Ogg file without Vorbis comment
FLAC in Ogg must have a Vorbis comment Metadata block, but in case
the file still has none avoid overwriting the first packet, i.e.
the Streaminfo Metadata block.
2026-01-18 20:08:09 +01:00
Stefan Brüns
70c6a0c75f Set last header flag in FLAC Metadata block type field
In case the Vorbis comment block is the last one the last block flag
has to be set.

As the other metadata blocks are kept as is, and in original order,
and no other metadata blocks are added/inserted, these can be kept
as is.

Also warn if the header scan loop detects a frame sync sequence (which
is the head of the first non-header packet). Previously, this was
detected as the last header packet, but streamStart and streamLength
are unused, and all packets but the vorbis comment packet are just copied
in order.

See taglib#1297
2026-01-18 20:08:09 +01:00
Stefan Brüns
c67e434939 Do not warn when seeing FLAC picture block in Ogg files 2026-01-18 20:08:09 +01:00
Urs Fleisch
5d44f3eeac MP4: Support 64-bit atoms on Windows
The check for the size of long is no longer needed because since
TagLib 2.0 the related fields are 64-bit on Windows too.

Related to https://bugs.kde.org/show_bug.cgi?id=513531
2026-01-11 20:24:33 +01:00
dependabot[bot]
9c042984d2 Bump actions/checkout from 5 to 6 (#1294)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 06:57:09 -06:00
Rosen Penev
49be371caa std::accumulate conversion
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2025-10-25 13:00:24 +02:00
Andreas Sturmlechner
cbb3de6836 Fix cmake_minimum_required version range typo
While CMake appears to accept the ".." notation, it does not correctly
apply the policy_max version in that case.

Amends ab31d11c8d

Signed-off-by: Andreas Sturmlechner <asturm@gentoo.org>
2025-10-22 16:37:21 +02:00
Rosen Penev
11efd2dd10 std::none_of conversion
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2025-10-21 07:17:13 +02:00
Andreas Sturmlechner
ab31d11c8d Raise CMake minimum version to 3.10..3.31 range
CMake 3.10 was released in 2017.

Amends 967aaf7af2, fixes warning since CMake-3.31:

CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required):
  Compatibility with CMake < 3.10 will be removed from a future version of
  CMake.

  Update the VERSION argument <min> value.  Or, use the <min>...<max> syntax
  to tell CMake that the project requires at least <min> but has been updated
  to work with policies introduced by <max> or earlier.

Signed-off-by: Andreas Sturmlechner <asturm@gentoo.org>
2025-10-18 17:00:09 +02:00
Artem Umerov
3c917caf52 Fix std::ostream not defined on Android NDK 29.0.14033849
Fix std::ostream not defined on Android NDK 29.0.14033849
2025-09-04 22:16:59 +02:00
dependabot[bot]
fa96a6458e Bump actions/checkout from 4 to 5 (#1283)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 07:03:51 -05:00
norma
e831f0929f Fix Unicode property keys in C bindings
The C bindings would convert a char* to String using the default
constructor, which uses the Latin1 encoding, breaking when a key
contains a Unicode character (e.g. an ID3v2 comment description).
2025-07-04 09:43:34 +02:00
Urs Fleisch
7d86716194 Version 2.1.1 2025-06-29 17:35:45 +02:00
Urs Fleisch
eb749ac55b Use ldexpl() instead of ldexp()
This will fix "warning: conversion from ‘long double’ to
‘double’ may change value [-Wfloat-conversion]".
2025-06-25 22:08:09 +02:00
Urs Fleisch
148cc9a921 Fix conversion compiler warnings
Using a release build with GCC 14.2.0 and -Wextra -Wconversion -Wall.
The generated binaries are not changed by this commit.
2025-06-25 22:08:09 +02:00
Urs Fleisch
88d6b18b4f Convert IPLS to TIPL and TMCL (#1274)
The involvement/involvee pairs which are supported for TIPL properties
(ARRANGER, ENGINEER, PRODUCER, DJ-MIX, MIX) are left in the TIPL
frame, other pairs are moved to a TMCL frame. This will result in a
consistent behavior for both ID3v2.3 and ID3v2.4 tags produced by
MusicBrainz Picard.
2025-06-21 10:45:43 +02:00
Urs Fleisch
d61a333f27 Map lowercase MusicBrainz TIPL keys to properties (#1274) 2025-06-21 10:45:43 +02:00
Urs Fleisch
e73517d058 CMake targets with TAGLIB_STATIC compile definition (#1277) 2025-06-20 10:19:01 +02:00
Urs Fleisch
6563ceaafa C bindings: Include missing wchar header in tag_c.h (#1273)
In C++, wchar_t is a built-in fundamental type, but in C, it is not.
Compilation with MinGW will fail without a header included which
defines wchar_t (wchar.h or stddef.h). The other compilers used in our
CI (gcc on Ubuntu, clang on macOS and MSVC on Windows) seem to
know wchar_t without an include.
2025-06-14 08:53:30 +02:00
Urs Fleisch
c57431e903 GitHub Actions: Build with MinGW 2025-06-14 08:53:30 +02:00
Urs Fleisch
42dcfec86b Use tag_c.h as first include in build with C compiler (#1273)
This will fail on MinGW because its gcc does not have wchar_t without
including wchar.h. With g++ as used with test_tag_c.cpp, the problem is
undetected because g++ supports wchar_t without including wchar.h.
2025-06-14 08:53:30 +02:00
Urs Fleisch
d48f02030d Version 2.1 2025-05-21 21:05:30 +02:00
Jamie
3ccc390155 Add file_new and file_new_type wchar variants for C binding (#1271) 2025-05-16 17:01:23 +02:00
Urs Fleisch
fbbead3efd Support custom temp and tests directories (#1268) (#1270)
The following user-settable values for CMake are supported:

- TESTS_DIR: Tests directory, is path to unit test data when 'data' is
  appended. Can be used to run the unit tests on a target.
- TESTS_TMPDIR: Directory for temporary files created during unit tests,
  system tmpdir is used if undefined. Has to be defined on systems
  without global temporary directory.
2025-05-01 19:55:46 +02:00
Urs Fleisch
ee1931b811 Compile time configuration of supported formats (#1262)
CMake options WITH_APE, WITH_ASF, WITH_DSF, WITH_MOD, WITH_MP4,
WITH_RIFF, WITH_SHORTEN, WITH_TRUEAUDIO, WITH_VORBIS, by default,
they are all ON.
2025-02-02 12:24:26 +01:00
Stephen Booth
648f5e5882 Add Shorten (SHN) support (#1257)
* Add Shorten (SHN) support

* Add `<cmath>` include and use `std::log2`

* Use `uintptr_t` for buffer size calculations

* Work around `byteSwap` not using fixed width types

* Remove four-character codes

* Attempt to fix `static_assert`

* Revert previous commit

* Update `read_uint`* functions

* Use ByteVector for byte swaps

* Use different ByteVector ctor

* Rework variable-length input to use ByteVector

* Rename some variables

* Naming and formatting cleanup

* Add basic Shorten tests

* Rename a constant

* Rename `internalFileType` to `fileType`

* Add documentation on `fileType` meaning

* Add DO_NOT_DOCUMENT guard

* Fix shadowVariable issues reported by cppcheck

cppcheck --enable=all --inline-suppr \
  --suppress=noExplicitConstructor --suppress=unusedFunction \
  --suppress=missingIncludeSystem --project=compile_commands.json

* Formatting cleanup

* More explicit types

Reason for these changes: getRiceGolombCode(k, uInt32CodeSize) was
called with int k for uint32_t& argument.
There was also a warning from MSVC for line 299:
warning C4267: 'argument': conversion from 'size_t' to 'int'

* Additional explicit types

* Rename `SHN` namespace to `Shorten`

Also rename files to match

---------

Co-authored-by: Urs Fleisch <ufleisch@users.sourceforge.net>
2024-12-30 07:23:11 -06:00
Stephen Booth
c1c60ebeea Use fixed width types (#1258)
Use fixed width types in `byteSwap` functions
2024-12-22 07:02:05 -06:00
Urs Fleisch
3bc0ea0ecb Preserve unicode encoding when downgrading to ID3v2.3 (#1259) (#1260) 2024-12-22 12:07:25 +01:00
Christian Schmitz
225c73e181 Fixed warning about shadowing variable (#1254)
You can't name parameter and structure field the same as complier frequently complain about this.
2024-11-22 06:55:34 +01:00
Urs Fleisch
90f62a3c94 Do not store too large FLAC metadata blocks (#1249) (#1250)
The size of FLAC metadata blocks is stored in only 24 bits. Remove
blocks exceeding this limit when saving FLAC and Ogg FLAC files.
2024-11-14 17:43:18 +01:00
Urs Fleisch
5b6f9ef848 Fix segfaults with String and ByteVector nullptr arguments (#1247) (#1248) 2024-11-02 06:39:21 +01:00
153 changed files with 12661 additions and 809 deletions

View File

@@ -9,14 +9,18 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-latest, windows-2025]
include:
- os: windows-latest
cmake_extra_args: '-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"'
- os: windows-2025
cmake_extra_args: '-G "MinGW Makefiles" -DCPPUNIT_INCLUDE_DIR="$env:GITHUB_WORKSPACE/pkg/usr/local/include" -DCPPUNIT_LIBRARIES="$($env:GITHUB_WORKSPACE -replace "\\", "/")/pkg/usr/local/lib/libcppunit.dll.a" -DCPPUNIT_INSTALLED_VERSION="1.15.1"'
# The windows-2025 runner is used for MinGW.
name: ${{ matrix.os == 'windows-2025' && 'mingw' || matrix.os }}
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
@@ -32,6 +36,24 @@ jobs:
run: vcpkg install cppunit utfcpp zlib --triplet x64-windows
if: matrix.os == 'windows-latest'
- name: Set up MinGW
shell: bash
run: |
# Fetch utf8cpp, build cppunit
git submodule update --init
# Fails with fatal error: cppunit/config-auto.h: No such file or directory
# vcpkg install cppunit utfcpp zlib --triplet x64-mingw-dynamic
# Probably not working with CMake and MinGW, so we have to build it ourselves.
curl -sL "https://dev-www.libreoffice.org/src/cppunit-1.15.1.tar.gz" | tar xz
mkdir -p build_cppunit
(cd build_cppunit && MAKE=mingw32-make ../cppunit-1.15.1/configure \
--enable-shared=yes --enable-static=no \
--disable-dependency-tracking --disable-doxygen)
find build_cppunit -name Makefile -exec sed -i 's/\($[({]SHELL[)}]\)/"\1"/' {} \;
sed -i 's/^\(SUBDIRS.*\) examples\(.*\)$/\1\2/' build_cppunit/Makefile
(cd build_cppunit && mingw32-make -j$(nproc) install DESTDIR=$(cd .. && pwd)/pkg)
if: matrix.os == 'windows-2025'
- name: Configure
run: >
cmake -B${{github.workspace}}/build
@@ -41,12 +63,12 @@ jobs:
${{ matrix.cmake_extra_args }}
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
- name: Test
working-directory: ${{github.workspace}}/build
run: ctest -C ${{env.BUILD_TYPE}} -V --no-tests=error
if: matrix.os != 'windows-latest'
if: matrix.os != 'windows-latest' && matrix.os != 'windows-2025'
- name: Test Windows
working-directory: ${{github.workspace}}/build
@@ -57,3 +79,10 @@ jobs:
$env:Path += ";$env:VCPKG_INSTALLATION_ROOT\packages\zlib_x64-windows\bin"
ctest -C ${{env.BUILD_TYPE}} -V --no-tests=error
if: matrix.os == 'windows-latest'
- name: Test MinGW
working-directory: ${{github.workspace}}/build
run: |
$env:Path += ";$PWD/taglib;$PWD/bindings/c;${{github.workspace}}/pkg/usr/local/bin"
ctest -C ${{env.BUILD_TYPE}} -V --no-tests=error
if: matrix.os == 'windows-2025'

View File

@@ -1,3 +1,42 @@
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)
============================
* Map ID3v2.3 IPLS frames to both ID3v2.4 TIPL and TMCL to have a consistent
behavior when using MusicBrainz tags with the property map interface.
* Fix missing include for `wchar_t` when using C bindings with MinGW.
TagLib 2.1 (May 31, 2025)
=========================
* Support for Shorten (SHN) files.
* Compile time configuration of supported formats: WITH_APE, WITH_ASF, ...
* Compile time configuration of data and temporary directories for unit tests:
TESTS_DIR and TESTS_TMPDIR.
* C bindings: Added taglib_file_new_wchar() and taglib_file_new_type_wchar().
* Preserve unicode encoding when downgrading to ID3v2.3.
* Do not store FLAC metadata blocks which are too large.
* Fix segfaults with String and ByteVector nullptr arguments.
TagLib 2.0.2 (Aug 24, 2024)
===========================

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.10...3.31)
project(taglib)
@@ -12,16 +12,13 @@ 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)
set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.")
endif()
endif()
if(NOT BUILD_SHARED_LIBS)
add_definitions(-DTAGLIB_STATIC)
endif()
option(ENABLE_STATIC_RUNTIME "Visual Studio, link with runtime statically" OFF)
option(ENABLE_CCACHE "Use ccache when building libtag" OFF)
@@ -48,7 +45,10 @@ set(TAGLIB_INSTALL_SUFFIX "" CACHE STRING
"Suffix added to installed files (include directory, libraries, .pc)")
add_definitions(-DHAVE_CONFIG_H)
set(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests/")
set(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests/" CACHE STRING
"Tests directory, is path to unit test data when 'data' is appended")
set(TESTS_TMPDIR "" CACHE STRING
"Directory for temporary files created during unit tests, system tmpdir is used if undefined")
if(CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
@@ -92,11 +92,22 @@ 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 0)
set(TAGLIB_SOVERSION_PATCH 2)
set(TAGLIB_SOVERSION_MINOR 2)
set(TAGLIB_SOVERSION_PATCH 1)
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)
option(WITH_SHORTEN "Build with Shorten" ON)
option(WITH_TRUEAUDIO "Build with TrueAudio" ON)
option(WITH_VORBIS "Build with Vorbis, FLAC, Ogg, Opus" ON)
# Determine whether zlib is installed.
option(WITH_ZLIB "Build with ZLIB" ON)
@@ -178,6 +189,39 @@ else()
endif()
endif()
if(WITH_APE)
set(TAGLIB_WITH_APE TRUE)
endif()
if(WITH_ASF)
set(TAGLIB_WITH_ASF TRUE)
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()
if(WITH_MP4)
set(TAGLIB_WITH_MP4 TRUE)
endif()
if(WITH_RIFF)
set(TAGLIB_WITH_RIFF TRUE)
endif()
if(WITH_SHORTEN)
set(TAGLIB_WITH_SHORTEN TRUE)
endif()
if(WITH_TRUEAUDIO)
set(TAGLIB_WITH_TRUEAUDIO TRUE)
endif()
if(WITH_VORBIS)
set(TAGLIB_WITH_VORBIS TRUE)
endif()
configure_file(taglib/taglib_config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/taglib_config.h")
add_subdirectory(taglib)
if(BUILD_BINDINGS)

View File

@@ -42,6 +42,9 @@ CMakeLists.txt file.
| `TAGLIB_INSTALL_SUFFIX` | Suffix added to installed libraries, includes, ... |
| `ENABLE_STATIC_RUNTIME` | Link with MSVC runtime statically |
| `BUILD_FRAMEWORK` | Build a macOS framework |
| `TESTS_DIR` | Where to find unit test data (with data appended) |
| `TESTS_TMPDIR` | Where to create temporary files in unit tests |
If you want to install TagLib 2 alongside TagLib 1, you can use
`-DTAGLIB_INSTALL_SUFFIX=-2` and make sure that `BUILD_EXAMPLES` is not `ON`
@@ -49,6 +52,31 @@ for both versions. The installed files will then include bin/taglib-2-config,
include/taglib-2, cmake/taglib-2, pkgconfig/taglib-2.pc,
pkgconfig/taglib_c-2.pc and the libraries have a suffix "-2".
### Compile Time Configuration of Supported Formats
To reduce the size of the library, it is possible to switch off supported file
formats. By default, all formats are enabled. Support for MPEG files (MP3, AAC)
and ID3 tags cannot be disabled. The following CMake options are available:
| Option | Description |
|-------------------------|----------------------------------------------------|
| `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 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
if the library is built specifically for a certain project. The public header
files still contain the full API, if you use TagLib with a reduced set of
formats, you can include taglib_config.h and use its definitions (prefixed with
`TAGLIB_`, e.g. `TAGLIB_WITH_APE`), as it is done in examples/framelist.cpp.
## Dependencies
@@ -438,3 +466,54 @@ cmake --build build_mingw --config Release
PATH=$PATH:$TAGLIB_PREFIX/bin
build_mingw/tagreader /path/to/audio-file
```
## Android
### Using vcpkg
The bash script below can be used to build TagLib for Android using vcpkg.
It must be started in the parent folder of the taglib source folder and will
build in a folder _android_build_ and install into _android_pkg_.
The package and the unit tests are then transferred to an Android device
and the unit tests are run on the device.
Note that `TESTS_TMPDIR` is set because there is no system-wide temporary folder
on Android. `TESTS_DIR` is set to run the tests on the target.
```
# You may have to adapt the NDK and vcpkg paths and the ABI/triplet.
export ANDROID_NDK_HOME=$HOME/Development/android-sdk/ndk/23.1.7779620
export VCPKG_ROOT=$HOME/Development/vcpkg
PATH=$PATH:$VCPKG_ROOT
# armeabi-v7a/arm-android or arm64-v8a/arm64-android or x86/x86-android or x86_64/x64-android
android_abi=armeabi-v7a
vcpkg_target_triplet=arm-android
vcpkg_toolchain_file=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
android_toolchain_file=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
vcpkg install --triplet $vcpkg_target_triplet utfcpp zlib cppunit
cmake -B android_build -S taglib \
-DCMAKE_TOOLCHAIN_FILE=$vcpkg_toolchain_file \
-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$android_toolchain_file \
-DVCPKG_TARGET_TRIPLET=$vcpkg_target_triplet \
-DANDROID_ABI=$android_abi \
-GNinja -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release \
-DVISIBILITY_HIDDEN=ON -DENABLE_CCACHE=ON -DBUILD_EXAMPLES=ON -DBUILD_TESTING=ON \
-DTESTS_DIR=/data/local/tmp/tests/ -DTESTS_TMPDIR=/data/local/tmp
cmake --build android_build --config Release
cmake --install android_build --config Release --prefix android_pkg --strip
cp -a android_build/tests/test_runner android_pkg/bin/
if hash adb 2>/dev/null; then
adb push android_pkg /data/local/tmp/
adb push android_build/tests/test_runner /data/local/tmp/tests/test_runner
adb push taglib/tests/data /data/local/tmp/tests/
adb shell "env LD_LIBRARY_PATH=/data/local/tmp/android_pkg/lib /data/local/tmp/tests/test_runner"
# You could also try an example binary:
# adb shell "env LD_LIBRARY_PATH=/data/local/tmp/android_pkg/lib /data/local/tmp/android_pkg/bin/tagreader '/sdcard/Music/Some Album/A Track.mp3'"
fi
```

View File

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

View File

@@ -1,31 +1,75 @@
include_directories(
set(tag_c_HDR_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/toolkit
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/asf
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpeg
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/vorbis
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/flac
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/flac
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpc
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mp4
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpeg/id3v2
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpeg/id3v2/frames
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/wavpack
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/speex
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/trueaudio
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/riff
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/riff/aiff
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/riff/wav
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ape
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/it
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mod
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/s3m
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/xm
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/opus
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/dsf
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/dsdiff
)
if(WITH_ASF)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/asf
)
endif()
if(WITH_VORBIS)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/vorbis
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/flac
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/flac
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/speex
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/opus
)
endif()
if(WITH_APE)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpc
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/wavpack
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ape
)
endif()
if(WITH_MP4)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mp4
)
endif()
if(WITH_TRUEAUDIO)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/trueaudio
)
endif()
if(WITH_RIFF)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/riff
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/riff/aiff
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/riff/wav
)
endif()
if(WITH_MOD)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/it
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mod
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/s3m
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/xm
)
endif()
if(WITH_DSF)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/dsf
${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/dsdiff
)
endif()
if(WITH_SHORTEN)
set(tag_c_HDR_DIRS ${tag_c_HDR_DIRS}
${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)
@@ -74,6 +118,9 @@ set_target_properties(tag_c PROPERTIES
DEFINE_SYMBOL MAKE_TAGLIB_C_LIB
INSTALL_NAME_DIR ${CMAKE_INSTALL_FULL_LIBDIR}
)
if(NOT BUILD_SHARED_LIBS)
target_compile_definitions(tag_c PUBLIC TAGLIB_STATIC)
endif()
if(TAGLIB_INSTALL_SUFFIX)
if(BUILD_SHARED_LIBS)

View File

@@ -29,34 +29,60 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "taglib_config.h"
#include "tstringlist.h"
#include "tbytevectorstream.h"
#include "tiostream.h"
#include "tfile.h"
#include "tpropertymap.h"
#include "fileref.h"
#include "asffile.h"
#include "vorbisfile.h"
#include "mpegfile.h"
#include "tag.h"
#include "id3v2framefactory.h"
#ifdef TAGLIB_WITH_ASF
#include "asffile.h"
#endif
#ifdef TAGLIB_WITH_VORBIS
#include "vorbisfile.h"
#include "flacfile.h"
#include "oggflacfile.h"
#include "speexfile.h"
#include "opusfile.h"
#endif
#ifdef TAGLIB_WITH_APE
#include "mpcfile.h"
#include "wavpackfile.h"
#include "speexfile.h"
#endif
#ifdef TAGLIB_WITH_TRUEAUDIO
#include "trueaudiofile.h"
#endif
#ifdef TAGLIB_WITH_MP4
#include "mp4file.h"
#endif
#ifdef TAGLIB_WITH_RIFF
#include "aifffile.h"
#include "wavfile.h"
#endif
#ifdef TAGLIB_WITH_APE
#include "apefile.h"
#endif
#ifdef TAGLIB_WITH_MOD
#include "itfile.h"
#include "modfile.h"
#include "s3mfile.h"
#include "xmfile.h"
#include "opusfile.h"
#endif
#ifdef TAGLIB_WITH_DSF
#include "dsffile.h"
#include "dsdifffile.h"
#include "tag.h"
#include "id3v2framefactory.h"
#endif
#ifdef TAGLIB_WITH_SHORTEN
#include "shortenfile.h"
#endif
#ifdef TAGLIB_WITH_MATROSKA
#include "matroskafile.h"
#endif
using namespace TagLib;
@@ -126,49 +152,73 @@ TagLib_File *taglib_file_new(const char *filename)
return reinterpret_cast<TagLib_File *>(new FileRef(filename));
}
TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type)
#ifdef _WIN32
TagLib_File *taglib_file_new_wchar(const wchar_t *filename)
{
return reinterpret_cast<TagLib_File *>(new FileRef(filename));
}
#endif
template<typename T>
TagLib_File *taglib_file_new_type_any_char(const T *filename, TagLib_File_Type type)
{
File *file = NULL;
switch(type) {
case TagLib_File_MPEG:
file = new MPEG::File(filename);
break;
#ifdef TAGLIB_WITH_VORBIS
case TagLib_File_OggVorbis:
file = new Ogg::Vorbis::File(filename);
break;
case TagLib_File_FLAC:
file = new FLAC::File(filename);
break;
case TagLib_File_MPC:
file = new MPC::File(filename);
break;
case TagLib_File_OggFlac:
file = new Ogg::FLAC::File(filename);
break;
case TagLib_File_WavPack:
file = new WavPack::File(filename);
break;
case TagLib_File_Speex:
file = new Ogg::Speex::File(filename);
break;
case TagLib_File_Opus:
file = new Ogg::Opus::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_APE
case TagLib_File_MPC:
file = new MPC::File(filename);
break;
case TagLib_File_WavPack:
file = new WavPack::File(filename);
break;
case TagLib_File_APE:
file = new APE::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_TRUEAUDIO
case TagLib_File_TrueAudio:
file = new TrueAudio::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_MP4
case TagLib_File_MP4:
file = new MP4::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_ASF
case TagLib_File_ASF:
file = new ASF::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_RIFF
case TagLib_File_AIFF:
file = new RIFF::AIFF::File(filename);
break;
case TagLib_File_WAV:
file = new RIFF::WAV::File(filename);
break;
case TagLib_File_APE:
file = new APE::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_MOD
case TagLib_File_IT:
file = new IT::File(filename);
break;
@@ -181,21 +231,43 @@ TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type)
case TagLib_File_XM:
file = new XM::File(filename);
break;
case TagLib_File_Opus:
file = new Ogg::Opus::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_DSF
case TagLib_File_DSF:
file = new DSF::File(filename);
break;
case TagLib_File_DSDIFF:
file = new DSDIFF::File(filename);
break;
#endif
#ifdef TAGLIB_WITH_SHORTEN
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;
}
return file ? reinterpret_cast<TagLib_File *>(new FileRef(file)) : NULL;
}
TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type)
{
return taglib_file_new_type_any_char(filename, type);
}
#ifdef _WIN32
TagLib_File *taglib_file_new_type_wchar(const wchar_t *filename, TagLib_File_Type type)
{
return taglib_file_new_type_any_char(filename, type);
}
#endif
TagLib_File *taglib_file_new_iostream(TagLib_IOStream *stream)
{
return reinterpret_cast<TagLib_File *>(
@@ -405,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) {
@@ -422,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);
@@ -467,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;
@@ -509,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());
@@ -563,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
@@ -612,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;
}

View File

@@ -43,6 +43,7 @@ extern "C" {
#define TAGLIB_C_EXPORT
#endif
#include <wchar.h>
#ifdef _MSC_VER
/* minwindef.h contains typedef int BOOL */
#include <windows.h>
@@ -130,7 +131,9 @@ typedef enum {
TagLib_File_XM,
TagLib_File_Opus,
TagLib_File_DSF,
TagLib_File_DSDIFF
TagLib_File_DSDIFF,
TagLib_File_SHORTEN,
TagLib_File_MATROSKA
} TagLib_File_Type;
/*!
@@ -141,12 +144,18 @@ typedef enum {
* be opened.
*/
TAGLIB_C_EXPORT TagLib_File *taglib_file_new(const char *filename);
#ifdef _WIN32
TAGLIB_C_EXPORT TagLib_File *taglib_file_new_wchar(const wchar_t *filename);
#endif
/*!
* Creates a TagLib file based on \a filename. Rather than attempting to guess
* the type, it will use the one specified by \a type.
*/
TAGLIB_C_EXPORT TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type);
#ifdef _WIN32
TAGLIB_C_EXPORT TagLib_File *taglib_file_new_type_wchar(const wchar_t *filename, TagLib_File_Type type);
#endif
/*!
* Creates a TagLib file from a \a stream.

View File

@@ -20,5 +20,6 @@
#cmakedefine TRACE_IN_RELEASE 1
#cmakedefine TESTS_DIR "@TESTS_DIR@"
#cmakedefine TESTS_TMPDIR "@TESTS_TMPDIR@"
#endif

View File

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

View File

@@ -25,6 +25,7 @@
#include <iostream>
#include <cstdlib>
#include "taglib_config.h"
#include "tbytevector.h"
#include "mpegfile.h"
#include "id3v2tag.h"
@@ -32,7 +33,9 @@
#include "id3v2header.h"
#include "commentsframe.h"
#include "id3v1tag.h"
#ifdef TAGLIB_WITH_APE
#include "apetag.h"
#endif
using namespace TagLib;
@@ -90,6 +93,7 @@ int main(int argc, char *argv[])
else
std::cout << "file does not have a valid id3v1 tag" << std::endl;
#ifdef TAGLIB_WITH_APE
APE::Tag *ape = f.APETag();
std::cout << std::endl << "APE" << std::endl;
@@ -106,6 +110,7 @@ int main(int argc, char *argv[])
}
else
std::cout << "file does not have a valid APE tag" << std::endl;
#endif
std::cout << std::endl;
}

139
examples/matroskareader.cpp Normal file
View 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) "" s ""
#define PRINT_PRETTY(label, value) printf(" " GREEN_TEXT(label) ": %s\n", value)
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("Usage: matroskareader FILE\n");
return 1;
}
TagLib::Matroska::File file(argv[1]);
if(!file.isValid()) {
printf("File is not valid\n");
return 1;
}
auto tag = dynamic_cast<TagLib::Matroska::Tag*>(file.tag());
if(!tag) {
printf("File has no tag\n");
return 0;
}
const TagLib::Matroska::SimpleTagsList &list = tag->simpleTagsList();
printf("Found %u tag(s):\n", list.size());
for(const TagLib::Matroska::SimpleTag &t : list) {
PRINT_PRETTY("Tag Name", t.name().toCString(true));
if(t.type() == TagLib::Matroska::SimpleTag::StringType)
PRINT_PRETTY("Tag Value", t.toString().toCString(true));
else if(t.type() == TagLib::Matroska::SimpleTag::BinaryType)
PRINT_PRETTY("Tag Value",
TagLib::Utils::formatString("Binary with size %i", t.toByteVector().size()).toCString(false)
);
auto targetTypeValue = static_cast<unsigned int>(t.targetTypeValue());
PRINT_PRETTY("Target Type Value",
targetTypeValue == 0 ? "None" : TagLib::Utils::formatString("%i", targetTypeValue).toCString(false)
);
if(auto trackUid = t.trackUid()) {
PRINT_PRETTY("Track UID",
TagLib::Utils::formatString("%llu",trackUid).toCString(false)
);
}
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;
}

View File

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

View File

@@ -22,11 +22,11 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "tag_c.h"
#include <stdio.h>
#include <string.h>
#include "tag_c.h"
#ifndef FALSE
#define FALSE 0
#endif

View File

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

View File

@@ -1,38 +1,84 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON)
include_directories(
set(tag_HDR_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/toolkit
${CMAKE_CURRENT_SOURCE_DIR}/asf
${CMAKE_CURRENT_SOURCE_DIR}/mpeg
${CMAKE_CURRENT_SOURCE_DIR}/ogg
${CMAKE_CURRENT_SOURCE_DIR}/ogg/flac
${CMAKE_CURRENT_SOURCE_DIR}/flac
${CMAKE_CURRENT_SOURCE_DIR}/mpc
${CMAKE_CURRENT_SOURCE_DIR}/mp4
${CMAKE_CURRENT_SOURCE_DIR}/ogg/vorbis
${CMAKE_CURRENT_SOURCE_DIR}/ogg/speex
${CMAKE_CURRENT_SOURCE_DIR}/ogg/opus
${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2
${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2/frames
${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v1
${CMAKE_CURRENT_SOURCE_DIR}/ape
${CMAKE_CURRENT_SOURCE_DIR}/wavpack
${CMAKE_CURRENT_SOURCE_DIR}/trueaudio
${CMAKE_CURRENT_SOURCE_DIR}/riff
${CMAKE_CURRENT_SOURCE_DIR}/riff/aiff
${CMAKE_CURRENT_SOURCE_DIR}/riff/wav
${CMAKE_CURRENT_SOURCE_DIR}/mod
${CMAKE_CURRENT_SOURCE_DIR}/s3m
${CMAKE_CURRENT_SOURCE_DIR}/it
${CMAKE_CURRENT_SOURCE_DIR}/xm
${CMAKE_CURRENT_SOURCE_DIR}/dsf
${CMAKE_CURRENT_SOURCE_DIR}/dsdiff
)
if(WITH_ASF)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/asf
)
endif()
if(WITH_VORBIS)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/ogg
${CMAKE_CURRENT_SOURCE_DIR}/ogg/flac
${CMAKE_CURRENT_SOURCE_DIR}/flac
${CMAKE_CURRENT_SOURCE_DIR}/ogg/vorbis
${CMAKE_CURRENT_SOURCE_DIR}/ogg/speex
${CMAKE_CURRENT_SOURCE_DIR}/ogg/opus
)
endif()
if(WITH_APE)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/mpc
${CMAKE_CURRENT_SOURCE_DIR}/ape
${CMAKE_CURRENT_SOURCE_DIR}/wavpack
)
endif()
if(WITH_MP4)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/mp4
)
endif()
if(WITH_TRUEAUDIO)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/trueaudio
)
endif()
if(WITH_RIFF)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/riff
${CMAKE_CURRENT_SOURCE_DIR}/riff/aiff
${CMAKE_CURRENT_SOURCE_DIR}/riff/wav
)
endif()
if(WITH_MOD)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/mod
${CMAKE_CURRENT_SOURCE_DIR}/s3m
${CMAKE_CURRENT_SOURCE_DIR}/it
${CMAKE_CURRENT_SOURCE_DIR}/xm
)
endif()
if(WITH_DSF)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}/dsf
${CMAKE_CURRENT_SOURCE_DIR}/dsdiff
)
endif()
if(WITH_SHORTEN)
set(tag_HDR_DIRS ${tag_HDR_DIRS}
${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
audioproperties.h
taglib_export.h
${CMAKE_CURRENT_BINARY_DIR}/../taglib_config.h
toolkit/taglib.h
toolkit/tstring.h
toolkit/tlist.h
@@ -82,66 +128,141 @@ set(tag_HDRS
mpeg/id3v2/frames/chapterframe.h
mpeg/id3v2/frames/tableofcontentsframe.h
mpeg/id3v2/frames/podcastframe.h
ogg/oggfile.h
ogg/oggpage.h
ogg/oggpageheader.h
ogg/xiphcomment.h
ogg/vorbis/vorbisfile.h
ogg/vorbis/vorbisproperties.h
ogg/flac/oggflacfile.h
ogg/speex/speexfile.h
ogg/speex/speexproperties.h
ogg/opus/opusfile.h
ogg/opus/opusproperties.h
flac/flacfile.h
flac/flacpicture.h
flac/flacproperties.h
flac/flacmetadatablock.h
ape/apefile.h
ape/apeproperties.h
ape/apetag.h
ape/apefooter.h
ape/apeitem.h
mpc/mpcfile.h
mpc/mpcproperties.h
wavpack/wavpackfile.h
wavpack/wavpackproperties.h
trueaudio/trueaudiofile.h
trueaudio/trueaudioproperties.h
riff/rifffile.h
riff/aiff/aifffile.h
riff/aiff/aiffproperties.h
riff/wav/wavfile.h
riff/wav/wavproperties.h
riff/wav/infotag.h
asf/asffile.h
asf/asfproperties.h
asf/asftag.h
asf/asfattribute.h
asf/asfpicture.h
mp4/mp4file.h
mp4/mp4atom.h
mp4/mp4tag.h
mp4/mp4item.h
mp4/mp4properties.h
mp4/mp4coverart.h
mp4/mp4itemfactory.h
mod/modfilebase.h
mod/modfile.h
mod/modtag.h
mod/modproperties.h
it/itfile.h
it/itproperties.h
s3m/s3mfile.h
s3m/s3mproperties.h
xm/xmfile.h
xm/xmproperties.h
dsf/dsffile.h
dsf/dsfproperties.h
dsdiff/dsdifffile.h
dsdiff/dsdiffproperties.h
dsdiff/dsdiffdiintag.h
)
if(WITH_VORBIS)
set(tag_HDRS ${tag_HDRS}
ogg/oggfile.h
ogg/oggpage.h
ogg/oggpageheader.h
ogg/xiphcomment.h
ogg/vorbis/vorbisfile.h
ogg/vorbis/vorbisproperties.h
ogg/flac/oggflacfile.h
ogg/speex/speexfile.h
ogg/speex/speexproperties.h
ogg/opus/opusfile.h
ogg/opus/opusproperties.h
flac/flacfile.h
flac/flacpicture.h
flac/flacproperties.h
flac/flacmetadatablock.h
)
endif()
if(WITH_APE)
set(tag_HDRS ${tag_HDRS}
ape/apefile.h
ape/apeproperties.h
ape/apetag.h
ape/apefooter.h
ape/apeitem.h
mpc/mpcfile.h
mpc/mpcproperties.h
wavpack/wavpackfile.h
wavpack/wavpackproperties.h
)
endif()
if(WITH_TRUEAUDIO)
set(tag_HDRS ${tag_HDRS}
trueaudio/trueaudiofile.h
trueaudio/trueaudioproperties.h
)
endif()
if(WITH_RIFF)
set(tag_HDRS ${tag_HDRS}
riff/rifffile.h
riff/aiff/aifffile.h
riff/aiff/aiffproperties.h
riff/wav/wavfile.h
riff/wav/wavproperties.h
riff/wav/infotag.h
)
endif()
if(WITH_ASF)
set(tag_HDRS ${tag_HDRS}
asf/asffile.h
asf/asfproperties.h
asf/asftag.h
asf/asfattribute.h
asf/asfpicture.h
)
endif()
if(WITH_MP4)
set(tag_HDRS ${tag_HDRS}
mp4/mp4file.h
mp4/mp4atom.h
mp4/mp4tag.h
mp4/mp4item.h
mp4/mp4properties.h
mp4/mp4coverart.h
mp4/mp4stem.h
mp4/mp4itemfactory.h
)
endif()
if(WITH_MOD)
set(tag_HDRS ${tag_HDRS}
mod/modfilebase.h
mod/modfile.h
mod/modtag.h
mod/modproperties.h
it/itfile.h
it/itproperties.h
s3m/s3mfile.h
s3m/s3mproperties.h
xm/xmfile.h
xm/xmproperties.h
)
endif()
if(WITH_DSF)
set(tag_HDRS ${tag_HDRS}
dsf/dsffile.h
dsf/dsfproperties.h
dsdiff/dsdifffile.h
dsdiff/dsdiffproperties.h
dsdiff/dsdiffdiintag.h
)
endif()
if(WITH_SHORTEN)
set(tag_HDRS ${tag_HDRS}
shorten/shortenfile.h
shorten/shortenproperties.h
shorten/shortentag.h
)
endif()
if(WITH_MATROSKA)
set(tag_HDRS ${tag_HDRS}
matroska/matroskaattachedfile.h
matroska/matroskaattachments.h
matroska/matroskachapter.h
matroska/matroskachapteredition.h
matroska/matroskachapters.h
matroska/matroskaelement.h
matroska/matroskafile.h
matroska/matroskaproperties.h
matroska/matroskasimpletag.h
matroska/matroskatag.h
)
set(tag_PRIVATE_HDRS ${tag_PRIVATE_HDRS}
matroska/ebml/ebmlbinaryelement.h
matroska/ebml/ebmlelement.h
matroska/ebml/ebmlmasterelement.h
matroska/ebml/ebmlmkattachments.h
matroska/ebml/ebmlmkchapters.h
matroska/ebml/ebmlmkcues.h
matroska/ebml/ebmlmkseekhead.h
matroska/ebml/ebmlmksegment.h
matroska/ebml/ebmlmkinfo.h
matroska/ebml/ebmlmktracks.h
matroska/ebml/ebmlmktags.h
matroska/ebml/ebmlstringelement.h
matroska/ebml/ebmluintelement.h
matroska/ebml/ebmlfloatelement.h
matroska/ebml/ebmlutils.h
matroska/ebml/ebmlvoidelement.h
matroska/matroskacues.h
matroska/matroskaseekhead.h
matroska/matroskasegment.h
)
endif()
set(mpeg_SRCS
mpeg/mpegfile.cpp
@@ -185,128 +306,190 @@ set(frames_SRCS
mpeg/id3v2/frames/podcastframe.cpp
)
set(ogg_SRCS
ogg/oggfile.cpp
ogg/oggpage.cpp
ogg/oggpageheader.cpp
ogg/xiphcomment.cpp
)
if(WITH_VORBIS)
set(ogg_SRCS
ogg/oggfile.cpp
ogg/oggpage.cpp
ogg/oggpageheader.cpp
ogg/xiphcomment.cpp
)
set(vorbis_SRCS
ogg/vorbis/vorbisfile.cpp
ogg/vorbis/vorbisproperties.cpp
)
set(vorbis_SRCS
ogg/vorbis/vorbisfile.cpp
ogg/vorbis/vorbisproperties.cpp
)
set(flacs_SRCS
flac/flacfile.cpp
flac/flacpicture.cpp
flac/flacproperties.cpp
flac/flacmetadatablock.cpp
flac/flacunknownmetadatablock.cpp
)
set(flacs_SRCS
flac/flacfile.cpp
flac/flacpicture.cpp
flac/flacproperties.cpp
flac/flacmetadatablock.cpp
flac/flacunknownmetadatablock.cpp
)
set(oggflacs_SRCS
ogg/flac/oggflacfile.cpp
)
set(oggflacs_SRCS
ogg/flac/oggflacfile.cpp
)
set(mpc_SRCS
mpc/mpcfile.cpp
mpc/mpcproperties.cpp
)
set(speex_SRCS
ogg/speex/speexfile.cpp
ogg/speex/speexproperties.cpp
)
set(mp4_SRCS
mp4/mp4file.cpp
mp4/mp4atom.cpp
mp4/mp4tag.cpp
mp4/mp4item.cpp
mp4/mp4properties.cpp
mp4/mp4coverart.cpp
mp4/mp4itemfactory.cpp
)
set(opus_SRCS
ogg/opus/opusfile.cpp
ogg/opus/opusproperties.cpp
)
endif()
set(ape_SRCS
ape/apetag.cpp
ape/apefooter.cpp
ape/apeitem.cpp
ape/apefile.cpp
ape/apeproperties.cpp
)
if(WITH_APE)
set(mpc_SRCS
mpc/mpcfile.cpp
mpc/mpcproperties.cpp
)
set(wavpack_SRCS
wavpack/wavpackfile.cpp
wavpack/wavpackproperties.cpp
)
set(ape_SRCS
ape/apetag.cpp
ape/apefooter.cpp
ape/apeitem.cpp
ape/apefile.cpp
ape/apeproperties.cpp
)
set(speex_SRCS
ogg/speex/speexfile.cpp
ogg/speex/speexproperties.cpp
)
set(wavpack_SRCS
wavpack/wavpackfile.cpp
wavpack/wavpackproperties.cpp
)
endif()
set(opus_SRCS
ogg/opus/opusfile.cpp
ogg/opus/opusproperties.cpp
)
if(WITH_MP4)
set(mp4_SRCS
mp4/mp4file.cpp
mp4/mp4atom.cpp
mp4/mp4tag.cpp
mp4/mp4item.cpp
mp4/mp4properties.cpp
mp4/mp4coverart.cpp
mp4/mp4stem.cpp
mp4/mp4itemfactory.cpp
)
endif()
set(trueaudio_SRCS
trueaudio/trueaudiofile.cpp
trueaudio/trueaudioproperties.cpp
)
if(WITH_TRUEAUDIO)
set(trueaudio_SRCS
trueaudio/trueaudiofile.cpp
trueaudio/trueaudioproperties.cpp
)
endif()
set(asf_SRCS
asf/asftag.cpp
asf/asffile.cpp
asf/asfproperties.cpp
asf/asfattribute.cpp
asf/asfpicture.cpp
)
if(WITH_ASF)
set(asf_SRCS
asf/asftag.cpp
asf/asffile.cpp
asf/asfproperties.cpp
asf/asfattribute.cpp
asf/asfpicture.cpp
)
endif()
set(riff_SRCS
riff/rifffile.cpp
)
if(WITH_RIFF)
set(riff_SRCS
riff/rifffile.cpp
)
set(aiff_SRCS
riff/aiff/aifffile.cpp
riff/aiff/aiffproperties.cpp
)
set(aiff_SRCS
riff/aiff/aifffile.cpp
riff/aiff/aiffproperties.cpp
)
set(wav_SRCS
riff/wav/wavfile.cpp
riff/wav/wavproperties.cpp
riff/wav/infotag.cpp
)
set(wav_SRCS
riff/wav/wavfile.cpp
riff/wav/wavproperties.cpp
riff/wav/infotag.cpp
)
endif()
set(mod_SRCS
mod/modfilebase.cpp
mod/modfile.cpp
mod/modtag.cpp
mod/modproperties.cpp
)
if(WITH_MOD)
set(mod_SRCS
mod/modfilebase.cpp
mod/modfile.cpp
mod/modtag.cpp
mod/modproperties.cpp
)
set(s3m_SRCS
s3m/s3mfile.cpp
s3m/s3mproperties.cpp
)
set(s3m_SRCS
s3m/s3mfile.cpp
s3m/s3mproperties.cpp
)
set(it_SRCS
it/itfile.cpp
it/itproperties.cpp
)
set(it_SRCS
it/itfile.cpp
it/itproperties.cpp
)
set(xm_SRCS
xm/xmfile.cpp
xm/xmproperties.cpp
)
set(xm_SRCS
xm/xmfile.cpp
xm/xmproperties.cpp
)
endif()
set(dsf_SRCS
dsf/dsffile.cpp
dsf/dsfproperties.cpp
)
if(WITH_DSF)
set(dsf_SRCS
dsf/dsffile.cpp
dsf/dsfproperties.cpp
)
set(dsdiff_SRCS
dsdiff/dsdifffile.cpp
dsdiff/dsdiffproperties.cpp
dsdiff/dsdiffdiintag.cpp
)
set(dsdiff_SRCS
dsdiff/dsdifffile.cpp
dsdiff/dsdiffproperties.cpp
dsdiff/dsdiffdiintag.cpp
)
endif()
if(WITH_SHORTEN)
set(shorten_SRCS
shorten/shortenfile.cpp
shorten/shortenproperties.cpp
shorten/shortentag.cpp
)
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
@@ -331,7 +514,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}
${dsf_SRCS} ${dsdiff_SRCS} ${shorten_SRCS} ${matroska_SRCS} ${ebml_SRCS}
tag.cpp
tagunion.cpp
fileref.cpp
@@ -339,7 +522,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>
@@ -359,6 +542,9 @@ set_target_properties(tag PROPERTIES
INTERFACE_LINK_LIBRARIES "${ZLIB_INTERFACE_LINK_LIBRARIES}"
PUBLIC_HEADER "${tag_HDRS}"
)
if(NOT BUILD_SHARED_LIBS)
target_compile_definitions(tag PUBLIC TAGLIB_STATIC)
endif()
if(VISIBILITY_HIDDEN)
set_target_properties(tag PROPERTIES C_VISIBILITY_PRESET hidden)
set_target_properties(tag PROPERTIES CXX_VISIBILITY_PRESET hidden)

View File

@@ -139,7 +139,7 @@ void APE::Properties::read(File *file, offset_t streamLength)
if(d->sampleFrames > 0 && d->sampleRate > 0) {
const auto length = static_cast<double>(d->sampleFrames) * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
}
}

View File

@@ -302,16 +302,16 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const
if(kind == 0) {
data = renderString(name, true) +
ByteVector::fromShort(static_cast<int>(d->type), false) +
ByteVector::fromShort(data.size(), false) +
ByteVector::fromShort(static_cast<short>(d->type), false) +
ByteVector::fromShort(static_cast<short>(data.size()), false) +
data;
}
else {
ByteVector nameData = renderString(name);
data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) +
ByteVector::fromShort(d->stream, false) +
ByteVector::fromShort(nameData.size(), false) +
ByteVector::fromShort(static_cast<int>(d->type), false) +
data = ByteVector::fromShort(static_cast<short>(kind == 2 ? d->language : 0), false) +
ByteVector::fromShort(static_cast<short>(d->stream), false) +
ByteVector::fromShort(static_cast<short>(nameData.size()), false) +
ByteVector::fromShort(static_cast<short>(d->type), false) +
ByteVector::fromUInt(data.size(), false) +
nameData +
data;

View File

@@ -221,7 +221,8 @@ void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, long l
const long long duration = data.toLongLong(40, false);
const long long preroll = data.toLongLong(56, false);
file->d->properties->setLengthInMilliseconds(static_cast<int>(duration / 10000.0 - preroll + 0.5));
file->d->properties->setLengthInMilliseconds(
static_cast<int>(static_cast<double>(duration) / 10000.0 - static_cast<double>(preroll) + 0.5));
}
ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const
@@ -271,11 +272,11 @@ ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *f
const ByteVector v4 = renderString(file->d->tag->comment());
const ByteVector v5 = renderString(file->d->tag->rating());
data.clear();
data.append(ByteVector::fromShort(v1.size(), false));
data.append(ByteVector::fromShort(v2.size(), false));
data.append(ByteVector::fromShort(v3.size(), false));
data.append(ByteVector::fromShort(v4.size(), false));
data.append(ByteVector::fromShort(v5.size(), false));
data.append(ByteVector::fromShort(static_cast<short>(v1.size()), false));
data.append(ByteVector::fromShort(static_cast<short>(v2.size()), false));
data.append(ByteVector::fromShort(static_cast<short>(v3.size()), false));
data.append(ByteVector::fromShort(static_cast<short>(v4.size()), false));
data.append(ByteVector::fromShort(static_cast<short>(v5.size()), false));
data.append(v1);
data.append(v2);
data.append(v3);
@@ -302,7 +303,7 @@ void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *
ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF::File *file)
{
data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false));
data.append(ByteVector::fromShort(static_cast<short>(attributeData.size()), false));
data.append(attributeData.toByteVector(""));
return BaseObject::render(file);
}
@@ -325,7 +326,7 @@ void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, long long /*
ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file)
{
data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false));
data.append(ByteVector::fromShort(static_cast<short>(attributeData.size()), false));
data.append(attributeData.toByteVector(""));
return BaseObject::render(file);
}
@@ -348,7 +349,7 @@ void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, long
ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file)
{
data.clear();
data.append(ByteVector::fromShort(attributeData.size(), false));
data.append(ByteVector::fromShort(static_cast<short>(attributeData.size()), false));
data.append(attributeData.toByteVector(""));
return BaseObject::render(file);
}

View File

@@ -90,7 +90,7 @@ namespace TagLib
{
ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false);
if(includeLength) {
data = ByteVector::fromShort(data.size(), false) + data;
data = ByteVector::fromShort(static_cast<short>(data.size()), false) + data;
}
return data;
}

View File

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

View File

@@ -26,7 +26,7 @@
#ifndef TAGLIB_DSDIFFFILE_H
#define TAGLIB_DSDIFFFILE_H
#include "rifffile.h"
#include "tfile.h"
#include "id3v2tag.h"
#include "dsdiffproperties.h"
#include "dsdiffdiintag.h"

View File

@@ -58,7 +58,7 @@ DSDIFF::Properties::Properties(unsigned int sampleRate,
d->sampleRate = sampleRate;
d->bitrate = bitrate;
d->length = d->sampleRate > 0
? static_cast<int>(d->sampleCount * 1000.0 / d->sampleRate + 0.5)
? static_cast<int>(static_cast<double>(d->sampleCount) * 1000.0 / d->sampleRate + 0.5)
: 0;
}

View File

@@ -59,7 +59,7 @@ bool DSF::File::isSupported(IOStream *stream)
return id.startsWith("DSD ");
}
DSF::File::File(FileName file, bool readProperties,
DSF::File::File(FileName file, bool,
AudioProperties::ReadStyle propertiesStyle,
ID3v2::FrameFactory *frameFactory) :
TagLib::File(file),
@@ -69,7 +69,7 @@ DSF::File::File(FileName file, bool readProperties,
read(propertiesStyle);
}
DSF::File::File(IOStream *stream, bool readProperties,
DSF::File::File(IOStream *stream, bool,
AudioProperties::ReadStyle propertiesStyle,
ID3v2::FrameFactory *frameFactory) :
TagLib::File(stream),

View File

@@ -129,6 +129,6 @@ void DSF::Properties::read(const ByteVector &data)
d->bitrate = static_cast<unsigned int>(
d->samplingFrequency * d->bitsPerSample * d->channelNum / 1000.0 + 0.5);
d->length = d->samplingFrequency > 0
? static_cast<unsigned int>(d->sampleCount * 1000.0 / d->samplingFrequency + 0.5)
? static_cast<unsigned int>(static_cast<double>(d->sampleCount) * 1000.0 / d->samplingFrequency + 0.5)
: 0;
}

View File

@@ -32,31 +32,54 @@
#include <cstring>
#include <utility>
#include "taglib_config.h"
#include "tfilestream.h"
#include "tpropertymap.h"
#include "tstringlist.h"
#include "tvariant.h"
#include "tdebug.h"
#include "aifffile.h"
#include "apefile.h"
#include "asffile.h"
#include "flacfile.h"
#include "itfile.h"
#include "modfile.h"
#include "mp4file.h"
#include "mpcfile.h"
#include "mpegfile.h"
#ifdef TAGLIB_WITH_RIFF
#include "aifffile.h"
#include "wavfile.h"
#endif
#ifdef TAGLIB_WITH_APE
#include "apefile.h"
#include "mpcfile.h"
#include "wavpackfile.h"
#endif
#ifdef TAGLIB_WITH_ASF
#include "asffile.h"
#endif
#ifdef TAGLIB_WITH_VORBIS
#include "flacfile.h"
#include "speexfile.h"
#include "vorbisfile.h"
#include "oggflacfile.h"
#include "opusfile.h"
#include "s3mfile.h"
#include "speexfile.h"
#include "trueaudiofile.h"
#include "vorbisfile.h"
#include "wavfile.h"
#include "wavpackfile.h"
#endif
#ifdef TAGLIB_WITH_MOD
#include "itfile.h"
#include "modfile.h"
#include "xmfile.h"
#include "s3mfile.h"
#endif
#ifdef TAGLIB_WITH_MP4
#include "mp4file.h"
#endif
#ifdef TAGLIB_WITH_TRUEAUDIO
#include "trueaudiofile.h"
#endif
#ifdef TAGLIB_WITH_DSF
#include "dsffile.h"
#include "dsdifffile.h"
#endif
#ifdef TAGLIB_WITH_SHORTEN
#include "shortenfile.h"
#endif
#ifdef TAGLIB_WITH_MATROSKA
#include "matroskafile.h"
#endif
using namespace TagLib;
@@ -135,6 +158,7 @@ namespace
if(ext == "MP3" || ext == "MP2" || ext == "AAC")
file = new MPEG::File(stream, readAudioProperties, audioPropertiesStyle);
#ifdef TAGLIB_WITH_VORBIS
else if(ext == "OGG")
file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "OGA") {
@@ -147,26 +171,38 @@ namespace
}
else if(ext == "FLAC")
file = new FLAC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "MPC")
file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "WV")
file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "SPX")
file = new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "OPUS")
file = new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_APE
else if(ext == "MPC")
file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "WV")
file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "APE")
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_TRUEAUDIO
else if(ext == "TTA")
file = new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_MP4
else if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V")
file = new MP4::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_ASF
else if(ext == "WMA" || ext == "ASF")
file = new ASF::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_RIFF
else if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC")
file = new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "WAV")
file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "APE")
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_MOD
// module, nst and wow are possible but uncommon extensions
else if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW")
file = new Mod::File(stream, readAudioProperties, audioPropertiesStyle);
@@ -176,10 +212,21 @@ namespace
file = new IT::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "XM")
file = new XM::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_DSF
else if(ext == "DSF")
file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(ext == "DFF" || ext == "DSDIFF")
file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_SHORTEN
else if(ext == "SHN")
file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_MATROSKA
else if(ext == "MKA" || ext == "MKV" || ext == "WEBM")
file = new Matroska::File(stream, readAudioProperties);
#endif
// if file is not valid, leave it to content-based detection.
@@ -201,36 +248,58 @@ namespace
if(MPEG::File::isSupported(stream))
file = new MPEG::File(stream, readAudioProperties, audioPropertiesStyle);
#ifdef TAGLIB_WITH_VORBIS
else if(Ogg::Vorbis::File::isSupported(stream))
file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::FLAC::File::isSupported(stream))
file = new Ogg::FLAC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(FLAC::File::isSupported(stream))
file = new FLAC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(MPC::File::isSupported(stream))
file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(WavPack::File::isSupported(stream))
file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::Speex::File::isSupported(stream))
file = new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle);
else if(Ogg::Opus::File::isSupported(stream))
file = new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_APE
else if(MPC::File::isSupported(stream))
file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle);
else if(WavPack::File::isSupported(stream))
file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle);
else if(APE::File::isSupported(stream))
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_TRUEAUDIO
else if(TrueAudio::File::isSupported(stream))
file = new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_MP4
else if(MP4::File::isSupported(stream))
file = new MP4::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_ASF
else if(ASF::File::isSupported(stream))
file = new ASF::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_RIFF
else if(RIFF::AIFF::File::isSupported(stream))
file = new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(RIFF::WAV::File::isSupported(stream))
file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
else if(APE::File::isSupported(stream))
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_DSF
else if(DSF::File::isSupported(stream))
file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(DSDIFF::File::isSupported(stream))
file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_SHORTEN
else if(Shorten::File::isSupported(stream))
file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
#ifdef TAGLIB_WITH_MATROSKA
else if(Matroska::File::isSupported(stream))
file = new Matroska::File(stream, readAudioProperties, audioPropertiesStyle);
#endif
// isSupported() only does a quick check, so double check the file here.
@@ -263,7 +332,7 @@ public:
return !file || !file->isValid();
}
bool isNullWithDebugMessage(const String &methodName) const
bool isNullWithDebugMessage([[maybe_unused]] const String &methodName) const
{
if(isNull()) {
debug("FileRef::" + methodName + "() - Called without a valid file.");
@@ -400,17 +469,25 @@ StringList FileRef::defaultFileExtensions()
{
StringList l;
l.append("mp3");
l.append("mp2");
l.append("aac");
#ifdef TAGLIB_WITH_VORBIS
l.append("ogg");
l.append("flac");
l.append("oga");
l.append("opus");
l.append("mp3");
l.append("mp2");
l.append("spx");
#endif
#ifdef TAGLIB_WITH_APE
l.append("mpc");
l.append("wv");
l.append("spx");
l.append("ape");
#endif
#ifdef TAGLIB_WITH_TRUEAUDIO
l.append("tta");
l.append("aac");
#endif
#ifdef TAGLIB_WITH_MP4
l.append("m4a");
l.append("m4r");
l.append("m4b");
@@ -418,14 +495,19 @@ StringList FileRef::defaultFileExtensions()
l.append("3g2");
l.append("mp4");
l.append("m4v");
#endif
#ifdef TAGLIB_WITH_ASF
l.append("wma");
l.append("asf");
#endif
#ifdef TAGLIB_WITH_RIFF
l.append("aif");
l.append("aiff");
l.append("afc");
l.append("aifc");
l.append("wav");
l.append("ape");
#endif
#ifdef TAGLIB_WITH_MOD
l.append("mod");
l.append("module"); // alias for "mod"
l.append("nst"); // alias for "mod"
@@ -433,9 +515,20 @@ StringList FileRef::defaultFileExtensions()
l.append("s3m");
l.append("it");
l.append("xm");
#endif
#ifdef TAGLIB_WITH_DSF
l.append("dsf");
l.append("dff");
l.append("dsdiff"); // alias for "dff"
#endif
#ifdef TAGLIB_WITH_SHORTEN
l.append("shn");
#endif
#ifdef TAGLIB_WITH_MATROSKA
l.append("mkv");
l.append("mka");
l.append("webm");
#endif
return l;
}

View File

@@ -265,12 +265,19 @@ bool FLAC::File::save()
// Render data for the metadata blocks
ByteVector data;
for(const auto &block : std::as_const(d->blocks)) {
ByteVector blockData = block->render();
for(auto it = d->blocks.begin(); it != d->blocks.end();) {
ByteVector blockData = (*it)->render();
ByteVector blockHeader = ByteVector::fromUInt(blockData.size());
blockHeader[0] = block->code();
if(blockHeader[0] != 0) {
debug("FLAC::File::save() -- Removing too large block.");
delete *it;
it = d->blocks.erase(it);
continue;
}
blockHeader[0] = static_cast<char>((*it)->code());
data.append(blockHeader);
data.append(blockData);
++it;
}
// Compute the amount of padding, and append that to data.

View File

@@ -134,7 +134,7 @@ void FLAC::Properties::read(const ByteVector &data, offset_t streamLength)
if(d->sampleFrames > 0 && d->sampleRate > 0) {
const auto length = static_cast<double>(d->sampleFrames) * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
}
if(data.size() >= pos + 16)

View File

@@ -167,7 +167,7 @@ bool IT::File::save()
if(messageOffset + messageLength >= fileSize) {
// append new message
seek(54);
writeU16L(message.size());
writeU16L(static_cast<unsigned short>(message.size()));
writeU32L(messageOffset);
seek(messageOffset);
writeBlock(message);

View File

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

View File

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

View File

@@ -0,0 +1,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);
}

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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());
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,186 @@
/***************************************************************************
* 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"
//! 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;
/*!
* Returns a pointer to the attachments of the file.
*
* If \a create is \c false this may return a null pointer if there are no
* attachments.
* If \a create is \c true it will create attachments if none exist and
* returns a valid pointer.
*/
Attachments *attachments(bool create = false) const;
/*!
* Returns a pointer to the chapters of the file.
*
* If \a create is \c false this may return a null pointer if there are no
* chapters.
* If \a create is \c true it will create chapters if none exist and
* returns a valid pointer.
*/
Chapters *chapters(bool create = false) const;
/*!
* Returns whether or not the given \a stream can be opened as a Matroska
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
void read(bool readProperties, Properties::ReadStyle readStyle);
class FilePrivate;
friend class Properties;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<FilePrivate> d;
};
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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 {};
}

View 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

View File

@@ -0,0 +1,660 @@
/***************************************************************************
* 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));
}
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;
}

View 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

View File

@@ -37,7 +37,7 @@ namespace {
constexpr std::array containers {
"moov", "udta", "mdia", "meta", "ilst",
"stbl", "minf", "moof", "traf", "trak",
"stsd"
"stsd", "stem"
};
} // namespace
@@ -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") {

View File

@@ -60,8 +60,8 @@ namespace TagLib {
#ifndef DO_NOT_DOCUMENT
struct AtomData {
AtomData(AtomDataType type, const ByteVector &data) :
type(type), data(data) { }
AtomData(AtomDataType ptype, const ByteVector &pdata) :
type(ptype), data(pdata) { }
AtomDataType type;
int locale { 0 };
ByteVector data;

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

View File

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

View File

@@ -87,6 +87,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 +130,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 +179,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 +226,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:
@@ -303,6 +308,7 @@ ItemFactory::NameHandlerMap ItemFactory::nameHandlerMap() const
{"akID", ItemHandlerType::Byte},
{"gnre", ItemHandlerType::Gnre},
{"covr", ItemHandlerType::Covr},
{"stem", ItemHandlerType::Stem},
{"purl", ItemHandlerType::TextImplicit},
{"egid", ItemHandlerType::TextImplicit},
};
@@ -400,7 +406,7 @@ Map<ByteVector, String> ItemFactory::namePropertyMap() const
}
MP4::AtomDataList ItemFactory::parseData2(
const MP4::Atom *atom, const ByteVector &data, int expectedFlags,
const MP4::Atom *, const ByteVector &data, int expectedFlags,
bool freeForm)
{
AtomDataList result;
@@ -633,6 +639,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)
@@ -663,7 +675,7 @@ ByteVector ItemFactory::renderInt(
const ByteVector &name, const MP4::Item &item)
{
ByteVectorList data;
data.append(ByteVector::fromShort(item.toInt()));
data.append(ByteVector::fromShort(static_cast<short>(item.toInt())));
return renderData(name, TypeInteger, data);
}
@@ -703,8 +715,8 @@ ByteVector ItemFactory::renderIntPair(
{
ByteVectorList data;
data.append(ByteVector(2, '\0') +
ByteVector::fromShort(item.toIntPair().first) +
ByteVector::fromShort(item.toIntPair().second) +
ByteVector::fromShort(static_cast<short>(item.toIntPair().first)) +
ByteVector::fromShort(static_cast<short>(item.toIntPair().second)) +
ByteVector(2, '\0'));
return renderData(name, TypeImplicit, data);
}
@@ -714,8 +726,8 @@ ByteVector ItemFactory::renderIntPairNoTrailing(
{
ByteVectorList data;
data.append(ByteVector(2, '\0') +
ByteVector::fromShort(item.toIntPair().first) +
ByteVector::fromShort(item.toIntPair().second));
ByteVector::fromShort(static_cast<short>(item.toIntPair().first)) +
ByteVector::fromShort(static_cast<short>(item.toIntPair().second)));
return renderData(name, TypeImplicit, data);
}
@@ -742,6 +754,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)
{

View File

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

View File

@@ -196,7 +196,7 @@ MP4::Properties::read(File *file, const Atoms *atoms)
}
}
if(unit > 0 && length > 0)
d->length = static_cast<int>(length * 1000.0 / unit + 0.5);
d->length = static_cast<int>(static_cast<double>(length) * 1000.0 / static_cast<double>(unit) + 0.5);
MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd");
if(!atom) {

64
taglib/mp4/mp4stem.cpp Normal file
View 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
View 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

View File

@@ -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;
}
@@ -227,11 +255,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 +533,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 +570,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 +603,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;
}

View File

@@ -229,7 +229,7 @@ void MPC::Properties::readSV8(File *file, offset_t streamLength)
frameCount > 0 && d->sampleRate > 0) {
const auto length = static_cast<double>(frameCount) * 1000.0 / d->sampleRate;
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
}
}
else if (packetType == "RG") {
@@ -331,6 +331,6 @@ void MPC::Properties::readSV7(const ByteVector &data, offset_t streamLength)
d->length = static_cast<int>(length + 0.5);
if(d->bitrate == 0)
d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
d->bitrate = static_cast<int>(static_cast<double>(streamLength) * 8.0 / length + 0.5);
}
}

View File

@@ -176,7 +176,7 @@ void ID3v1::Tag::setComment(const String &s)
void ID3v1::Tag::setGenre(const String &s)
{
d->genre = ID3v1::genreIndex(s);
d->genre = static_cast<unsigned char>(ID3v1::genreIndex(s));
}
void ID3v1::Tag::setYear(unsigned int i)
@@ -186,7 +186,7 @@ void ID3v1::Tag::setYear(unsigned int i)
void ID3v1::Tag::setTrack(unsigned int i)
{
d->track = i < 256 ? i : 0;
d->track = static_cast<unsigned char>(i < 256 ? i : 0);
}
unsigned int ID3v1::Tag::genreNumber() const
@@ -196,7 +196,7 @@ unsigned int ID3v1::Tag::genreNumber() const
void ID3v1::Tag::setGenreNumber(unsigned int i)
{
d->genre = i < 256 ? i : 255;
d->genre = static_cast<unsigned char>(i < 256 ? i : 255);
}
void ID3v1::Tag::setStringHandler(const StringHandler *handler)

View File

@@ -30,6 +30,7 @@
#include "tbytevectorlist.h"
#include "tdebug.h"
#include "tpropertymap.h"
#include "unknownframe.h"
using namespace TagLib;
using namespace ID3v2;
@@ -259,7 +260,7 @@ void ChapterFrame::parseFields(const ByteVector &data)
return;
// Checks to make sure that frame parsed correctly.
if(frame->size() <= 0) {
if(frame->size() <= 0 || dynamic_cast<UnknownFrame *>(frame)) {
delete frame;
return;
}

Some files were not shown because too many files have changed in this diff Show More