From d2bf7e72d2e753bc2c425b0dbd764b96c7283790 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Thu, 4 Sep 2025 15:58:02 +0200 Subject: [PATCH] Unit tests for Matroska --- tests/data/no-tags.mka | Bin 0 -> 12390 bytes tests/data/no-tags.webm | Bin 0 -> 352 bytes tests/data/optimized.mkv | Bin 0 -> 3341 bytes tests/data/tags-before-cues.mkv | Bin 0 -> 3412 bytes tests/test_matroska.cpp | 980 +++++++++++++++++++++++++++++++- tests/test_sizes.cpp | 10 +- 6 files changed, 984 insertions(+), 6 deletions(-) create mode 100644 tests/data/no-tags.mka create mode 100644 tests/data/no-tags.webm create mode 100644 tests/data/optimized.mkv create mode 100644 tests/data/tags-before-cues.mkv diff --git a/tests/data/no-tags.mka b/tests/data/no-tags.mka new file mode 100644 index 0000000000000000000000000000000000000000..b69ed2c435f73b6a468e9322df9b638686c31fba GIT binary patch literal 12390 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 literal 0 HcmV?d00001 diff --git a/tests/test_matroska.cpp b/tests/test_matroska.cpp index d1fc3c0a..ca1cd0e0 100644 --- a/tests/test_matroska.cpp +++ b/tests/test_matroska.cpp @@ -1,10 +1,136 @@ +/*************************************************************************** + 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/ * + ***************************************************************************/ + +/* + * # Test files + * + * no-tags.mka was created from a small mp3 file using + * mkvmerge -o small.mka small.mp3 + * The tags were removed afterwards. + * + * tags-before-cues.mkv was created using + * ffmpeg -t 0.1 -s qcif -f rawvideo -pix_fmt rgb24 -r 25 \ + * -i /dev/zero zero_second.mkv + * The tags were added using Handbrake, which places them before the Cluster + * and Cues elements. The tags were rewritten to conform to the specification. + * + * optimized.mpk is the same as tags-before-cues.mkv, but optimized using + * mkclean --keep-cues --optimize tags-before-cues.mkv + * This is done to have a segment with the size encoded with fewer than 8 bytes, + * so the resizing of the segment can be verified by adding a large attachment. + * + * no-tags.webm was created using + * ffmpeg -f lavfi -i color=c=blue:s=64x64 -frames:v 1 \ + * -pix_fmt yuv420p frame.yuv + * vpxenc --codec=vp8 --width=64 --height=64 --fps=10/1 \ + * --end-usage=cq --cq-level=4 --lag-in-frames=0 --auto-alt-ref=0 \ + * --profile=0 --target-bitrate=1000 -o onems.webm frame.yuv + * Then the EMBL void element after the seek head was transformed to have + * the content size encoded with 8 bytes instead of 1 byte to have it in the + * same format as used here. + * + * # File validation + * + * The files are read with read style "Accurate" to verify the segment positions + * in seek head and cues. + * + * # Manual testing + * + * The integrity of Matroska files modified with TagLib can be verified with + * mkvalidator v0.6.0. For inspection of files, mkvinfo and mkvtoolnix-gui + * are useful. + * + * All tags of Matroska files can be read and written using properties and + * complex properties, so tagwriter and tagreader are sufficient for testing. + * + * - Set standard tags + * examples/tagwriter -t 'Track Title' -a 'Artist Name' -A 'Album Title' \ + * -c 'Comment' -g 'Genre' -y 2025 -T 1 test.mka + * + * To remove standard tags, set them to an empty string or 0 for numeric tags. + * Using the property interface, the properties listed in propertymapping.dox + * and arbitrary string tags with track target level can be written. + * + * - Insert property + * examples/tagwriter -I 'ALBUMARTIST' 'Album Artist' test.mka + * - Replace property + * examples/tagwriter -R 'ALBUMARTIST' 'Other Artist' test.mka + * - Delete property + * examples/tagwriter -D 'ALBUMARTIST' test.mka + * + * Pictures can be attached with a description + * examples/tagwriter -p file.jpg 'Picture description' test.mka + * + * Alternatively, they can be set using complex properties. A complex property + * can be set with + * + * examples/tagwriter -C FILE + * + * The second parameter can be set to "" to delete complex properties with the + * given key. To 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 given after "file://". There is + * no escape character, but hex codes are supported, e.g. "\x2C" to include a ',' + * and \x3B to include a ';'. + * + * - 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 + * + * # Test coverage + * + * Not yet covered by the unit tests are: + * - MkCueDuration, MkCueBlockNumber, MkCueCodecState, MkCueReference, MkCueRefTime, + * MkBitDepth and the methods handling these elements because none of the test + * files has such elements, + * - some unused functions like EBML::parseVINT(), EBML::FloatElement::render(), + * - some error cases because they never occur with the unit tests. + */ + #include #include #include "tbytevectorlist.h" +#include "tbytevectorstream.h" #include "tpropertymap.h" -#include "tag.h" #include "matroskafile.h" +#include "matroskatag.h" +#include "matroskaattachments.h" +#include "matroskaattachedfile.h" +#include "matroskasimpletag.h" #include "plainfile.h" #include #include "utils.h" @@ -15,14 +141,860 @@ using namespace TagLib; class TestMatroska : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestMatroska); - CPPUNIT_TEST(testTags); + CPPUNIT_TEST(testPropertiesMka); + CPPUNIT_TEST(testPropertiesMkv); + CPPUNIT_TEST(testPropertiesWebm); + CPPUNIT_TEST(testSimpleTagsAndAttachments); + CPPUNIT_TEST(testAddRemoveTagsAttachments); + CPPUNIT_TEST(testTagsWebm); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST(testPropertyInterface); + CPPUNIT_TEST(testComplexProperties); + CPPUNIT_TEST(testOpenInvalid); + CPPUNIT_TEST(testSegmentSizeChange); CPPUNIT_TEST_SUITE_END(); public: - void testTags() + void testPropertiesMka() { - // TODO implement + Matroska::File f(TEST_FILE_PATH_C("no-tags.mka")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(444, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(223, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(String("matroska"), f.audioProperties()->docType()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->docTypeVersion()); + CPPUNIT_ASSERT_EQUAL(String("A_MPEG/L3"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String(""), f.audioProperties()->title()); } + + void testPropertiesMkv() + { + Matroska::File f(TEST_FILE_PATH_C("tags-before-cues.mkv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(120, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(227, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(String("matroska"), f.audioProperties()->docType()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->docTypeVersion()); + CPPUNIT_ASSERT_EQUAL(String(""), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("handbrake"), f.audioProperties()->title()); + } + + void testPropertiesWebm() + { + Matroska::File f(TEST_FILE_PATH_C("no-tags.webm")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(2816, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(String("webm"), f.audioProperties()->docType()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->docTypeVersion()); + CPPUNIT_ASSERT_EQUAL(String(""), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String(""), f.audioProperties()->title()); + + Matroska::File noProps(TEST_FILE_PATH_C("no-tags.webm"), false); + CPPUNIT_ASSERT(!noProps.audioProperties()); + } + + void testSimpleTagsAndAttachments() + { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + auto tag = f.tag(true); + CPPUNIT_ASSERT(tag->isEmpty()); + tag->addSimpleTag(Matroska::SimpleTag( + "Test Name 1", String("Test Value 1"), + Matroska::SimpleTag::TargetTypeValue::Track, "en")); + tag->addSimpleTag(Matroska::SimpleTag( + "Test Name 2", String("Test Value 2"), + Matroska::SimpleTag::TargetTypeValue::Album)); + tag->setTitle("Test title"); + tag->setArtist("Test artist"); + tag->setYear(1969); + auto attachments = f.attachments(true); + Matroska::AttachedFile attachedFile; + attachedFile.setFileName("cover.jpg"); + attachedFile.setMediaType("image/jpeg"); + attachedFile.setDescription("Cover"); + attachedFile.setData(ByteVector("JPEG data")); + attachedFile.setUID(5081000385627515072ULL); + attachments->addAttachedFile(attachedFile); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + auto attachments = f.attachments(false); + CPPUNIT_ASSERT(attachments); + CPPUNIT_ASSERT(!tag->isEmpty()); + + CPPUNIT_ASSERT_EQUAL(String("Test title"), tag->title()); + CPPUNIT_ASSERT_EQUAL(String("Test artist"), tag->artist()); + CPPUNIT_ASSERT_EQUAL(1969U, tag->year()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->album()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->comment()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->genre()); + CPPUNIT_ASSERT_EQUAL(0U, tag->track()); + + const auto &simpleTags = tag->simpleTagsList(); + CPPUNIT_ASSERT_EQUAL(5U, simpleTags.size()); + + CPPUNIT_ASSERT_EQUAL(String("en"), simpleTags[0].language()); + CPPUNIT_ASSERT_EQUAL(String("Test Name 1"), simpleTags[0].name()); + CPPUNIT_ASSERT_EQUAL(String("Test Value 1"), simpleTags[0].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[0].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[0].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[0].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[0].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[1].language()); + CPPUNIT_ASSERT_EQUAL(String("TITLE"), simpleTags[1].name()); + CPPUNIT_ASSERT_EQUAL(String("Test title"), simpleTags[1].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[1].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[1].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[1].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[1].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[1].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[2].language()); + CPPUNIT_ASSERT_EQUAL(String("ARTIST"), simpleTags[2].name()); + CPPUNIT_ASSERT_EQUAL(String("Test artist"), simpleTags[2].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[2].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[2].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[2].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[2].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[2].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[3].language()); + CPPUNIT_ASSERT_EQUAL(String("Test Name 2"), simpleTags[3].name()); + CPPUNIT_ASSERT_EQUAL(String("Test Value 2"), simpleTags[3].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[3].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[3].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Album, simpleTags[3].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[3].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[3].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[4].language()); + CPPUNIT_ASSERT_EQUAL(String("DATE_RELEASED"), simpleTags[4].name()); + CPPUNIT_ASSERT_EQUAL(String("1969"), simpleTags[4].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[4].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[4].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Album, simpleTags[4].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[4].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[4].type()); + + const auto &attachedFiles = attachments->attachedFileList(); + CPPUNIT_ASSERT_EQUAL(1U, attachedFiles.size()); + + CPPUNIT_ASSERT_EQUAL(String("Cover"), attachedFiles[0].description()); + CPPUNIT_ASSERT_EQUAL(String("cover.jpg"), attachedFiles[0].fileName()); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), attachedFiles[0].mediaType()); + CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), attachedFiles[0].data()); + CPPUNIT_ASSERT_EQUAL(5081000385627515072ULL, attachedFiles[0].uid()); + + PropertyMap expectedProps; + expectedProps["ARTIST"] = StringList("Test artist"); + expectedProps["DATE"] = StringList("1969"); + expectedProps["TEST NAME 1"] = StringList("Test Value 1"); + expectedProps["TITLE"] = StringList("Test title"); + expectedProps.addUnsupportedData("Test Name 2"); + auto props = f.properties(); + if (expectedProps != props) { + CPPUNIT_ASSERT_EQUAL(expectedProps.toString(), props.toString()); + } + CPPUNIT_ASSERT(expectedProps == props); + CPPUNIT_ASSERT(expectedProps == tag->properties()); + + auto keys = f.complexPropertyKeys(); + CPPUNIT_ASSERT_EQUAL(StringList({"Test Name 2", "PICTURE"}), keys); + auto pictures = f.complexProperties("PICTURE"); + CPPUNIT_ASSERT_EQUAL(1U, pictures.size()); + const VariantMap expectedPic { + {"data", ByteVector("JPEG data")}, + {"mimeType", "image/jpeg"}, + {"description", "Cover"}, + {"fileName", "cover.jpg"}, + {"uid", 5081000385627515072ULL} + }; + CPPUNIT_ASSERT(List({expectedPic}) == pictures); + const VariantMap expectedComplexProps { + {"defaultLanguage", true}, + {"language", "und"}, + {"name", "Test Name 2"}, + {"value", "Test Value 2"}, + {"targetTypeValue", 50}, + }; + auto complexProps = f.complexProperties("Test Name 2"); + CPPUNIT_ASSERT(List({expectedComplexProps}) == complexProps); + + tag->clearSimpleTags(); + attachments->clear(); + f.save(); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + + // Check if file without tags is same as original empty file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("no-tags.mka")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + + void testAddRemoveTagsAttachments() + { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + f.tag(true)->setComment("C"); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(String("C"), f.tag(false)->comment()); + f.tag()->setComment(""); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + f.attachments(true)->addAttachedFile(Matroska::AttachedFile()); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + const auto &attachedFiles = f.attachments(false)->attachedFileList(); + CPPUNIT_ASSERT_EQUAL(1U, attachedFiles.size()); + f.attachments()->removeAttachedFile(attachedFiles.front().uid()); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + } + + void testTagsWebm() + { + ScopedFileCopy copy("no-tags", ".webm"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + + f.setProperties(SimplePropertyMap{ + {"ARTIST", {"First artist", "second artist"}}, + {"", {"Invalid", "Ignored"}} + }); + f.tag(false)->addSimpleTag(Matroska::SimpleTag("", ByteVector("Not valid"))); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), false, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(!f.attachments(false)); + CPPUNIT_ASSERT_EQUAL(String("First artist"), tag->artist()); + CPPUNIT_ASSERT_EQUAL(StringList({"First artist", "second artist"}), f.properties().value("ARTIST")); + + tag->setAlbum("Album"); + tag->setTrack(5); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), false, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT_EQUAL(String("First artist"), tag->artist()); + CPPUNIT_ASSERT_EQUAL(String("Album"), tag->album()); + CPPUNIT_ASSERT_EQUAL(5U, tag->track()); + + tag->setArtist(""); + tag->removeSimpleTag("TITLE", Matroska::SimpleTag::TargetTypeValue::Album); + tag->setTrack(0); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), false, AudioProperties::Accurate); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + + // Check if file without tags is same as original empty file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("no-tags.webm")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + + void testRepeatedSave() + { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + String text = "01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789"; + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.save()); + f.tag(true)->setTitle(text.substr(0, 23)); + CPPUNIT_ASSERT(f.save()); + f.tag(true)->setTitle(text.substr(0, 5)); + CPPUNIT_ASSERT(f.save()); + f.tag(true)->setTitle(text); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(text, f.tag()->title()); + } + } + + void testPropertyInterface() + { + ScopedFileCopy copy("tags-before-cues", ".mkv"); + string newname = copy.fileName(); + + PropertyMap initialProps; + initialProps["ARTIST"] = StringList("Actors"); + initialProps["DATE"] = StringList("2023"); + initialProps["DESCRIPTION"] = StringList("Description"); + initialProps["DIRECTOR"] = StringList("Director"); + initialProps["ENCODEDBY"] = StringList("Lavf59.27.100"); + initialProps["GENRE"] = StringList("Genre"); + initialProps["SUMMARY"] = StringList("Comment"); + initialProps["SYNOPSIS"] = StringList("Plot"); + + const VariantMap initialComplexProps { + {"defaultLanguage", true}, + {"language", "und"}, + {"name", "DURATION"}, + {"value", "00:00:00.120000000"}, + {"trackUid", 9584013959154292683ULL}, + }; + + PropertyMap newProps; + newProps["ALBUM"] = StringList("Album"); + newProps["ALBUMARTIST"] = StringList("Album Artist"); + newProps["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + newProps["ALBUMSORT"] = StringList("Album Sort"); + newProps["ARTIST"] = StringList("Artist"); + newProps["ARTISTS"] = StringList("Artists"); + newProps["ARTISTSORT"] = StringList("Artist Sort"); + newProps["ASIN"] = StringList("ASIN"); + newProps["BARCODE"] = StringList("Barcode"); + newProps["CATALOGNUMBER"] = StringList("Catalog Number 1").append("Catalog Number 2"); + newProps["COMMENT"] = StringList("Comment"); + newProps["DATE"] = StringList("2021-01-10"); + newProps["DISCNUMBER"] = StringList("3"); + newProps["DISCTOTAL"] = StringList("5"); + newProps["DJMIXER"] = StringList("Mixed by"); + newProps["ENCODEDBY"] = StringList("Encoded by"); + newProps["ENCODING"] = StringList("Encoder settings"); + newProps["ENCODINGTIME"] = StringList("Date encoded"); + newProps["GENRE"] = StringList("Genre"); + newProps["INITIALKEY"] = StringList("Initial key"); + newProps["ISRC"] = StringList("UKAAA0500001"); + newProps["LABEL"] = StringList("Label 1").append("Label 2"); + newProps["MEDIA"] = StringList("Media"); + newProps["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + newProps["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + newProps["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + newProps["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + newProps["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + newProps["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + newProps["OWNER"] = StringList("Purchase owner"); + newProps["ORIGINALDATE"] = StringList("2021-01-09"); + newProps["RELEASECOUNTRY"] = StringList("Release Country"); + newProps["RELEASEDATE"] = StringList("2021-01-10"); + newProps["RELEASESTATUS"] = StringList("Release Status"); + newProps["RELEASETYPE"] = StringList("Release Type"); + newProps["REMIXER"] = StringList("Remixed by"); + newProps["SCRIPT"] = StringList("Script"); + newProps["TAGGINGDATE"] = StringList("2021-01-08"); + newProps["TITLE"] = StringList("Title"); + newProps["TRACKNUMBER"] = StringList("2"); + newProps["TRACKTOTAL"] = StringList("4"); + + const VariantMap newComplexProps { + {"defaultLanguage", false}, + {"language", "en"}, + {"name", "BINARY"}, + {"data", ByteVector("\x01\x02\x03\x04\x05\x06")}, + {"targetTypeValue", static_cast(Matroska::SimpleTag::TargetTypeValue::Collection)}, + }; + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(!f.attachments(false)); + + CPPUNIT_ASSERT_EQUAL(String("handbrake"), tag->title()); + CPPUNIT_ASSERT_EQUAL(String("Actors"), tag->artist()); + CPPUNIT_ASSERT_EQUAL(2023U, tag->year()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->album()); + CPPUNIT_ASSERT_EQUAL(String(""), tag->comment()); + CPPUNIT_ASSERT_EQUAL(String("Genre"), tag->genre()); + CPPUNIT_ASSERT_EQUAL(0U, tag->track()); + + const auto &simpleTags = tag->simpleTagsList(); + CPPUNIT_ASSERT_EQUAL(9U, simpleTags.size()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[0].language()); + CPPUNIT_ASSERT_EQUAL(String("DURATION"), simpleTags[0].name()); + CPPUNIT_ASSERT_EQUAL(String("00:00:00.120000000"), simpleTags[0].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[0].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::None, simpleTags[0].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(9584013959154292683ULL, simpleTags[0].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[0].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[1].language()); + CPPUNIT_ASSERT_EQUAL(String("ARTIST"), simpleTags[1].name()); + CPPUNIT_ASSERT_EQUAL(String("Actors"), simpleTags[1].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[1].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[1].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[1].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[1].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[1].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[2].language()); + CPPUNIT_ASSERT_EQUAL(String("DESCRIPTION"), simpleTags[2].name()); + CPPUNIT_ASSERT_EQUAL(String("Description"), simpleTags[2].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[2].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[2].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[2].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[2].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[2].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[3].language()); + CPPUNIT_ASSERT_EQUAL(String("DIRECTOR"), simpleTags[3].name()); + CPPUNIT_ASSERT_EQUAL(String("Director"), simpleTags[3].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[3].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[3].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[3].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[3].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[3].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[4].language()); + CPPUNIT_ASSERT_EQUAL(String("ENCODER"), simpleTags[4].name()); + CPPUNIT_ASSERT_EQUAL(String("Lavf59.27.100"), simpleTags[4].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[4].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[4].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[4].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[4].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[4].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[5].language()); + CPPUNIT_ASSERT_EQUAL(String("GENRE"), simpleTags[5].name()); + CPPUNIT_ASSERT_EQUAL(String("Genre"), simpleTags[5].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[5].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[5].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[5].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[5].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[5].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[6].language()); + CPPUNIT_ASSERT_EQUAL(String("SUMMARY"), simpleTags[6].name()); + CPPUNIT_ASSERT_EQUAL(String("Comment"), simpleTags[6].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[6].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[6].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[6].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[6].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[6].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[7].language()); + CPPUNIT_ASSERT_EQUAL(String("SYNOPSIS"), simpleTags[7].name()); + CPPUNIT_ASSERT_EQUAL(String("Plot"), simpleTags[7].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[7].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[7].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[7].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[7].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[7].type()); + + CPPUNIT_ASSERT_EQUAL(String("und"), simpleTags[8].language()); + CPPUNIT_ASSERT_EQUAL(String("DATE_RELEASED"), simpleTags[8].name()); + CPPUNIT_ASSERT_EQUAL(String("2023"), simpleTags[8].toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[8].toByteVector()); + CPPUNIT_ASSERT_EQUAL(true, simpleTags[8].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Album, simpleTags[8].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(0ULL, simpleTags[8].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[8].type()); + + auto props = f.properties(); + if (initialProps != props) { + CPPUNIT_ASSERT_EQUAL(initialProps.toString(), props.toString()); + } + CPPUNIT_ASSERT(initialProps == props); + CPPUNIT_ASSERT(initialProps == tag->properties()); + + auto keys = f.complexPropertyKeys(); + CPPUNIT_ASSERT_EQUAL(StringList({"DURATION"}), keys); + auto complexProps = f.complexProperties("DURATION"); + CPPUNIT_ASSERT(List({initialComplexProps}) == complexProps); + + CPPUNIT_ASSERT(f.setComplexProperties("DURATION", {})); + CPPUNIT_ASSERT(f.setComplexProperties("BINARY", {newComplexProps})); + CPPUNIT_ASSERT(f.setProperties(newProps).isEmpty()); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(false); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(!f.attachments(false)); + + auto props = f.properties(); + if (newProps != props) { + CPPUNIT_ASSERT_EQUAL(newProps.toString(), props.toString()); + } + CPPUNIT_ASSERT(newProps == props); + CPPUNIT_ASSERT(newProps == tag->properties()); + + auto keys = f.complexPropertyKeys(); + CPPUNIT_ASSERT_EQUAL(StringList({"BINARY"}), keys); + auto complexProps = f.complexProperties("BINARY"); + CPPUNIT_ASSERT(List({newComplexProps}) == complexProps); + + const auto &simpleTags = tag->simpleTagsList(); + StringList simpleTagNames; + for(const auto &simpleTag : simpleTags) { + simpleTagNames.append(simpleTag.name()); + } + const StringList expectedSimpleTagNames { + "BINARY", "TITLE", "ARTIST", "ARTISTSORT", "TITLESORT", + "DATE_RELEASED", "PART_NUMBER", "TOTAL_PARTS", + "MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ_ALBUMID", + "MUSICBRAINZ_RELEASEGROUPID", "ARTIST", "ARTISTS", "ARTISTSORT", + "ASIN", "BARCODE", "CATALOG_NUMBER", "CATALOG_NUMBER", "COMMENT", + "MIXED_BY", "ENCODER", "ENCODER_SETTINGS", "DATE_ENCODED", "GENRE", + "INITIAL_KEY", "ISRC", "LABEL_CODE", "LABEL_CODE", + "ORIGINAL_MEDIA_TYPE", "MUSICBRAINZ_ARTISTID", + "MUSICBRAINZ_RELEASETRACKID", "MUSICBRAINZ_TRACKID", "ORIGINALDATE", + "PURCHASE_OWNER", "RELEASECOUNTRY", "DATE_RELEASED", "RELEASESTATUS", + "RELEASETYPE", "REMIXED_BY", "SCRIPT", "DATE_TAGGED", "TITLE", + "PART_NUMBER", "TOTAL_PARTS" + }; + CPPUNIT_ASSERT_EQUAL(expectedSimpleTagNames, simpleTagNames); + + CPPUNIT_ASSERT(f.setComplexProperties("DURATION", {initialComplexProps})); + CPPUNIT_ASSERT(f.setComplexProperties("BINARY", {})); + CPPUNIT_ASSERT(f.setProperties(initialProps).isEmpty()); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + + // Check if file with initial tags is same as original file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("tags-before-cues.mkv")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + + void testComplexProperties() + { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + const VariantMap picture { + {"data", ByteVector("JPEG data")}, + {"mimeType", "image/jpeg"}, + {"description", "Cover"}, + {"fileName", "folder.jpg"}, + {"uid", 123ULL} + }; + const VariantMap font { + {"data", ByteVector("TTF data")}, + {"mimeType", "font/ttf"}, + {"description", "Subtitle font"}, + {"fileName", "file.ttf"}, + {"uid", 456ULL} + }; + const VariantMap trackUidTag { + {"defaultLanguage", true}, + {"language", "und"}, + {"name", "DURATION"}, + {"trackUid", 8315232342706310039ULL}, + {"value", "00:00:00.120000000"}, + }; + const VariantMap targetTypeTag { + {"defaultLanguage", false}, + {"language", "en"}, + {"name", "PART_NUMBER"}, + {"targetTypeValue", 20}, + {"value", "2"}, + }; + const VariantMap binaryTag { + {"defaultLanguage", false}, + {"language", "und"}, + {"name", "THUMBNAIL"}, + {"targetTypeValue", 30}, + {"data", ByteVector("JPEG data")}, + }; + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + CPPUNIT_ASSERT(f.complexPropertyKeys().isEmpty()); + CPPUNIT_ASSERT(f.complexProperties("PICTURE").isEmpty()); + + CPPUNIT_ASSERT(f.setComplexProperties("PICTURE", {picture})); + CPPUNIT_ASSERT(f.setComplexProperties("file.ttf", {font})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + auto attachments = f.attachments(false); + CPPUNIT_ASSERT(attachments); + + const auto &attachedFiles = attachments->attachedFileList(); + CPPUNIT_ASSERT_EQUAL(2U, attachedFiles.size()); + CPPUNIT_ASSERT_EQUAL(picture.value("fileName").value(), attachedFiles[0].fileName()); + CPPUNIT_ASSERT_EQUAL(picture.value("data").value(), attachedFiles[0].data()); + CPPUNIT_ASSERT_EQUAL(picture.value("description").value(), attachedFiles[0].description()); + CPPUNIT_ASSERT_EQUAL(picture.value("mimeType").value(), attachedFiles[0].mediaType()); + CPPUNIT_ASSERT_EQUAL(picture.value("uid").value(), attachedFiles[0].uid()); + CPPUNIT_ASSERT_EQUAL(font.value("fileName").value(), attachedFiles[1].fileName()); + CPPUNIT_ASSERT_EQUAL(font.value("data").value(), attachedFiles[1].data()); + CPPUNIT_ASSERT_EQUAL(font.value("description").value(), attachedFiles[1].description()); + CPPUNIT_ASSERT_EQUAL(font.value("mimeType").value(), attachedFiles[1].mediaType()); + CPPUNIT_ASSERT_EQUAL(font.value("uid").value(), attachedFiles[1].uid()); + + CPPUNIT_ASSERT_EQUAL(StringList({"PICTURE", "file.ttf"}), f.complexPropertyKeys()); + CPPUNIT_ASSERT(List({picture}) == f.complexProperties("PICTURE")); + CPPUNIT_ASSERT(List({font}) == f.complexProperties("file.ttf")); + + CPPUNIT_ASSERT(f.setComplexProperties("DURATION", {trackUidTag})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(true); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(f.attachments(false)); + + const auto &simpleTags = tag->simpleTagsList(); + CPPUNIT_ASSERT_EQUAL(1U, simpleTags.size()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("value").value(), simpleTags[0].toString()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("language").value(), simpleTags[0].language()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("name").value(), simpleTags[0].name()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("defaultLanguage").value(), simpleTags[0].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("trackUid").value(), simpleTags[0].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::None, simpleTags[0].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[0].type()); + + CPPUNIT_ASSERT_EQUAL(StringList({ "DURATION", "PICTURE", "file.ttf"}), f.complexPropertyKeys()); + CPPUNIT_ASSERT(List({trackUidTag}) == f.complexProperties("DURATION")); + + CPPUNIT_ASSERT(f.setComplexProperties("PICTURE", {})); + CPPUNIT_ASSERT(f.setComplexProperties("file.ttf", {})); + CPPUNIT_ASSERT(f.setComplexProperties("PART_NUMBER", {targetTypeTag})); + CPPUNIT_ASSERT(f.setComplexProperties("THUMBNAIL", {binaryTag})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(true); + CPPUNIT_ASSERT(tag); + CPPUNIT_ASSERT(!f.attachments(false)); + + const auto &simpleTags = tag->simpleTagsList(); + CPPUNIT_ASSERT_EQUAL(3U, simpleTags.size()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("value").value(), simpleTags[0].toString()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("language").value(), simpleTags[0].language()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("name").value(), simpleTags[0].name()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("defaultLanguage").value(), simpleTags[0].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(trackUidTag.value("trackUid").value(), simpleTags[0].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::None, simpleTags[0].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[0].type()); + + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("value").value(), simpleTags[1].toString()); + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("language").value(), simpleTags[1].language()); + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("name").value(), simpleTags[1].name()); + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("defaultLanguage").value(), simpleTags[1].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(targetTypeTag.value("trackUid").value(), simpleTags[1].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Subtrack, simpleTags[1].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), simpleTags[1].toByteVector()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::StringType, simpleTags[1].type()); + + CPPUNIT_ASSERT_EQUAL(binaryTag.value("data").value(), simpleTags[2].toByteVector()); + CPPUNIT_ASSERT_EQUAL(binaryTag.value("language").value(), simpleTags[2].language()); + CPPUNIT_ASSERT_EQUAL(binaryTag.value("name").value(), simpleTags[2].name()); + CPPUNIT_ASSERT_EQUAL(binaryTag.value("defaultLanguage").value(), simpleTags[2].defaultLanguageFlag()); + CPPUNIT_ASSERT_EQUAL(binaryTag.value("trackUid").value(), simpleTags[2].trackUid()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::TargetTypeValue::Track, simpleTags[2].targetTypeValue()); + CPPUNIT_ASSERT_EQUAL(String(), simpleTags[2].toString()); + CPPUNIT_ASSERT_EQUAL(Matroska::SimpleTag::ValueType::BinaryType, simpleTags[2].type()); + + CPPUNIT_ASSERT_EQUAL(StringList({"DURATION", "PART_NUMBER", "THUMBNAIL"}), f.complexPropertyKeys()); + CPPUNIT_ASSERT(List({trackUidTag}) == f.complexProperties("DURATION")); + CPPUNIT_ASSERT(List({targetTypeTag}) == f.complexProperties("PART_NUMBER")); + CPPUNIT_ASSERT(List({binaryTag}) == f.complexProperties("THUMBNAIL")); + + CPPUNIT_ASSERT(f.setComplexProperties("PART_NUMBER", {})); + CPPUNIT_ASSERT(f.setComplexProperties("THUMBNAIL", {})); + CPPUNIT_ASSERT(f.setComplexProperties("DURATION", {})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + + CPPUNIT_ASSERT(f.setComplexProperties("font/ttf", {{ + {"data", ByteVector("TTF data")}, + {"description", "Subtitle font"}, + {"fileName", "file.ttf"}, + }})); + CPPUNIT_ASSERT(f.setComplexProperties("file.otf", {{ + {"data", ByteVector("OTF data")}, + {"mimeType", "font/otf"}, + {"description", "OpenType"}, + }})); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.tag(false)); + CPPUNIT_ASSERT(f.attachments(false)); + + CPPUNIT_ASSERT_EQUAL(StringList({"file.ttf", "file.otf"}), f.complexPropertyKeys()); + auto ttfs = f.complexProperties("file.ttf"); + auto otfs = f.complexProperties("file.otf"); + CPPUNIT_ASSERT_EQUAL(1U, ttfs.size()); + CPPUNIT_ASSERT_EQUAL(1U, otfs.size()); + auto ttf = ttfs.front(); + auto otf = otfs.front(); + CPPUNIT_ASSERT_EQUAL(ByteVector("TTF data"), ttf.value("data").value()); + CPPUNIT_ASSERT_EQUAL(String("Subtitle font"), ttf.value("description").value()); + CPPUNIT_ASSERT_EQUAL(String("file.ttf"), ttf.value("fileName").value()); + CPPUNIT_ASSERT_EQUAL(String("font/ttf"), ttf.value("mimeType").value()); + CPPUNIT_ASSERT(ttf.value("uid").value() != 0ULL); + CPPUNIT_ASSERT_EQUAL(ByteVector("OTF data"), otf.value("data").value()); + CPPUNIT_ASSERT_EQUAL(String("OpenType"), otf.value("description").value()); + CPPUNIT_ASSERT_EQUAL(String("file.otf"), otf.value("fileName").value()); + CPPUNIT_ASSERT_EQUAL(String("font/otf"), otf.value("mimeType").value()); + CPPUNIT_ASSERT(otf.value("uid").value() != 0ULL); + + CPPUNIT_ASSERT(f.setComplexProperties("file.ttf", {})); + CPPUNIT_ASSERT(f.setComplexProperties("file.otf", {})); + CPPUNIT_ASSERT(f.save()); + } + + // Check if file without tags is same as original empty file + const ByteVector origData = PlainFile(TEST_FILE_PATH_C("no-tags.mka")).readAll(); + const ByteVector fileData = PlainFile(newname.c_str()).readAll(); + CPPUNIT_ASSERT(origData == fileData); + } + + void testOpenInvalid() + { + { + Matroska::File f(TEST_FILE_PATH_C("garbage.mp3")); + CPPUNIT_ASSERT(!f.isValid()); + } + { + ByteVector origData = PlainFile(TEST_FILE_PATH_C("no-tags.mka")).readAll(); + ByteVectorStream origStream(origData); + Matroska::File origFile(&origStream, true, AudioProperties::Accurate); + CPPUNIT_ASSERT(origFile.isValid()); + + ByteVector truncatedData = origData.mid(0, 4000); + ByteVectorStream truncatedStream(truncatedData); + Matroska::File truncatedFile(&truncatedStream, true, AudioProperties::Accurate); + CPPUNIT_ASSERT(!truncatedFile.isValid()); + } + } + + void testSegmentSizeChange() + { + ScopedFileCopy copy("optimized", ".mkv"); + string newname = copy.fileName(); + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + auto attachments = f.attachments(true); + Matroska::AttachedFile attachedFile; + attachedFile.setFileName("cover.jpg"); + attachedFile.setMediaType("image/jpeg"); + attachedFile.setDescription("Cover"); + // Large enough for emitSizeChanged() from Matroska::Segment::render() + attachedFile.setData(ByteVector(20000, 'x')); + attachedFile.setUID(5081000385627515072ULL); + attachments->addAttachedFile(attachedFile); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + auto tag = f.tag(true); + CPPUNIT_ASSERT(tag); + auto attachments = f.attachments(false); + CPPUNIT_ASSERT(attachments); + CPPUNIT_ASSERT(PropertyMap(SimplePropertyMap{ + {"ARTIST", {"Actors"}}, + {"DATE", {"2023"}}, + {"DESCRIPTION", {"Description"}}, + {"DIRECTOR", {"Director"}}, + {"ENCODEDBY", {"Lavf59.27.100"}}, + {"GENRE", {"Genre"}}, + {"SUMMARY", {"Comment"}}, + {"SYNOPSIS", {"Plot"}} + }) == tag->properties()); + + attachments->clear(); + f.save(); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT(!f.attachments(false)); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMatroska); diff --git a/tests/test_sizes.cpp b/tests/test_sizes.cpp index 9bf0ee93..550919fc 100644 --- a/tests/test_sizes.cpp +++ b/tests/test_sizes.cpp @@ -156,6 +156,9 @@ #include +#include "matroskaattachedfile.h" +#include "matroskaattachments.h" + using namespace std; using namespace TagLib; @@ -306,8 +309,11 @@ public: #ifdef TAGLIB_WITH_MATROSKA CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::File)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Properties)); - // TODO move non pimpl fields into private class. - // CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Tag)); + CPPUNIT_ASSERT_EQUAL(classSize(3, true), sizeof(TagLib::Matroska::Tag)); + CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::Matroska::Element)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Matroska::Attachments)); + CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::SimpleTag)); + CPPUNIT_ASSERT_EQUAL(classSize(0, false), sizeof(TagLib::Matroska::AttachedFile)); #endif }